0

I'm working on a Training Enrollment web application with the following (simplified) features:

  1. Maintenance (Add, Edit, Deactivation) of Trainings
  2. Student sign-up
  3. Student enrollment to Active Trainings

I've employed Onion Architecture. All my business logic/validation is in the Domain Layer and the Data Access Layer consists of ADO.NET components and stored procedures (no business logic whatsoever). The problem is that since the business logic/validation is in the domain, there may be a race condition in the following scenarios:

Scenario 1

  1. Training A allows a maximum of 50 students only

  2. Training A currently has 49 students enrolled

  3. In the web application, two users are attempting to enroll simultaneously. At this point two threads spawned from two concurrent requests to the web application are able to query 49 users, so both validations succeed in the Domain layer and both users are able to enroll.

Scenario 2

  1. Training A is created

  2. Student A is attempting to enroll to Training A

  3. While Student A is attempting to enroll to Training A, a Training Admin decides to deactivate Training A. A race condition occurs when in the Domain layer, when the following happens: In thread 1 of User 1, validating whether Training A is active succeeds (returns true) and at the same time;

    1. In thread 2, the thread on which Training A is being deactived validates that there are no enrollments yet; so that both validations succeed.

An obvious solution is to add business logic (checking for enrollment count, checking whether a Training is active) in my stored procedures but I'm hesitant to do this as it breaks the design.

My validation logic has a single point of entry and I can add a lock to such that the validation logic becomes a critical section so only one operation (validation and DB persistence) happens at a time, but this reduces concurrency.

Any alternative approach or how has this solved in the systems you've worked on?

1

I would go with locking and making sure that validating the enrollment is single-threaded.

This is similar to any one of another similar scenarios where a potentially large number of users are attempting to secure a scarce resource: registering for class/training, booking a seat on an airplane, buying a movie theater ticket in advance for opening day of a blockbuster, etc. In other words, to ensure that a scarce resource is assigned correctly in these situations, you have to use concurrency.

Most of the time, users will be okay with the tiny bit of additional time and low chance of failure ("please try again"). These are the sorts of processes that involve charging credit cards, talking with external systems, etc. and we are used to the drawbacks.

I am not sure where your validation logic is exactly, but this is normally done in the database as a stored procedure. If nothing else, a procedure that locks the registration table for modification, inserts a row, and either returns the record ID or fails. Aside from the locking logic in your flavor of SQL, it should be two or three statements.

  • My validation logic is in the Domain Layer, which consists of a stored procedure call (get the Training for enrollment from the database), and C# code (check if the retrieved Training is active). The Insert SPROC performs no check whatsoever, thus the potential race condition. The decision to have no business logic in SPROC is a design decision. And now I we're seeing limitation of that design. Thanks for your reply. – rro Apr 14 '15 at 3:10
  • Is there anything that can be done inside a stored procedure that can't be done without one other than saving a bunch of code on the database? – JeffO Apr 14 '15 at 17:19
0

An alternative approach, which may or may not be appropriate for your situation, would be to implement a queue and have a worker thread or process which processes items in the queue. There would always be only a single instance of the worker. The worker processes requests in a FIFO order.

This way you eliminate race conditions - if two users both sign up for the last slot at the same time, only one of them will get it, and you can send a notification to the other one that their request was denied. There are various ways you could do that, one would be to have users redirected to a page which says "request pending" which is updated once their request is processed by the worker thread.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.