Optymistic lockining like in JAVA entityManager.lock(entity, LockModeType.OPTIMISTIC_FORCE_INCREMENT)

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 ?

1 Like

Hi @DUCH_MARIUSZ, welcome to TMS Support Center.

Please check this documentation topic dedicated to such subject.

https://doc.tmssoftware.com/biz/aurelius/guide/objects.html#concurrency-control

I know that versioning is supported in TMS Aurelius.

I try to explain using example from documentation.

We have entity

[Entity, Automapping]
TCustomer = class
private
FId: Integer;
FName: String;
{...}
[Version]
FVersion: Integer;
{...}
end;

User1Customer := Manager1.Find(1);
User1Customer.City := 'New City';
User1Customer.Flush;

SQL is
Update Customer Set City = 'New City', Version = 2 Where Id = 1 and Version = 1

THAT'S OK

But I need this functionality

User1Customer := Manager1.Find(1);
Manager1.Lock(User1Customer) ;
User1Customer.Flush;

SQL should be
Update Customer Version = 2 Where Id = 1 and Version = 1

I can't simply do
User1Customer := Manager1.Find(1);
User1Customer.Flush;

because User1Customer hasn't changed so no SQL update will be performed

Why? It doesn't seem to prevent anything.

In any case, there is no such feature in TMS Aurelius.

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:

  1. 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

  1. 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

  1. 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.

OK.

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

  1. Changing field value of TOrder (this will force version change)
  2. 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)