Software Structure with DDD

2022 06 01 head

Domain Driven Development DDD is the preferred software architecture approach for designing digital products. The method nicely matches with the operational concepts of microservices.

Domain-driven design is a holistic approach to understanding, designing, and building software applications.

Seminal books [1, 2, 3] describes the approach and key concepts defining the development method.

Concrete recipes and examples of code structure are still sparse.

How should you structure your namespaces and modules?

We propose a simple and adequate code structure for implementation using modern Java constructs. The approach builds on the strengths of the Java packages and modules.

The language visibility rules enforce cohesion and minimize coupling.

The described approach is ideal for small to medium software solutions. The source code size of a bounded domain is often less than 50'000 lines of code.

The heart of software is its ability to solve domain-related problems for its user.

— Eric Evans
Domain-Driven Design: Tackling Complexity in the Heart of Software

History

It originated in the 1980s and 1990s, with Richard P. Gabriel being an early pioneer who introduced domain models as mental models of the business guiding software and digital product development.

Eric Evans further developed DDD in the late 1990s and early 2000s. His book Domain-Driven Design: Tackling Complexity in the Heart of Software [1] defined DDD as creating software based on a deep understanding of the business domain. It outlined principles for creating domain models, including using object-relational mapping (ORM) tools.

Today, DDD is widely accepted and applied across various domains like financial services, healthcare, and e-commerce.

Layers in DDD

2022 06 01 layers

Domain-driven design DDD has four layers in the architecture:

Interface

This layer is in charge of the opportunity with the user, whether software presents information to the user or receives information from the user.
I use vaadin to increase productivity. Vaadin allows us to use the single technology stack Java to develop all backend and frontend functionalities.

Application

This is a thin layer between the interface and the domain, it could call domain services to serve the solution purposes.

Domain

At The heart of the software, this layer holds domain logic and business knowledge.
A major goal is to avoid any tainting of the core domain model from other layers. Try to avoid extending domain entities with implementation-specific root classes or to a lesser degree annotations.

Infrastructure

A supporting layer for the other layers. This layer contains supporting libraries or external services like a database or UI supporting library.

I use libraries to minimize effort in support functions such as persistence, JSON transformation, archiving, and reporting. The microstream library is a good example of this approach. The persistence capability is less than fifty lines of code. No changes in the domain model were necessary. We did not have to add a root persistent class or use annotations.

Package Structure

A bounded domain shall be configured as a Java module [4]. The exported services and entities are explicitly listed with the exports directive. A huge advantage is the obligation to list all dependencies with the requires directive.

2022 06 01 hexagonal architecture

The Java compiler validates the module dependencies and interfaces.

Services

defines the bounded domain context. The domain uses the library to implement a bounded domain. The ports, handlers, and realms are declared in the context package for small bounded domains. Complex domains can declare these interfaces in separate packages for legibility.

Logic

contains common business logic and features. Elaborate domains can contain more business features. Use a package structure increasing legibility and enforcing separation of concerns.

Ports

contains the services receiving data and events from the environment.

Handlers

contains the services providing data and events to the environment and to other applications.

Realm

contains the repository functions to persist and create domain entities and object values. The current approach uses the MicroStream library. The amount of code to persist the domain model is minimal and non-intrusive [1].

Domain

contains the abstraction describing the bounded domain. Care is taken to define resilient and legible domain abstractions.

bounded-domain-structure

The above diagram shows the package structure for a bounded domain named bounded-domain. The names with the regular font are package names, the ones in italics are class names.

The infrastructure supports classes are not part of this structure. These classes shall be defined in a separate Java module and imported with regular dependency declaration.

We use the ArchUnit tool to ensure the expected structure is provided for each bounded domain.

The user interface for a bounded domain is stored in a separate package and often in its own Java module. The bounded domain implementation shall not constrain the technologies used to provide a user interface. Different approaches for user interface realization are available and no clear winner can currently be identified.

I mainly use Vaadin to realize browser- or mobile-first internal applications.

Java Considerations

Entities shall have well-documented internal or external identifiers. Entities visible to other bounded domains shall always have an external identifier. Identifiers are always immutable objects.

Objects that have a distinct identity that runs through time and different representations. You also hear these called reference objects.

— Martin Fowler

Value objects shall be expressed as Java value types. Record construct is the preferred way to model a value object in Java. The record concept provides the expected equals(Object) behavior.

Objects that matter only as the combination of their attributes. Two value objects with the same values for all their attributes are considered equal.

— Martin Fowler

An aggregate is a set of Entities and Value Objects that do not make sense alone. Every aggregate has a root entity, which will be responsible for providing all methods involving business rules that will modify its child entities. The first rule is that aggregates reference each other by identity instead of object references. Aggregates shall be constructed with the help of factory patterns such as factory method, abstract factory, or builder.

A DDD aggregate is a cluster of domain objects that can be treated as a unit. An example may be an order and its line-items, these will be separate objects. Tt is useful to treat the order together with its line items as a single aggregate.

— Martin Fowler

Rich domains are models that have full control of their data and do not rely on external objects to manipulate them. Anemic domains are models that rely on other classes to validate their data. Anemic domains are a smell in the domain-driven design world.

Services should be pure functions and be stateless.

