In “traditional” architectures with technical layers, we most often have services classes that perform the business logic, whereas the entities are only plain java objects. They are called anemic objects.
It’s fine for small projects, but once it’s becoming big, it’s hard to maintain because the responsibilities of the services are not clear.
He put the Repository in the package domain. Interesting.
One way to keep the domain entities pure (i.e. no JPA annotations) will be to:
create a BookEntity containing those JPA annotations in the infrastructure package
have an implementation of the repository and map the domain entity to infrastructure entity and vice versa
However, that’s not the approach the speaker is leaning for. Instead:
Add the JPA annotations in the domain entity, e.g.
@EmbeddedId instead of @Id for the id class
@Embedded for the value objects
@Embedded@AttributeOverride(name = "value", column = @Column(name = "isbn"))private Isbn isbn;
However, we have to add a default constructor with no parameter (we can put it in package private), because hibernate needs it. It’s the violation that we need to accept if we want to adopt the JPA annotations.
The speaker suggests using the concept of use cases in clean architecture, i.e. use specific classes to perform business logic instead of a global service object (e.g. RegisterBook instead of BookService).
Add a Repository when creating a new domain entity where we can perform some checks to validate the state of the model entity, e.g.:
Using spring-modulith, we can add a test to check if there’s any violation between the packages (i.e. no hard coupling between two packages).
If there’s a call between one package to another, the speaker suggests extends the domain entity with AbstractAggregateRoot<> (a spring class) where we can register events that will be triggered on the entity update.
However, this will introduce eventual consistency as it’s not the same transaction.
The other package (here catalog) still has some coupling on the original package (here lending) because the former has references to the domain entities LoanClosed and LoanCreated… Not sure how spring-modulith handles this “violation”.
This event is not published immediately after the call to registerEvent(new LoanClosed(copyId)), but after the repository.save(loan).
Then, in the other side, in the application layer, we will have a DomainEventListener that will listen to those events: