30

I'm wondering what would be the best way to implement optimistic locking (optimistic concurrency control) in a system where entity instances with a certain version can not be kept between requests. This is actually a pretty common scenario but almost all examples are based on applications that would hold the loaded entity between requests (in a http session).

How could optimistic locking be implemented with as little API pollution as possible?

Constraints

  • The system is developed based on Domain Driven Design principles.
  • Client/server system
  • Entity instances can not be kept between requests (for availability and scalability reasons).
  • Technical details should pollute the API of the domain as little as possible.

The stack is Spring with JPA (Hibernate), if this should be of any relevance.

Problem using @Version only

In many documents it looks like all you need to do would be to decorate a field with @Version and JPA/Hibernate would automatically check versions. But that only works if the loaded objects with their then current version are kept in memory until the update changes the same instance.

What would happen when using @Version in a stateless application:

  1. Client A loads item with id = 1 and gets Item(id = 1, version = 1, name = "a")
  2. Client B loads item with id = 1 and gets Item(id = 1, version = 1, name = "a")
  3. Client A modifies the item and sends it back to the server: Item(id = 1, version = 1, name = "b")
  4. The server loads the item with the EntityManager which returns Item(id = 1, version = 1, name = "a"), it changes the name and persist Item(id = 1, version = 1, name = "b"). Hibernate increments the version to 2.
  5. Client B modifies the item and sends it back to the server: Item(id = 1, version = 1, name = "c").
  6. The server loads the item with the EntityManager which returns Item(id = 1, version = 2, name = "b"), it changes the name and persist Item(id = 1, version = 2, name = "c"). Hibernate increments the version to 3. Seemingly no conflict!

As you can see in step 6, the problem is that the EntityManager reloads the then current version (version = 2) of the Item immediately before the update. The information that Client B started editing with version = 1 is lost and the conflict can not be detected by Hibernate. The update request performed by Client B would have to persist Item(id = 1, version = 1, name = "b") instead (and not version = 2).

The automatic version check provided by JPA/Hibernate would only work if the instances loaded on the the initial GET request would be kept alive in some kind of client session on the server, and would be updated later by the respective client. But in a stateless server the version coming from the client must be taken into consideration somehow.

Possible solutions

Explicit version check

An explicit version check could be performed in a method of an application service:

@Transactional
fun changeName(dto: ItemDto) {
    val item = itemRepository.findById(dto.id)
    if (dto.version > item.version) {
        throw OptimisticLockException()
    }
    item.changeName(dto.name)
}

Pros

  • The domain class (Item) doesn't need a way to manipulate the version from the outside.
  • Version checking is not part of the domain (except the version property itself)

Cons

  • easy to forget
  • Version field must be public
  • automatic version checking by the framework (at the latest possible point in time) is not used

Forgetting the check could be prevented through an additional wrapper (ConcurrencyGuard in my example below). The repository would not directly return the item, but a container that would enforce the check.

@Transactional
fun changeName(dto: ItemDto) {
    val guardedItem: ConcurrencyGuard<Item> = itemRepository.findById(dto.id)
    val item = guardedItem.checkVersionAndReturnEntity(dto.version)
    item.changeName(dto.name)
}

A downside would be that the check is unnecessary in some cases (read-only access). But there could be another method returnEntityForReadOnlyAccess. Another downside would be that the ConcurrencyGuard class would bring a technical aspect to the domain concept of a repository.

Loading by ID and version

Entities could be loaded by ID and version, so that the conflict would show at load time.

@Transactional
fun changeName(dto: ItemDto) {
    val item = itemRepository.findByIdAndVersion(dto.id, dto.version)
    item.changeName(dto.name)
}

If findByIdAndVersion would find an instance with the given ID but with a different version, an OptimisticLockException would be thrown.

Pros

  • impossible to forget handle the version
  • version doesn't pollute all methods of the domain object (though repositories are domain objects, too)

Cons

  • Pollution of the repository API
  • findById without version would be needed anyway for initial loading (when editing starts) and this method could be easily used accidentally

Updating with explicit version

@Transactional
fun changeName(dto: itemDto) {
    val item = itemRepository.findById(dto.id)
    item.changeName(dto.name)
    itemRepository.update(item, dto.version)
}

Pros

  • not every mutating method of the entity must be polluted with a version parameter

Cons

  • Repository API is polluted with the technical parameter version
  • Explicit update methods would contradict the "unit of work" pattern

Update version property explicitly on mutation

The version parameter could be passed to mutating methods which could internally update the version field.

@Entity
class Item(var name: String) {
    @Version
    private version: Int

