Domain Driven Design
A lot of what we do as software engineers is about increasing profits, or helping to cut costs. To that end, achieving these goals requires modelling real world domains in computers.
Domain driven design teaches us that in order to build manageable systems we should aim to keep our systems decoupled, we not try to build the system in one go, but rather iteratively and we should try to evolve a domain model by working closely with domain experts.
We should not attempt to document every aspect or decision of a system, but use UML diagrams sparingly to explain hard to explain concepts that are core to the application, otherwise maintaining the documentation becomes more of a chore than maintaining the system, resulting in it eventually deteriorating and becoming out of sync/wrong.
In DDD we must be sure that the language used by the development team and domain experts is consistent, ever evolving and encoded in the domain model.
We must model the domain accurately. To do this we follow various design principles.
In order to keep designs simple we must attempt as much as possible to keep the relationships between the objects as simple as possible. Usually we want associations to be unidirectional. We may have bidirectional relations but if we can include some other property that helps the relationship become unidirectional, we will. This helps us to simplify the model and decouples the objects from one another. Example: Perhaps we have managers at a company. A company may have many managers and a manager may work for many companies. To simplify this relationship we can include a date so that the relationship between manager and company becomes one directional rather than bidirectional.
Some objects are defined not only by their attributes, but by a thread of continuity and identity. They have lifecycles that can change their form and content but that thread of continuity must be maintained. Objects primarily defined by their identity are entities.
Continuity – the lifetime of the object.
Identity – each object treated as a unique instance with an identity. e.g. personnel at a company. An example of something that does not have identity would be currency. Any £/$ 1 can be swapped out for another coin/note of the same denomination and there’s no noticeable difference.
When we are developing entities we want to keep that object focussed on lifecycle and identity rather than attributes. We must be aware of requirements that call for matching objects by attributes. We should only define operations that produce a unique result for each object.
Sometimes the identity of an object identity uniqueness must exist beyond the lifetime of the system – e.g. a mail tracking Id. Often these IDs are associated with other details – eg. a customers telephone number/national insurance number.
These are objects that describe things. They are not just devoid of identity. Colours are an example of value objects. Value objects are interchangeable for other equal value objects. Coins in the current system of operation all have the same monetary worth. We don’t care which instance of a value object we have. This design gives us the ability to simplify and optimise performance.
The fewer and simpler the associations in the domain model, the better.
Remember, bidirectional associations between entities may be hard, but between value objects makes no sense at all. If at any point such an association seems necessary, rethink the decision to declare the object a value object in the first place. Maybe it has an identity that hans’t been explicitly recognised yet.
Entities and value object are the main elements of conventional object models, but pragmatic designers have come to use one other element, services…
Sometimes operations do not belong to any objects. Rather than force the issue we can follow natural contours of the problem space and include services explicitly in the model.
Sometimes services exist but as objects with other names like ‘Manager’ and end up with no state of their own nor any meaning in the domain beyond the operation they host. Still, at least this solution gives these distinct behaviours a home without messing up a real model object.
Typically when we need a job done for us, we refer to modules that do work for us as services.
Services will be offered as an interface without encapsulating state, as entities and value objects do.
Services are named for an activity rather than as an entity/noun. Services can still have. Parameters and results should come from the domain objects.
Services shouldn’t be used to strip entities of all behaviour. We only want to use them when they make sense as part of the domain concept. They should genuinely represent a concept rather than being a stand in for a more legitimate object.
A good service has three characteristics:
- the operation relates to a domain concept that is not a natural part of an entity or value object.
- the interface is defined in terms of other elements of the domain model.
- the operation is stateless.
Statelessness here means that any client can use any instance of a particular service without regard to the instance’s individual history. The service will execute operations using information that is accessible globally and may even change that global information (i.e. the operation may not be “pure”… it may have side effects on other state not included as part of the inputs). But the service does not hold its own state which may affect behaviour, as most domain objects do.