Domain-Driven-Design With JPA: A Practical Guide
A practical guide to DDD with JPA in a modular monolithic architecture by leveraging ORM annotations and addressing some pitfalls.
Join the DZone community and get the full member experience.
Join For FreeSome Codebase Context
The Core Principles
The very main principle is to apply Single Responsibility Principle (SRP) for writing data: only one sub-domain must be in charge of writing into a column of the database. Other sub-domains will either not consider the column or only read it.
The second principle is to allow read overlap: several domains may access the same column in read-only mode.
As you see, we don't need to apply replication here. Even if this is still possible, the objective is to manage a normalized-form database.
Best Practices Before Starting
-
the major one is that it usually helps your ORM to handle the persistence because you can afford to set everything as eagerly loaded (avoiding N+1 problems), and it also helps to have some better SQL queries that use joins.
-
it clarifies the code and lets you align your domain name with the root of the aggregate.
-
learn the different forms of inheritance mapping (usual ones, polymorphic ones) and their impact on the database design
-
master its annotations, for example, when joining tables (you may tweak the default joined columns) or composing your entity with
@Embeddableor@SecondaryTable -
choose the appropriate identifier policy (be aware that some JPA policies don't have the same result on the database depending on the support of auto-increment, sequence, and so on), especially for read-only entities; the already-assigned one is suitable.
Some Tips
@Entity
public class Equipment {
@Column(updatable=false, insertable=false)
private String name;
@Column(updatable=false, insertable=false)
private String vendor;
}
@Entity
public class Stock {
@OneToMany // no cascade => no-operation is applyied to the collection
private Set<Equipment> equipments;
}
-
@Embeddableis an easy one that, for our case, can push aside some columns with some specific concepts or make the main entity lighter, whereas the original concept is much more complete in another sub-domain and, overall, is fully a part of an entity. -
@SecondaryTablecan replace@Inheritance(JOINED)in some read circumstances depending on the design or complexity you have to deal with. -
@Inheritance(SINGLE_TABLE)permits separating different concepts from the same table into a clear hierarchy; however, you must be careful with such annotation because it requires filling the mandatory columns that might not be part of your entities.
Pitfalls
-
Because JPA forbids having the same entity name twice in the same persistence context, you will need to name them with
@Entity(name="...")to overcome the name conflict. You can simply prefix the entity name with your sub-domain name to avoid "duplicates." -
If you prefer having several
@PersistenceContext(one per sub-domain), you'll have to name them and inject the right one in your services. Also, you must make it scan the right sub-packages; otherwise, it will iterate over the whole classpath and generate a humongous persistence context containing all the entities of your sub-domains. -
Finally, here comes the tests and read-only properties. If you segregate your domains and no one sees each other, you will struggle to insert data easily, simply because your JPA context is not allowed to fill the read-only columns. Then you have 2 choices here: either do it with some low-level access (JDBC, script, etc.) or... create yet-another-entity-model dedicated to database filling for the tests.
Conclusion
Opinions expressed by DZone contributors are their own.
Comments