In Java Spring, the EntityManager can be used to manage the persistence context and interact with the underlying database. The lock method of the EntityManager can be used to apply a lock to an entity to manage concurrency. The LockModeType parameter allows specifying the type of lock.
The LockModeType.OPTIMISTIC_FORCE_INCREMENT is used for optimistic locking with forced version increment. This lock mode is applied to ensure that the entity's version is incremented, thus preventing other transactions from updating the entity until the current transaction is complete.
This lock mode increments the version of the entity even if the entity has not been modified.
This setup ensures that the entity's version is managed optimistically, preventing conflicting updates in a concurrent environment.
In TMS Aurelius, the equivalent of EntityManager is ObjectManager. Is it possible to add a lock method to TObjectManager that will force the version field to increment even if other fields of the entity have not been changed in update sql ?
Example: Optimistic Locking in DDD with Order and Order Line Aggregate Root
Context
In a system handling orders and order lines, we have an aggregate root Order which contains multiple OrderLine entities.
Aggregate Root:Order
Entities:OrderLine
Attributes of Order:
id
status (e.g., pending, confirmed, shipped)
orderLines (list of OrderLine)
version
Attributes of OrderLine:
productId
quantity
price
Scenario
Consider the following scenario where multiple users are trying to update the same order concurrently:
Adding Items to an Order:
User A tries to add a new OrderLine to an order.
User B tries to update the quantity of an existing OrderLine in the same order.
Actions
User A's Actions:
User A retrieves the Order aggregate from the database. The version attribute of the Order is 1.
User A adds a new OrderLine to the orderLines list.
User A attempts to save the Order aggregate back to the database. The version attribute is incremented from 1 to 2.
We need to increment version of root aggragete in spite of any field of order hasn't changed
User B's Actions:
User B retrieves the same Order aggregate from the database. The version attribute of the Order is still 1 because User A hasn't committed the transaction yet.
User B updates the quantity of an existing OrderLine.
User B attempts to save the Order aggregate back to the database. The system detects that the current version in the database is 2, not 1.
Optimistic Locking
Version Attribute: Every update to the Order aggregate increments the version attribute.
Concurrent Update Detection:
When User A saves their changes, the version attribute is incremented to 2.
When User B attempts to save their changes, the system checks the version attribute.
Since the version in the database (2) does not match the version User B retrieved (1), an OptimisticLockException is thrown.
Outcome
User A's Changes: Successfully saved, and the version is updated to 2.
User B's Changes: The system detects the version mismatch and prevents User B from overwriting User A's changes. User B is notified of the conflict and can re-fetch the Order aggregate and reapply their changes.
The problem here is not some kind of lock mechanism, but the fact that in Aurelius, a master record is separated from a detail record. If you update a detail object, it doesn't check for concurrency in the parent object. In this case you have to control this at application level.
You are right but this small Lock method simplify the problem with using application logic.
Usually workflow looks like
procedure TOrderService.DoSomething(AOrderId: TOrderId);
var
LOrder: TOrder;
begin
LOrder:= FOrderRepository.Load(AOrderId); // Load Aggrgate root
LOrder.DoSomething; // Do application logic with the aggregate
FOrderRepository.Save(LOrder); // Save the Aggregate
ObjectManager.Flush; // save change to database
end;
In LOrder.DoSomething; is application logic -> can be anything
Changing field value of TOrder (this will force version change)
Changing OrderLine (this will not force version change - so is not safe)
I have generic repository
procedure TAurelisRepository.Save(Aggregate: T);
begin
if DBSession.ObjectManager.IsAttached(Aggregate) then
//locking Aggregate Root logically protects whole aggregate //entityManager.lock(aggregate, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
else
DBSession.ObjectManager.Save(Aggregate);
end;
With ObjectManagher.lock metohod I'm sure that Order object (Aggregate root/master record) is always protected.
To achive tthis result without lock method I had to add additional field to Order object to force version change for example
procedure TOrder.AddProduct(AName: String);
begin
FOrderLineList.Add(TProduct.Add(AName));
FOrderChanged:= True; // this will force version change
end;
I'm testing Aurelius and try to rewrite sample java spring application using Domain-Driven Design (DDD) to delphi.
I try to emulate java JPA EntityManager.Lock( OPTIMISTIC_FORCE_INCREMENT)