Translated from: vaadin.com/learn/tutor…

In the previous article, we learned how to build value objects that persist in JPA. Now it’s time to move on to the objects that will actually contain your value objects: entities and aggregates.

JPA has its own @Entity concept, but it is much less restrictive than the Entity concept in DDD. This is both a strength and a weakness. The advantage is that implementing entities and aggregations using JPA is very easy. The disadvantage is that it is also easy to do things that DDD does not allow. This can be particularly problematic if you work with developers who have previously used JPA extensively but are not familiar with DDD.

Value objects have just implemented an empty tag interface, while entities and aggregate roots will require a broader base class. It is important to get the base classes right from the start, because they can be difficult to change later, especially if the domain model gets larger. To help us with this task, we will use Spring Data. Spring Data provides some base classes right out of the box that you can use as needed, so let’s start by looking at them.

Persistable, AbstractPersistable, and AbstractAggregateRoot are used

Spring Data provides an off-the-shelf interface Persistable. This interface has two methods, one to get the ID of the entity and the other to check whether the entity is new or persistent. If an entity implements this interface, Spring Data will use it to decide whether to call persist (new entity) or merge (persistent entity) when saving. However, you do not need to implement this interface. Spring Data can also use optimistic locking versions to determine if an entity is new: if not, it is. You need to be aware of this when deciding how to generate entity ids.

The spring data also provides an abstract base class that implements the Persistable interface: AbstractPersistable. It is a generic class that takes the type of ID as its single generic parameter. The ID field is annotated, @GeneratedValue, which means that JPA implementations such as Hibernate will try to generate ids automatically when the entity is first retained. This class treats entities with non-empty ids as persistent and entities with empty ids as new. Finally, it overrides equals and hashCode so that only classes and ids are considered when checking for equality. This is consistent with DDD – two entities are considered the same if they have the same ID.

If you can use common Java types (such as Long or UUID) as entity ids and have the JPA implementation generate them for you when the entities are first retained, this base class is an ideal starting point for your entity and aggregate root. But wait, there’s more.

Spring Data also provides an abstract base class called AbstractAggregateRoot. You guessed it, this is a class designed to be extended by the aggregate root. However, it does not extend AbstractPersistable, nor does it implement the Persistable interface. So why use this class? Well, it provides methods that allow your aggregation to register domain events and then publish those events after the entity is saved. This is really useful, and we’ll revisit this topic in a future article. Similarly, there are some advantages to not declaring the ID field in the base class and having the aggregate root declare its own ID. We will revisit this topic in a future article.

In practice, you want the aggregate root to become root Persistable, so you end up implementing methods in your base class, AbstractAggregteRoot, or AbstractPersistable, in your base class. Let’s see what we can do next.

Create your own base class

In almost every project I work on, either at work or in private, I start by creating my own base class. Most of my domain models are built on aggregate roots and value objects; I rarely use what are called local entities (entities that belong to collections but are not roots).

I usually start with a base class called BaseEntity, which looks like this:

@MappedSuperclass / / < 1 >
public abstract class BaseEntity<Id extends Serializable> extends AbstractPersistable<Id> { / / < 2 >

    @Version / / < 3 >
    private Long version;

    public @NotNull Optional<Long> getVersion(a) {
        return Optional.ofNullable(version);
    }

    protected void setVersion(@Nullable version) { / / < 4 >
        this.version = version; }}Copy the code
  1. Even if the class is named BaseEntity, it is not JPA@Entity but an @mappedSuperClass.
  2. The scope Serializable comes directly from AbstractPersistable.
  3. I use optimistic locking for all entities. We will return to this point later in this article.
  4. In rare cases, you want to manually set the open lock version. However, to be on the safe side, I offer a protected way to make this possible. I think most Java developers with a few years of experience have experienced situations where they actually do need to set a property or call a method in a superclass, only to find that it’s private.

After BaseEntity, I will continue to learn BaseAggregateRoot. This is essentially a copy of Spring Data, AbstractAggregateRoot, but it extends BaseEntity:

@MappedSuperclass / / < 1 >
public abstract class BaseAggregateRoot<Id extends Serializable> extends BaseEntity<Id> {

    private final @Transient List<Object> domainEvents = new ArrayList<>(); / / < 2 >

    protected void registerEvent(@NotNull Object event) { / / < 3 >
        domainEvents.add(Objects.requireNonNull(event));
    }

    @AfterDomainEventPublication / / < 4 >
    protected void clearDomainEvents(a) {
        this.domainEvents.clear();
    }

    @DomainEvents / / < 5 >
    protected Collection<Object> domainEvents(a) {
        returnCollections.unmodifiableList(domainEvents); }}Copy the code
  1. This base class is also @mappedSuperClass.
  2. This list will contain all domain events to publish when you save the aggregation. This is at sign Transient because we don’t want to store them in the database.
  3. If you want to publish a domain event from an aggregation, register it with this protected method. We will examine this in more detail later in this article.
  4. This is a Spring Data annotation. Spring Data calls this method after the domain event is published.
  5. This is also a Spring Data annotation. Spring Data will call this method to get the domain events to publish.

As I said, I rarely use local entities. However, when needed, I often create a BaseLocalEntity class that extends BaseEntity without providing any additional functionality (except perhaps a reference to the aggregation root that owns it). I’ll leave that as an exercise for the reader.

Optimistic locking

We have added a field, BaseEntity, for optimistic locking at @Version, but we haven’t discussed why. In DDD: Tactical Domain Driven design, the fourth principle of converged design is the use of optimistic locking. But why did we add the @Version field to BaseEntity instead of BaseAggregateRoot? After all, isn’t the aggregation root responsible for always maintaining the integrity of the aggregation?

The answer to that question is yes, but here again, the underlying persistence technology (JPA and its implementation) is creeping into our domain design. Suppose we use Hibernate for our JPA implementation.

Hibernate doesn’t know what an aggregation root is – it only deals with entities and embeddable objects. Hibernate also keeps track of which entities have actually changed and only flushes those changes to the database. In practice, this means that even if you explicitly ask Hibernate to save entities, no changes are actually written to the database, and the optimistic version number may remain the same.

This is not a problem as long as you only deal with the root and value objects of the collection. With Hibernate, a change to an embeddable object is always a change to the entity it owns, so the optimistic version of that entity (in this case, the total root) increases as expected. However, once local entities are added to the composition, things change. Such as:

@Entity
public class Invoice extends BaseAggregateRoot<InvoiceId> { / / < 1 >

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<InvoiceItem> items; / / < 2 >

    // The rest of the methods and fields are omitted
}
Copy the code
  1. Invoice is the aggregation root, so it extends the BaseAggregateRoot class.
  2. InvoiceItem is a local entity, so it can extend the BaseEntity class, or BaseLocalEntity can extend the class based on your base class hierarchy. The implementation of this class is not important, so we omit it, but note the cascade option in the @onetomany annotation.

Local entities are owned by their aggregation roots and therefore retained through cascading. However, if you change only the local entity without changing the aggregate root, saving the aggregate root will only result in flushing the local entity to the database. In the example above, if we only made changes to the invoice items and then saved the entire invoice, the invoice version number would remain the same. If another user makes changes to the same item before saving the invoice, we will silently overwrite the other user’s changes.

We can prevent this from happening by adding the open locked version field BaseEntity to. Both the aggregate root and local entities are optimistically locked, and there is no chance of accidentally overwriting someone else’s changes.