Domain Entities

Introduction

Most domain objects that the end-user interacts with are likely to be domain entities, such as Customer, Order, Product and so on. These are persistent objects and which are mapped to a relational database using either JPA/EclipseLink or JDO/DataNucleus ORM.

Some domain entities are really aggregates, a combination of multiple objects. A commonly cited example of this is an Order, which really consists of both a root Order entity and a collection of OrderItems. From the end-users' perspective, when they talk of "order" they almost always mean the aggregate rather than just the Order root entity.

Eric Evans' Domain Driven Design has a lot to say about aggregate roots and their responsibilities: in particular that it is the responsibility of the aggregate root to maintain the invariants of its component pieces, and that roots may only reference other roots. There’s good logic here: requiring only root-to-root relationships reduces the number of moving parts that the developer has to think about.

On the other hand, this constraint can substantially complicate matters when mapping domain layer to the persistence layer. DDD tends to de-emphasise such matters: it aims to be completely agnostic about the persistence layer, with the responsibilities for managing relationships moved (pretty much by definition) into the domain layer.

As a framework Apache Causeway is less dogmatic about such things. Generally the domain objects are mapped to a relational database and so we can lean on the referential integrity capabilities of the persistence layer to maintain referential invariants. Said another way: we don’t tend to require that only roots can maintain roots: we don’t see anything wrong in an InvoiceItem referencing an OrderItem, for example.

Nonetheless the concepts of "aggregate" and "aggregate root" are worth holding onto. You’ll probably find yourself defining a repository service (discussed in more detail below) for each aggregate root: for example Order will have a corresponding OrderRepository service. Similarly, you may also have a factory service, for example OrderFactory. However, you are less likely to have a repository service for the parts of an aggregate root: the role of retrieving OrderItems should fall to the Order root (typically by way of lazy loading of an "items" collection) rather than through an OrderItemRepository service. This isn’t a hard-n-fast rule, but it is a good rule of thumb.

@DomainObject

Domain entities are persistent domain objects, and will typically be annotated with @DomainObject(nature=ENTITY).

Their persistence is handled by either JPA/EclipseLink or JDO/DataNucleus ORM, with the ORM taking care of both lazy loading and also the persisting of modified ("dirty") objects.

As such, they will also require ORM metadata. Both ORMs allow the metadata to be specified using either annotations or XML. The following sections show the basics for both ORMs, but see their respective guides for more detail.

Entities (JPA)

If the JPA/EclipseLink object store is to be used, then the domain entities should be annotated using JPA annotations.

This section shows a simple example. See the JPA/Eclipselink object store documentation for further information on annotating domain entities.

Class definition

For the domain class itself, this will be the @javax.persistence.Entity annotation and probably the @javax.persistence.Table annotation, as well as others to define indices and queries.

@javax.persistence.Entity                                               (1)
@javax.persistence.Table(
    schema= "simple"                                                    (2)
    // ...
)
@EntityListeners(CausewayEntityListener.class)                          (3)
@Named("simple.SimpleObject")                                           (4)
@DomainObject                                                           (5)
public class SimpleObject {

    @javax.persistence.Id                                               (6)
    @javax.persistence.GeneratedValue(strategy = GenerationType.AUTO)   (3)
    @javax.persistence.Column(name = "id", nullable = false)            (7)
    private Long id;

    @javax.persistence.Version                                          (8)
    @javax.persistence.Column(name = "version", nullable = false)       (5)
    @PropertyLayout(fieldSetId = "metadata", sequence = "999")
    @Getter @Setter
    private long version;

    //...
}
1 The @Entity annotation indicates that this is an entity to EclipseLink.
2 Specifies the RDBMS database schema for this entity. It’s recommended that the schema corresponds to the module in which the entity resides. The table will default to the entity name if omitted.
3 Required boilerplate that allows Causeway to inject domain services into the entity when retrieved from the database
4 the @Named annotation defines a logical name for the concrete class; used in security and bookmarks.
5 The @DomainObject annotation identifies the domain object to Apache Causeway (not EclipseLink). It isn’t necessary to include this annotation — at least, not for entities — but it is nevertheless recommended.
6 Specified the primary key, indicating that the database will assign the key, for example using an identity column or a sequence.
7 Indicates the column name (though this would be inferred) and nullability (such a primary keys should not be nullable).
8 The @Version annotation is useful for optimistic locking; the strategy indicates what to store in the version column.

Scalar Properties

All domain entities will have some sort of mandatory key properties. Additional annotations are also required to define their scalar properties and relationships to other entities.

The example below is a very simple case, where the entity is identified by a name property. This is often used in database unique indices, and in the toString() implementation:

