Implementing DDD with spring

Abstract

  • Understand the domain: talk to the people.
  • Split big domain into subdomains.
  • Develop an ubiquitous language: close the gap between the domain experts and the code.
  • Develop a domain model.
  • Separate domain model from implementation details.

Domain model encapsulates:

  • domain knowledge,
  • domain rules,
  • processes,
  • constraints,
  • behaviors,
  • state changes.

Tactical design:

  • Entities
  • Value Objects
  • Domain Services
  • Factories
  • Aggregates
  • Repositories

There’s no one way to do it with DDD, but most often implement a cocentric architecture, so either onion architecture, hexagonal architecture or clean architecture.

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.

Demo:

  • The speaker is using spring-modulith in his demo.
  • 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.:
public Loan(Copy copyId, UserId userId, LoanRepository loanRepository) {
  Assert.isTrue(loanRepository.isAvailable(copyId), "Not available");
}
  • 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:
@ApplicationModuleListener
public void handle(LoanCreated event) {
  Copy copy = copyRepository.findById(new Copy(event.copyId().id()));
  copy.makeUnavailable();
  copyRepository.save(copy);
}

Repository: GitHub - maciejwalkowiak/implementing-ddd-with-spring-talk Slides: Implementing Domain Driven Design with Spring - Speaker Deck