    fun changeName(name: String, version: Int) {
        this.version = version
        this.name = name
    }
}

Pros

  • impossible to forget

Cons

  • technical details leaks in all mutating domain methods
  • easy to forget
  • It is not allowed to change the version attribute of managed entities directly.

A variant of this pattern would be to set the version directly on the loaded object.

@Transactional
fun changeName(dto: ItemDto) {
    val item = itemRepository.findById(dto.id)
    it.version = dto.version
    item.changeName(dto.name)
}

But that would expose the version directly expose for reading and writing and it would increase the possibility for errors, since this call could be easily forgotten. However, not every method would be polluted with a version parameter.

Create a new Object with the same ID

A new object with the same ID as the object to be update could created in the application. This object would get the version property in the constructor. The newly created object would then be merged into the persistence context.

@Transactional
fun update(dto: ItemDto) {
    val item = Item(dto.id, dto.version, dto.name) // and other properties ...
    repository.save(item)
}

Pros

  • consistent for all kinds of modifications
  • impossible to forget version attribute
  • immutable objects are easy to create
  • no need to load the existing object first in many cases

Cons

  • ID and version as technical attributes are part of the interface of domain classes
  • Creating new objects would prevent the usage of mutation methods with a meaning in the domain. Maybe there is a changeName method that should perform a certain action only on changes but not on the initial setting of the name. Such a method wouldn't be called in this scenario. Maybe this downside could be mitigated with specific factory methods.
  • Conflicts with the "unit of work" pattern.

Question

How would you solve it and why? Is there a better idea?

Related

Jason Aller
  • 3,391
  • 28
  • 37
  • 36