import javax.persistence.Column;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.ToString.Include;

@javax.persistence.Table(
    schema= SimpleModule.SCHEMA,
    uniqueConstraints = {
        @UniqueConstraint(name = "SimpleObject__name__UNQ",
                          columnNames = {"name"})                   (1)
    }
)
public class SimpleObject
             implements Comparable<SimpleObject> {                  (2)

    // ...
    public SimpleObject(String name) {
        setName(name);
    }

    @Column(allowsNull="false", length=50)                          (3)
    @Getter @Setter                                                 (4)
    @ToString.Include                                               (5)
    private String name;

    private final static Comparator<SimpleObject> comparator =
            Comparator.comparing(SimpleObject::getName);

    @Override
    public int compareTo(final SimpleObject other) {
        return comparator.compare(this, other);                     (6)
    }
}
1 EclipseLink will automatically add a unique index to the primary surrogate id (discussed above), but additional alternative keys can be defined using the @Unique annotation. In the example above, the "name" property is assumed to be unique.
2 Although not required, we strongly recommend that all entities are naturally Comparable. This then allows parent/child relationships to be defined using SortedSets; RDBMS after all are set-oriented.
3 Chances are that some of the properties of the entity will be mandatory, for example any properties that represent an alternate unique key to the entity. The @Column annotation specifies the length of the column in the RDBMS, and whether it is mandatory.

Given there is a unique index on name, we want this to be mandatory.

We can also represent this using a constructor that defines these mandatory properties. The ORM will create a no-arg constructor to allow domain entity to be rehydrated from the database at runtime (it then sets all state reflectively).

4 Use Lombok to generate the getters and setters for the name property itself.
5 Use Lombok to create a toString() implementation that includes the value of name property.
6 Use java.util.Comparator#comparing() to implement Comparable interface.

Queries

When using JPA, it’s also common for domain entities to have queries annotated on them. These are used by repository domain services to query for instances of the entity:

...
@javax.persistence.NamedQueries({
        @javax.persistence.NamedQuery(                                  (1)
                name = "findByNameLike",                                (2)
                query = "SELECT so " +                                  (3)
                        "FROM SimpleObject so " +
                        "WHERE so.name LIKE :name"
        )
})

@javax.jdo.annotations.Queries({
    @javax.jdo.annotations.Query(                                     (4)
      name = "findByName",                                            (5)
      value = "SELECT "                                               (6)
            + "FROM domainapp.modules.simple.dom.impl.SimpleObject "  (7)
            + "WHERE name.indexOf(:name) >= 0 ")                      (8)
})
...
public class SimpleObject { /* ... */ }
1 There may be several @NamedQuery annotations, nested within a @NamedQueries annotation) defines queries using JPAQL.
2 Defines the name of the query.
3 The definition of the query, using JPAQL syntax.

To actually use the above definition, the framework provides the RepositoryService. This is a generic repository for any domain class.

The corresponding repository method for the above query is:

public List<SimpleObject> findByName(String name) {
    return repositoryService.allMatches(            (1)
            Query.named(SimpleObject.class,         (2)
                        "findByNameLike")           (3)
                   .withParameter("name", name)     (4)
            );
}

@Inject RepositoryService repositoryService;
1 find all instances that match the query
2 Specifies the class that is annotated with @NamedQuery
3 Corresponds to the @NamedQuery#name attribute
4 Corresponds to the :name parameter in the query JDOQL string

Entities (JDO)

If the JDO/DataNucleus object store is to be used, then the domain entities should be annotated using JDO annotations.

This section shows a simple example. See the JDO/DataNucleus object store documentation for further information on annotating domain entities.

Class definition

For the domain class itself, this will be @javax.jdo.annotations.PersistenceCapable annotation and probably the @javax.jdo.annotations.DatastoreIdentity, as well as others to define indices and queries.