Pure functions are functions (or methods) that do not change the value of any object outside it. It avoids side effects and guarantees the same output for certain inputs, meaning it needs to be completely deterministic.

The Spring project has added support for bounded domain in their framework with the Modulith extension.

Gradle modules are a natural mapping for bounded domains. Use either Java modules or archUnit to enforce that communication always goes through bounded domain interfaces.

Architecture Integrity

A Bounded Context is a logical boundary of a domain where particular terms and rules apply consistently. Inside this boundary, all terms, definitions, and concepts form the Ubiquitous Language.

Good practices shall be applied to ensure the quality of the bounded domain software architecture [5, 6, 7]

The Java Platform Module System (JPMS) encourages us to build more reliable and strongly encapsulated modules. As a result, these features can help to isolate our contexts and establish clear boundaries.

A bounded domain is implemented as a Java module.

The domain internal layered architecture is verified with ArchUnit custom validation rules. The rules are coded as unit tests and are processed in the continuous integration pipeline.

The advantages of the architecture are:

  • The whole company talking the same ubiquitous language, reduced the risk of misunderstandings. Everyone needs to be aligned, both in vocabulary and ownership of the components. The engineers have common understanding and coding guidelines to realize the layers inside a bounded domain.

  • You have a segregated architecture defining a modular monolith application.

  • Smaller and well-defined components are easier to maintain. Your services are independent and can more easily be refactored.

  • Development scalability is implicitly provided. Teams can develop simultaneous and independently bounded domain features.

Bounded Domain Relations

There are five main types of relationships between Bounded Contexts:

Partnership

a relationship between two contexts that cooperates to align the two teams with dependent goals.

Shared Kernel

a kind of relationship when common parts of several contexts are extracted to another context/module to reduce code duplication.

Customer-supplier

a connection between two contexts, where one context (upstream) produces data, and the other (downstream) consumes it. In this relationship, both sides are interested in establishing the best possible communication.

Conformist

this relationship also has upstream and downstream. However, downstream always conforms to the upstream’s APIs.

Anti-corruption layer

this relationship kind is widely used for legacy systems to adapt them to a new architecture and gradually migrate from the legacy codebase. The protection layer acts as an adapter to translate data from the upstream and protect from undesired changes

Lessons Learnt

Great technologies, programming languages and tools are used when building software applications. That is good and right.

But unfortunately, it is often lost that the decisive factor for the success of a project is not technology, but the solution. In order to understand the subject matter or domain, we need a common language with the domain experts and users. If we do not map the technical model in the software and its architecture, it will not help our users in their work.

As a computer scientist, it is easy to fall into the trap of focusing on technology instead of specialist knowledge footnoote:[In the modern trend of technology driven curriculum this dreadful approach is often encountered]. The principle of bounded contexts from DDD can help us here.

Domain-driven design (DDD) is a useful approach that provides excellent guidelines for modeling and building systems, but it is a means to an end, not an end in itself.

While the concepts are valid, you lose a lot if you limit yourself to using them only: There actually is a life beyond DDD.

— Stefan Tilkov
2021

The DDD approach emphasizes systematic refactoring and code improvements [8, 9, 10, 5]. It recommends test driven approach for bounded domain development [11, 12, 13].

References

[1] E. Evans, Domain-driven design. Addison-Wesley, 2004 [Online]. Available: https://www.amazon.com/dp/0321125215

[2] V. Vernon, Domain-Driven Design Distilled. Addison-Wesley Professional, 2016 [Online]. Available: https://www.amazon.com/dp/B01JJSGE5S/

[3] V. Vernon, Implementing Domain driven Design. Addison-Wesley Professional, 2012 [Online]. Available: https://www.amazon.com/dp/B00BCLEBN8

[4] S. Mak, Java 9 Modularity. O’Reilly Media, 2017 [Online]. Available: https://www.amazon.com/dp/1491954167

[5] R. C. Martin, Clean Architecture. Pearson, 2017 [Online]. Available: https://www.amazon.com/dp/0134494164

[6] M. Fowler, Refactoring, First. Addision-Wesley, 1999 [Online]. Available: https://www.amazon.com/dp/B004PQQRK2

[7] A. Scott and P. J. Sadalage, Refactoring Databases. Addison-Wesley Professional, 2006 [Online]. Available: https://www.amazon.com/dp/B001QAP36E

[8] M. Fowler, Refactoring, Second. Addision-Wesley, 2018 [Online]. Available: https://www.amazon.com/dp/0134757599

[9] R. C. Martin, Clean Code. Prentice Hall, 2009 [Online]. Available: https://www.amazon.com/dp/0132350882

[10] R. C. Martin, The Clean Coder. Prentice Hall, 2011 [Online]. Available: https://www.amazon.com/dp/0137081073

[11] L. Crispin, Agile testing. Addison-Wesley, 2009 [Online]. Available: https://www.amazon.com/dp/0321534468

[12] J. G. Gregory and L. Crispin, More Agile Testing. Addison-Wesley Professional [Online]. Available: https://www.amazon.com/dp/0321967054

[13] G. Adzic, Bridging the Communication Gap. Neuri Limited, 2009 [Online]. Available: https://www.amazon.com/dp/B008YZ993W/


1. You do not need to modify your domain model. No inheritance from a special persistence class, no annotations are required.