Domain Driven Design
Domain Driven Design (DDD) is an approach of how to model the core logic of an application. The term itself was coined by Eric Evans in his book "Domain Driven Design". The basic idea is that the design of your software should directly reflect the Domain and the Domain-Logic of the (business-) problem you want to solve with your application. That helps understanding the problem as well as the implementation and increases maintainability of the software.
The Domain Driven Design approach introduces common principles and patterns that should be used when modeling your Domain. There are the "building blocks" that should be used to build your domain model and principles that helps to have a nice "supple design" in your implementation.
This article tries to introduce some of the concepts shortly and should inspire to read more. But reading this article still requires some minutes - so take your time :-)
The Developer never knows enough about the problem. But the domain experts know their domain and the developer has to understand it. The domain experts know rules of their business process but often they are not aware using them because it is natural for them. Therefore "knowledge crunching" is required to identify a proper domain model (DM).
A common language between users (domain experts) and developers is required that helps to understand the tdomain and the problem.
The domain model offers a simplified, abstract view of the problem. It can have several illustrations: speech / UML / code. Defining the domain model is a cyclic process of refining the domain model. The DM grows and changes because knowledge grows during implementation and analysis!
Of course the domain model must be useable for implementation. In practice there are so many analytic Models which are not implemented ever => you have to find one which can be implemented in design!
In order to understand the problem area (domain) of your application a common language should be used to describe the problem. The goal is that this language (and the common vocabulary in it) can be understood by the developers and the domain experts (e.g. the client).
- The DM is the backbone and it uses terms of the "ubiquitous language"
- Therefore no translation between developers and domain experts - as well between developers itself are required.
- During the analysis the ubiquitous language should be used to describe and discuss problems and requirements. If there are new requirements it means new words enter the common language...
Speak in ubiquitous language (like a foreign language)
- Try to explain scenarios loud with the use of the model and the ubiquitous language
- Try to explain scenarios more simple/better (find easier ways to say what you need to say). That helps refining the model.
UML and DDD
Common is the use of class-diagrams to describe the domain model.
- Use of UML class diagrams to sketch the main classes.
- Do not go to draw every class you going to code => keep overview of the main stuff!
- Use structure (packages and subpackages). Use freetext to explain constrains and relations.
- Simplify the class diagram when you use it to talk with domain experts (better a diagramm is useful than standard compliant)
“comprehensive UML of entire object model fails to communicate or explain; they overwhelm reader with details”
Therefore use simplified diagrams of conceptual important parts – that are essential to understand. (Present a skeleton of ideas)
The basic principle for software that is build with domain driven design is to use a layered architecture. Where the heart of the software is the domain model. Basic principles of layered architecture is that a layer never knows something about the layers above. That also mean the domain layer -as the heart of the application- is responsible for all the business logic but should never be "dirtied" by view requirements for example.
(MVC or MVP Pattern fits perfectly into that principle)
Main Domain Model elements (building blocks)
The main elements of a domain model are "entities", "values" and "services". They are connected with associations (relations). Common pattern like "repositories" and "factories" helps completing the model.
Associations between elements
Avoid many association and use only a minimum of relations because they decrease maintainability!
- impose a traversal direction instead of a bidirectional (bidirectional means both objects can only exist together -> ask if this is really the case)
- adding qualifier, effectively reduces multiplicity: If you have much associations and/or multiplicity for both ends of the association, it can be a sign that a class is missing in your model.
- eliminate nonessential associations
In sourcecode a association can be for example: a function that querys / some internal array / a collection object / just a reference ...
= Object which is primary defined by its identity (not by attributes). For example "Person", "Car", "Costumer" or "BankTransaction".
- has continuity through live cycle
- object defined by its own not by attributes
- keep the class definition simple => focus on live cycle
- be alert to requirements that require a matching through attributes (because of possible problems with the need for identity)
- use of some Identifier (IDs) is often useful
=describe the characteristic of a thing and identity is not required. “we care what they are not who or which”. For example "address", "color" ...
- value objects are allowed to reference an entity
- often passed as parameter in messages between other objects
- they are immutable! Ideally the only have getter methods and their attributes are set during construction (constructor method).
- make them a “whole value” - means a conceptual whole (e.g. address)
- identity gives freedom in design – we don’t need to care of identity: we can simple delete and create new ones if required.
A good example is "color" that can be implemented as value object. That means it is an object that represents a certain color. Since value objects are immutable it is not allowed to change the color object. So if you need to get a color that is lighter than another color: You throw the old color away and build a new color - that is possible because you don't need to take care about identity. (Typically implemented as method in a colorService or colorFactory class for example)
“...if a single process or transformation in domain is not a natural responsibility of an entity or value => make it a standalone service with a nice interface."
A service decouple entities and values from client (client in this case= the object thatwant to use another object) and therefore. They define an easy interface to use in client objects.
A service should
- be stateless
- be defined in the common language
- use entity/value objects as parameters
A service should not be mixed with other layers:
- be mixed with responsibility of infrastructure or system layer
- be mixed with the application layer. For example export or import is not part of the domain layer (an export or import fileformat has no meaning in the domainlayer – they belong to application)
“If your model tells a story a modul is a “chapter””
- helps understanding of large systems
- low coupling between modules
- independent development possible
- if you group objects in a modul you want others to think of this objects together => group by meaning in the domain layer
It is difficult to guarantee the consistency of changes in a model with many associations. Aggregates helps to limit dependencies.
An aggregate is a group of objects that belong together (a group of individual objects that represents a unit). Each aggregate has a aggregate root. The client-objects only "talk" to the aggregate root.
- Invariants and rules have to be maintained. The aggregate root normaly takes care of invariants.
- cluster entities/values into aggregates and define boundaries around each.
- One entity is the aggregate root and controls all changes and access to the objects inside
- Delete have to delete complete boundary
“a car do not build itself” - a car is an useful and powerful object because of its associations and behavior, but to overload it with logic for creating it is unlikely!
A factory hides logic for building objects - this is especially relevant for aggregates!
A factory can be implemented as a seperate object or also as a embedded factory method.
Instead of a seperate factory object - a factory method can be located in existing elements: for example in an aggregate root or in a host class where it is natural (e.g. createOrder method in a BookingClass).
Factory functionality should only be located in the constructor if creation is simple and if there are good reasons. A constructor should always be atomic (never call other constructors).
A good factory:
- is atomic (you need to pass all parameters that are need to build a valid "thing")
- are predictable
- not allowed to give wrong results (instead throw exception)
- the factory will be coupled to its arguments, therefore carefully choose the parameters:
- there is the danger of too much dependencies - better use parameters that are allready in the conceptual group or in dependencies allready
- use abstract types as parameter
- the factory is responsible for invariants… (maybe delegate it)
- there are also factories for reconstitution
Factories for entities takes just essential attributes that are required to make a valid entity or aggregate. Details can be added later if they are not required by an invariant. The factory knows from where to get the identifier for an entity.
Since value objects are immutable a factory or factory method for a value object takes the full description as parameters.
Get references of entities and objects:
To do anything with an object you need to have a reference to it:
So how to get it? There are several options:
- build a new object - e.g. with the help of a factory
- reconstitution of an object (relevant when the id is known - done by the factory)
- traversal between objects, means to ask other objects for objects. For example that have to be used for inner aggregates of course.
But there are some entities that need to be accessible through search based on attributes. One way to deal with this is the "query and build" techniquebut this tends on a too technical focus and views objects as datacontainer (that is not optimal for DDD). That is where repositories enter the scene...
For each object where you need global access create a repository object that can provide the illusion of an in memory collection of all objects of that type. Setup access through a well knows global interface.
A Repository typically has methods for add, remove and find (=select based on some criteria). Advantages:
- Decouple client from technical storage
- Performance tuning possible
- Communicate design decisions related to data access
- keep model focus
- dummy implementation for unit testing is possible.
- Repositories can hide the OR mapping.
If the repository is responsible for retrieving a certain entity that is build by a factory, the repository normally calls the "reconstitution" method of the factory.
This diagram (taken from the book) is an overview showing the parts of domain driven design.
Beside the building blocks Eric Evans also pointed out principles that should help to have a supple design in the implementation. One of the main goal of a supple design is that you have code where the developers love to work with - because it is understandable, logic, maintainable and extendable. This are some of the the principles:
- classes should have an "intention revalving interface": names of classes and methods should explain clearly what they do.
- methods shouldn't have side-effects: As much as possible should be query (no change to the state of the model). Commands should be clear and simple.
- If there are unavoidable sideeffects they should be made explicit through assertions. (e.g. state post-conditions and invariants)
- conceptual contours: "Decompose design elements (operations, interfaces, classes, and aggregates) into cohesive units, taking into consideration your intuition of the important divisions in the domain. Observe the axes of change and stability through successive refactorings and look for the underlying conceptual contours that explain these shearing patterns. Align the model with the consistent aspects of the domain that make it a viable area of knowledge in the first place."
- where logic use simple "standalone classes" with a certain functionality, that is easier to understand and better to maintain (e.g. because you have less dependencies)
- closure of operations, simple means a method should answer with the same object/datatype which it takes as parameters. You should use this where possible because no new dependencies are introduced when using this method.
DDD supports the agile paradigm and ideally goes hand and hand with refactoring, test driven development and an agile project management like scrum.
More to read
or download an extract: domaindrivendesign.org/books/PatternSummariesUnderCreativeCommons.doc
Domain Driven Design in practice
- There is a sample application to show how the implementation with domain driven design can work. It is the example application for cargo (used also in the book from Eric Evans): http://dddsample.sourceforge.net/
- There is also a good article abould DD in practice with a demo application for download: www.infoq.com/articles/ddd-in-practice
- The homepage of Domain Driven Design is www.domaindrivendesign.org - it is maintained by the leaders in this area like Eric Evans and Jimmy Nilsson.
- Recently a interesting interview was published there: domaindrivendesign.org/events/qcon2008SF/eric_evans/index.html
- New book from Martin Fowler: martinfowler.com/dslwip/