@javax.jdo.annotations.PersistenceCapable(                              (1)
        identityType=IdentityType.DATASTORE,                            (2)
        schema = "simple",                                              (3)
        table = "SimpleObject"
)
@javax.jdo.annotations.DatastoreIdentity(                               (4)
        strategy=javax.jdo.annotations.IdGeneratorStrategy.IDENTITY,
        column="id"
)
@javax.jdo.annotations.Version(                                         (5)
        strategy= VersionStrategy.DATE_TIME,
        column="version"
)
@Named("simple.SimpleObject")                                           (6)
@DomainObject                                                           (7)
public class SimpleObject { /* ... */ }
1 The @PersistenceCapable annotation indicates that this is an entity to DataNucleus. The DataNucleus enhancer acts on the bytecode of compiled entities, injecting lazy loading and dirty object tracking functionality. Enhanced entities end up also implementing the javax.jdo.spi.PersistenceCapable interface.
2 Indicates how identifiers for the entity are handled. Using DATASTORE means that a DataNucleus is responsible for assigning the value (rather than the application).
3 Specifies the RDBMS database schema and table name for this entity will reside. It’s recommended that the schema corresponds to the module in which the entity resides. The table will default to the entity name if omitted.
4 For entities that are using DATASTORE identity, indicates how the id will be assigned. A common strategy is to allow the database to assign the id, for example using an identity column or a sequence.
5 The @Version annotation is useful for optimistic locking; the strategy indicates what to store in the version column.
6 the @Named annotation defines a logical name for the concrete class; used in security and bookmarks.
7 The @DomainObject annotation identifies the domain object to Apache Causeway (not to DataNucleus). It isn’t necessary to include this annotation — at least, not for entities — but it is nevertheless recommended.

Scalar Properties

All domain entities will have some sort of mandatory key properties. Additional annotations are also required to define their scalar properties and relationships to other entities.

The example below is a very simple case, where the entity is identified by a name property. This is often used in database unique indices, and in the toString() implementation:

import javax.jdo.annotations.Column;
import javax.jdo.annotations.Unique;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.ToString.Include;
...
@Unique(
    name="SimpleObject_name_UNQ", members = {"name"})               (1)
@ToString(onlyExplicitlyIncluded = true)
public class SimpleObject
             implements Comparable<SimpleObject> {                  (2)

    public SimpleObject(String name) {
        setName(name);
    }

    @Column(allowsNull="false", length=50)                          (3)
    @Getter @Setter                                                 (4)
    @ToString.Include                                               (5)
    private String name;

    private final static Comparator<SimpleObject> comparator =
            Comparator.comparing(SimpleObject::getName);

    @Override
    public int compareTo(final SimpleObject other) {
        return comparator.compare(this, other);                     (6)
    }
}
1 DataNucleus will automatically add a unique index to the primary surrogate id (discussed above), but additional alternative keys can be defined using the @Unique annotation. In the example above, the "name" property is assumed to be unique.
2 Although not required, we strongly recommend that all entities are naturally Comparable. This then allows parent/child relationships to be defined using SortedSets; RDBMS after all are set-oriented.
3 Chances are that some of the properties of the entity will be mandatory, for example any properties that represent an alternate unique key to the entity. The @Column annotation specifies the length of the column in the RDBMS, and whether it is mandatory.

Given there is a unique index on name, we want this to be mandatory.

We can also represent this using a constructor that defines these mandatory properties. The ORM will create a no-arg constructor to allow domain entity to be rehydrated from the database at runtime (it then sets all state reflectively).

4 Use Lombok to generate the getters and setters for the name property itself.
5 Use Lombok to create a toString() implementation that includes the value of name property.
6 Use java.util.Comparator#comparing() to implement Comparable interface.

Queries

When using JDO, it’s also common for domain entities to have queries annotated on them. These are used by repository domain services to query for instances of the entity:

...
@javax.jdo.annotations.Queries({
    @javax.jdo.annotations.Query(                                     (1)
      name = "findByName",                                            (2)
      value = "SELECT "                                               (3)
            + "FROM domainapp.modules.simple.dom.impl.SimpleObject "  (4)
            + "WHERE name.indexOf(:name) >= 0 ")                      (5)
})
...
public class SimpleObject { /* ... */ }
1 There may be several @Query annotations, nested within a @Queries annotation) defines queries using JDOQL.
2 Defines the name of the query.
3 The definition of the query, using JDOQL syntax.
4 The fully-qualified class name.
Must correspond to the class on which the annotation is defined (the framework checks this automatically on bootstrapping).
5 In this particular query, is an implementation of a LIKE "name%" query.

JDO/DataNucleus provides several APIs for defining queries, including entirely programmatic and type-safe APIs; but JDOQL is very similar to SQL and so easily learnt.

To actually use the above definition, the framework provides the RepositoryService. This is a generic repository for any domain class.

The corresponding repository method for the above query is:

public List<SimpleObject> findByName(String name) {
    return repositoryService.allMatches(            (1)
            Query.named(SimpleObject.class,         (2)
                        "findByName")               (3)
                   .withParameter("name", name)     (4)
            );
}

@Inject RepositoryService repositoryService;
1 find all instances that match the query
2 Specifies the class that is annotated with @Query
3 Corresponds to the @Query#name attribute
4 Corresponds to the :name parameter in the query JDOQL string