0

Imagine you were working on time-tracking software. Two different managers are adding time worked for an employee on different columns on a time-sheet. The two managers are each adding 8 hours to two different days. But the time-sheet is already at 32 hours and should not go over 40 hours (that’s our new business rule). Right now, both cases will fetch from the database that the time-sheet has 32 hours left. By the time the top operation finished actually adding the additional 8 hours worked, the other operation has already fetched the state of the time-sheet…32 hours worked. What will happen is that both will succeed. And, we are left with 48 hours on a time-sheet!
I can solve this problem by moving Fryday working hours and Monday working hours into a single aggregate with method addHour(int hours, Enum Day) which will check the total hours or I can make Fryday, Monday, and Employee a separate aggregate. When the Monday manager decides to add hours the employee will receive an event addHours which will check the total hours and send back an event HoursAdded event if the total hours do not exceed 40 or HoursNotAdded event. Then the Monday aggregate will handle the event and add hours to his total hours.

manfrom
  • 91
  • 7

1 Answers1

0

What you describe is a persistence issue that arises when concurrent updates are made to a record.

To solve it you must detect concurrent updates of the time sheet. You usually do this with a version that is updated when the time sheet's persistence representation is updated. If the version changed since the time sheet was read it means that someone else updated it. In this case the repository can raise an exception.

This technique using a version is called optimistic locking.

There are several ways to deal with such an optimistic lock exception. You can either show the user a dialog and reload the data or you can retry the use case and execute it again. If you execute the use case again it will reload the time sheet from the database and thus see the updates made by someone else. Now a domain exception will be raised since the use case can not update the hours, because the hours to add will exceed the week limt (your business rule).

Since the version number is only for persistence reasons it sould not be modeled in the domain entity. To keep track of the version number a repository can use an identity map so that it can map the domain entity to it's initial persistence state. This identity map is scoped to a use case invocation so that you do not affect other use cases. There are a lot of different ways to implement this scope. E.g. you could use a thread local or you make the repository stateful which usually makes the use case stateful since it has a dependency to the repository. I can't go into depth here.

When you implement optimistic locking you should ensure that the version check and the record update is an atomic operation. Otherwise you haven't really solved it. How to implement optimistic locking depends on the database you use (SQL, NoSQL, etc.). You usually pass the version and the id as selection criteria in your update request.

René Link
  • 48,224
  • 13
  • 108
  • 140