deamon
  • 78,414
  • 98
  • 279
  • 415
  • But what is wrong with keeping version on client side? It is the same as keeping entity ID. – Antoniossss Aug 26 '19 at 19:13
  • This question is not about whether to keep the version on the client or not. All these approaches require exactly that. – deamon Aug 26 '19 at 19:15
  • Right, so if you use @Version which is part of JPA, no additional actions will be required from your side - in case of operation using entities ofc. – Antoniossss Aug 26 '19 at 19:16
  • The version send from the client must be re-applied to the server-side object somehow, if the object is not kept in the EntityManager between requests. JPA/Hibernate are doing no magic here! If I would rely on the automatic version control, I the entities would almost always have the most recent version just loaded from the database. To make optimistic locking work, the version current at the read time of a client is relevant. – deamon Aug 26 '19 at 19:20
  • 1
    No, this is not how it works. It does not "reapply " anything. What it does is adding additional constrain tou your queries so they look like eg UPDAT .... WHERE id=X and VERSION=y. Nothing needst to be kept inbetween. It comes with a cost yes, but its failry small one. – Antoniossss Aug 26 '19 at 19:22
  • Yes, I know. That is how it works under the hood. But to get the value of `version` it needs to be propagated from the client to hibernate somehow. It only works "magically" if the object loaded at the start of the "think time" is stored on the server (in a http session). If I reload the entity on the update request, the version sent from the client must be taken into consideration somehow. – deamon Aug 26 '19 at 19:27
  • Second part makes no sense to me. We are (mostly) programmers and "think time" is more confusing than explainatory i think (at least to me) And what do you mean by http session? Its request based solution, nothing needs to be p[ersisted inbetween them. – Antoniossss Aug 26 '19 at 19:40
  • "think time" is the time the user of the applications needs to perform the task between the initial fetch of the object and the update request. The version current at fetch time could only be kept on the server, if exactly this instace is kept until the update requests comes in and modifies this very object. If (as I desire) the object with the then current ID is loaded again from the database the version sent by the client would not be taken into consideration. – deamon Aug 26 '19 at 19:45
  • *The version current at fetch time could only be kept on the server,* no, on the client side. *if exactly this instace is kept until the update* No, it is not kept at any time, it is fetched when is used - that is not the "think time" - so once for initial fetch, second on update request to do `merge`. Last sentence - it should just throw OptimisticLockingException to inform client that some changes to the base entity were changed. So why @Version does not suit your needs ? – Antoniossss Aug 26 '19 at 20:00
  • 1
    I think that your assumpiton that you have to use `version` in every read query is wrong. You read only by ID. Version is used for write operations. No polution in API , no concurrent modification allowed. Remember that it is not versioning system. It is more like artificial composite PK in context of write operations. IMHO this is all you need and should fit yor requirement. There is no nee to use such things as `findByIdAndVersion` just `findById` – Antoniossss Aug 26 '19 at 20:04
  • Yes, I want to keep the version on the client, but the version coming from the client must be propagated to the database. If I reload the object on the update request, the version of this object would be the version current at reloading the object NOT the version the client started with. But the version the client started with is the version relevant to detect the conflict. – deamon Aug 26 '19 at 20:05
  • And what is the problem here? You send your entity with `version`, backend updates it - increments version number out of the box, returns whole enetity or whatever you want to return from update info, frontend applies new data. What is the problem? You dont set version on clinet side. It is done on backend only. – Antoniossss Aug 26 '19 at 20:06
  • 3
    If 2 users are working on the same enity and have its "Think time" both will have the same entity with the same version. If both will try to update it using the same version number, the one that will do it first (literally) will update the entity in DB. Other one will have OptimisticLockException as it have now outdatev entity version and is out of luck - must redo his work on new entity with new version. – Antoniossss Aug 26 '19 at 20:29
  • I've added the section "Problem using @Version only" to explain the problem better. – deamon Aug 27 '19 at 06:46
  • *But that only works if the loaded objects with their then current version are kept in memory until the update changes the same instance.* Is that in your *think time*? – Antoniossss Aug 27 '19 at 06:56
  • 3
    Your point 6 shows that versioning is not working at all. In STEP 6 OptimisticLockException should have been throws. Double check your conifiguration. In short - that update should not happen using versioning. Your expectations are correct, but for some reasons it does not work in your case (making you think it is by design). Your expectation aligns EXACTLY with how versioning with @Version works. – Antoniossss Aug 27 '19 at 06:58
  • 1
    Are you using `EntityManager#merge` for that ? If you update by hand (as you do in your example snippets) than no wonder it is not working for you. Instead of fetchig beforehand, just do `EntityManager#merge(dto)`. I think it is XY question about versioning not working due to missuse. – Antoniossss Aug 27 '19 at 07:06
  • I think it is XY question about versioning not working due to missuse. – Antoniossss Aug 27 '19 at 07:10
  • 1
    I wouldn't call it "misuse". But I don't want to simply persist the DTO directly, but I want do modify domain objects and then update these. Let's imagine a business rule that the new name must always be longer than the old name - such a constraint could only be checked with loaded objects. And that is what DDD is about. – deamon Aug 27 '19 at 07:30
  • *Let's imagine a business rule that the new name must always be longer than the old name - such a constraint could only be checked with loaded objects* What is the problem? For validation you fetch entity from DB, you do the bussiness validation, if it is correct you MERGE. OLE will be throw. If you are manually modyfing entity you will have to manually check for version mismatch (AND EXPLICITLY LOCK THAT ENTITY IN RDBMS!!!!) or detach instance and remerge. Sure it is valid use case, but auto version check feature was not ment to be used for it - thus your problem. – Antoniossss Aug 27 '19 at 09:03
  • 1
    Is there are typo in step 6 ? By that time , `EntityManager` should return `Item(id = 1, version = 2, name = "b")` which is updated by step (4). BTW , I think this is a pretty good question , why close it ? P.S. I already voted for reopen it. – Ken Chan Aug 29 '19 at 03:18
  • No, that's correct. Client A updated the item, so that version `2` is current if Client B loads the item for updating it. Thanks for your vote to reopen the question! – deamon Aug 29 '19 at 10:12
  • 1
    @deamon , I know it is version 2 , but isn't the name is updated to "b" in step (4) . Why it still return the name = "a" ? – Ken Chan Aug 29 '19 at 10:47
  • Good catch! The names were actually wrong. Fixed now. – deamon Aug 29 '19 at 11:14
  • @deamon i'll deleted my post. Of course you're right, this is pessimistic locking. This scenario was to cover multiple requests creating same objects within a nosql. – Manuel Polacek Aug 30 '19 at 13:13

4 Answers4

9

The server loads the item with the EntityManager which returns Item(id = 1, version = 1, name = "a"), it changes the name and persist Item(id = 1, version = 1, name = "b"). Hibernate increments the version to 2.

That's a misuse of the JPA API, and the root cause of your bug.

If you use entityManager.merge(itemFromClient) instead, the optimistic locking version would be checked automatically, and "updates from the past" rejected.

One caveat is that entityManager.merge will merge the entire state of the entity. If you only want to update certain fields, things are a bit messy with plain JPA. Specifically, because you may not assign the version property, you must check the version yourself. However, that code is easy to reuse:

<E extends BaseEntity> E find(E clientEntity) {
    E entity = entityManager.find(clientEntity.getClass(), clientEntity.getId());
    if (entity.getVersion() != clientEntity.getVersion()) {
        throw new ObjectOptimisticLockingFailureException(...);
    }
    return entity;
}

and then you can simply do:

public Item updateItem(Item itemFromClient) {
    Item item = find(itemFromClient);
    item.setName(itemFromClient.getName());
    return item;
}

depending on the nature of the unmodifiable fields, you may also be able to do:

public Item updateItem(Item itemFromClient) {
    Item item = entityManager.merge(itemFromClient);
    item.setLastUpdated(now());
}

As for doing this in a DDD way, the version checking is an implementation detail of the persistence technology, and should therefore occur in the repository implementation.

To pass the version through the various layers of the app, I find it convenient to make the version part of the domain entity or value object. That way, other layers do not have to explicitly interact with the version field.

meriton
  • 61,876
  • 13
  • 96
  • 163
  • Loading and modifying entities is the essence of the [unit of work](https://martinfowler.com/eaaCatalog/unitOfWork.html) pattern. But maybe it is not applied correctly here. Do I understand correctly that `itemFromClient` would contain the `version` from the client? If so: would you instantiate a new `Item` object from the DTO and passing the ID and Version to the `Item` constructor? – deamon Aug 30 '19 at 13:45
  • Yes, the domain class would contain the version. To answer your question about conversion to and from DTOs, I'd need to know what you use these DTOs for. (Since DDD does not mention DTOs, this is not self evident ...). Personally I try to avoid unnecessary conversion and prefer to use domain classes in all layers of the app ... – meriton Aug 30 '19 at 14:06
  • ... but of course you many good reasons to use them. It's just that I can imagine different purposes for DTOs, with correspondingly different solutions. – meriton Aug 30 '19 at 14:19
  • A DTO is a typesafe representation of a Json message. It could represent a complete object or only an update. In the first case a new entity could be constructed from the DTO, although the domain class would expose the technical `version` in the constructor. But validations regarding the previous state would be impossible. In the second case I would load the entity and pass parameters from DTO to a mutating method. This would allow to check based on previous state. Example: "new name must always be longer than the old name" could only be enforced with previous state. – deamon Aug 30 '19 at 14:54
  • The key question is whether your DTOs are part of the domain, i.e. whether they are DDD value objects. If so, you could pass them to domain classes or even repositories, freeing the application service from having to interact with individual properties. Some people prefer to even use the command pattern in these cases, declaring an `ItemUpdateCommand` with privileged access to internals of the Item class. But anyway, we are getting way off topic here, and these comment boxes are way to small to discuss architectural styles. Pass the version in whichever way fits your architecture :-) – meriton Aug 30 '19 at 16:38
  • *That's a misuse of the JPA API, and the root cause of your bug.* When I said that - he opposed thta this is NOT missuse ..... – Antoniossss Sep 01 '19 at 08:17
3

When you load the record from DB to process the update request , you have to configure that loaded instance to have the same version supplied by the client. But unfortunately when an entity is managed , its version cannot be changed manually as required by the JPA spec.

I try to trace the Hibernate source codes and do not notice there are any Hibernate specific feature that can by-passed this limitation. Thankfully, the version checking logic is straightforward such that we can check it by ourself. The entity returned is still managed which means unit of work pattern can still be applied to it :


// the version in the input parameter is the version supplied from the client
public Item findById(Integer itemId, Integer version){
    Item item = entityManager.find(Item.class, itemId);

    if(!item.getVersoin().equals(version)){
      throws  new OptimisticLockException();
    }
    return item;
}

For the concern about API will be polluted by the version parameter, I would model entityId and version as a domain concept which is represented by a value object called EntityIdentifier :

public class EntityIdentifier {
    private Integer id;
    private Integer version;
}

Then have a BaseRepository to load an entity by EntityIdentifier. If the version in EntityIdentifier is NULL, it will be treated as the latest version. All repositories of other entities will extend it in order to reuse this method :

public abstract class BaseRepository<T extends Entity> {

    private EntityManager entityManager;

    public T findById(EntityIdentifier identifier){

         T t = entityManager.find(getEntityClass(), identifier.getId());    

        if(identifier.getVersion() != null && !t.getVersion().equals(identifier.getVersion())){
            throws new OptimisticLockException();
        }
        return t;
 } 

Note: This method does not mean loading the state of the entity at an exact version since we are not doing event sourcing here and will not store the entity state at every version. The state of the loaded entity will always be the latest version , the version in EntityIdentifier is just for handling the optimistic locking.

To make it more generic and easily to use , I will also define an EntityBackable interface such that the BaseRepository can load the backed entity of anything (e.g. DTO) once they implement it.

public interface EntityBackable{
    public EntityIdentifier getBackedEntityIdentifier();
}

And add the following method to BaseRepository :

 public T findById(EntityBackable eb){
     return findById(eb.getBackedEntityIdentifier());
 }

So at the end, ItemDto and updateItem() application service looks like:

public class ItemDto implements EntityBackable {

    private Integer id;
    private Integer version;

    @Override
    public EntityIdentifier getBackedEntityIdentifier(){
         return new EntityIdentifier(id ,version);
    }
}
@Transactional
public void changeName(ItemDto dto){
    Item item = itemRepository.findById(dto);
    item.changeName(dto.getName());
}

To summarise , this solution can :

  • Unit of work pattern still valid
  • Repository API will not populated with version parameter
  • All technical details about controlling the version are encapsulated inside BaseRepository , so no technical details are leaks into the domain.

Note:

  • setVersion() is still need to be exposed from the domain entity.But I am okay with it as the entity get from the repository is managed which means there are no effect on the entity even developers calls setVersion(). If you really don't want developers to call setVersion(). You can simply to add an ArchUnit test to verify that it can only be called from the BaseRepository.
Ken Chan
  • 64,456
  • 22
  • 117
  • 138
  • The detach and merge trick may have unintended consequences if the entity has previously been loaded in this persistence context. Specifically, `entityManager.find` would then return the previous instance, and `detach` remove that instance from the persistence context. If the code the originally loaded it keeps using the original reference after that, this may cause surprising behavior. For instance, later updates to this object would not get written to the database, and JPA would throw if another entity keeps referring to the previous instance. – meriton Sep 01 '19 at 10:44
  • yes . you are right. It may has such surprise consequences. I agree with you that it is more easier to check the version by ourself unless Hibernate has specific feature to allow this use-case (manually change version of the managed entities) in the future. – Ken Chan Sep 01 '19 at 15:50
2

All explanations and suggestions made here were very helpful, but since the final solutions differs a bit, I think it is worth sharing it.

Manipulating the version directly didn't work properly and conflicts with the JPA spec, so it was no option.

The final solution is the explicit version check + automatic version checking by JPA Hibernate. The explicit version check is performed at the application layer:

@Transactional
fun changeName(dto: ItemDto) {
    val item = itemRepository.findById(dto.id)
    rejectConcurrentModification(dto, item)
    item.changeName(dto.name)
}

To reduce repetition, the actual check happens in a separate method:

fun rejectConcurrentModification(dto: Versioned, entity: Versioned) {
    if (dto.version != entity.version) {
        throw ConcurrentModificationException(
            "Client providing version ${dto.version} tried to change " + 
            "entity with version ${entity.version}.")
    }
}

Entities and DTOs both implement the Versioned interface:

interface Versioned {
    val version: Int
}

@Entity
class Item : Versioned {
    @Version
    override val version: Int = 0
}

data class ItemDto(override val version: Int) : Versioned

But pulling version from both and pass it to rejectConcurrentModification would work equally well:

rejectConcurrentModification(dto.version, item.verion)

The obvious downside of the explicit check at the application layer is that it could be forgotten. But since the repository must provide a way to load an entity without a version anyway, adding the version to the find method of the repository wouldn't be safe either.

The upside of the explicit version check at the application layer is that it doesn't pollute the domain layer except the version needs to be readable from the outside (by implementing the Versioned interface). Entity or repository methods, which are both part of the domain, are not polluted with version parameters.

That the explicit version check is not performed at the latest possible point in time doesn't matter. If between this check and the final update on the database another user would modify the same entity, then the automatic version check by Hibernate would become effective, since the version loaded at the beginning of the update request is still in memory (on the stack of the changeName method in my example). So, the first explicit check would prevent a concurrent modification happend between the begin of the edit on the client and the explicit version check. And the automatic version check would prevent a concurrent modification between the explicit check and the final update on the database.

deamon
  • 78,414
  • 98
  • 279
  • 415
0

To prevent concurrent modifications, we have to keep track of which version of item is being modified, somewhere.

If the application was stateful, we would have an option to keep this info on serverside, possibly in session, though this may not be best choice.

In stateless application, this info will have to go all the way to client and come back with every mutating request.

So, IMO, if preventing concurrent modifications is a functional requirement, then having item version info in mutating API calls does not pollute the API, it makes it complete.

ckedar
  • 1,669
  • 2
  • 7
  • In consider a `version` parameter "API pollution" because it brings a technical aspect into the domain. It is correct that the conflict needs to be handled through some rules defined by the business, but the way the conflict is detected is a technical aspect. – deamon Sep 04 '19 at 11:41