For the definition of your objects or internal data structures (what good programmers care about), you are therefore encouraged to make a difference between several kind of objects.
Domain-level representation are, generally speaking, rich on behavior,
therefore also of several families/species of objects.
Let us list the most high-level definitions of DDD:
- Value Objects contain attributes (value, size) but no conceptual identity - e.g. money bills, or seats in a Rock concert, as they are interchangeable;
- Entity objects are defined by their attributes, but have a thread of continuity, signified by an identity - e.g. persons, or seats in most planes, as each one is unique and identified;
- Aggregates are collection of objects (Values and/or Entities) that are bound together by a root entity, otherwise known as an aggregate root. Typically, aggregates are persisted in the database, and guarantee the consistency of changes by isolating its members from external objects (i.e. you can link to an aggregate via its ID, but you can not directly access to its internal objects). See share-nothing architecture article which, in mORMot, sounds very much like an Aggregate-Oriented-Database.
In addition to these domain-level objects, some cross-cutting types may appear, especially at the application layer:
- Data Transfer Objects (DTO) are transmission objects, which purpose is to not send your domain across the wire (i.e. separate your layers, following the Anti-Corruption Layer pattern). It encourages you to create gatekeepers that work to prevent non-domain concepts from leaking into your model. They keep both domain and application layers clean.
- Commands and Events are some kind of DTO, since they communicate data about an event and they themselves encapsulate no behavior - in mORMot, we try to let the framework do all the plumbing, letting those types be implemented via interfaces, avoiding the need to define them by hand.
DDD then favors some patterns to use those objects efficiently:
- Ubiquitous Language is where everything begins.
In the real outside world, i.e. for the other 10th kind of people how do not know about binary, domain experts use company- or industry- standard terminology. As developers, we have to understand this vocabulary and not only use it when speaking with domain experts but also see the same terminology reflected in our code. If the terms "class code" or "rate sets" or "exposure" are frequently used in conversation, we shall find corresponding class names in the code. In DDD, it is critical that developers use the business language in code consciously and as a disciplined rule. - Factory pattern is used to create object instances.
Main benefit is that alternative implementations may be easily interchanged. Such abstraction helps testing - see mocking - but also introduces interface-based services. - Value objects are by definition immutable, so should be handled as
read-only.
They are incapable of change once they are created. Why is it important that they be immutable? With value objects, you're seeking side-effect-free functions, yet another concept borrowed by DDD. When you add $20 to $20, are you changing $20? No, you are creating a new money descriptor of $40. - Repository pattern is used to save and dispense each Aggregate
root.
It matches the "Layer Supertype" pattern, e.g. via our mORMotTSQLRecord
andTSQLRest
classes and their Client-Server ORM features, or via dedicated repository classes - saving data is indeed a concern orthogonal to the model itself. Some architects claim that persistence is infrastructure, not domain. You may benefit in defining your own repository interface, if the standard ORM / CRUD operations are not enough. - Aggregate roots and entities, with all their methods, often end up as
state machines, and the behavior matches accordingly.
In the domain, since aggregate roots are the only kind of entity to which your software may hold a reference, they tend to be the main access point of all process. - Domain services pattern is used to model primary operations.
Domain Services give you a tool for modeling processes that do not have an identity or life-cycle in your domain, that is, that are not linked to one aggregate root, perhaps none, or several. In this terminology, services are not tied to a particular person, place, or thing in my application, but tend to embody processes. They tend to be named after verbs or business activities that domain experts introduce into the so-called Ubiquitous Language. If you follow the interface segregation principle, your domain services should be exposed as dedicated client-oriented methods. Do not leak your domain! In DDD, you develop your application layer services directly from the needs of your client applications, letting the Domain layer focus on the business logic and persistence tasks.
In practice, mORMot's Client-Server architecture may be used as such:
- Value Objects are probably meant to be defined as
record
, with methods (i.e. in this case asobject
for older versions of Delphi).
But you would need to customize the JSON serialization, when targeting AJAX clients (by default,record
s are serialized as binary + Base64 encoding). You may also useTComponent
orTSQLRecord
classes, ensuring the published properties do not have setters but justread F...
definition, to make them read-only, and, at the same time, directly serializable. - Client-Server ORM
can be used to persist your aggregate roots directly, or you can use a
repository service.
In the first case, your aggregates will be defined asTSQLRecord
; otherwise, you should define a dedicated persistence service, then use plain DTO (like Delphirecord
) or even publish theTSQLRecord
types, and benefit of their automated serialization. -
Services via methods - can be used to publish methods corresponding to your
aggregate roots defined as
TSQLRecord
.
This will make it pretty RESTful compatible. - Services
via interfaces can be used to publish all your processes.
Dedicated factories can be used on both Client and Server side, to define your repositories and/or domain operations.
Don't be afraid of writing some translation layers between
TSQLRecord
and DTO records. It will be very fast, on the server
side. If your service interfaces are cleaner, don't hesitate. But if it tends
to enforce you writing a lot of wrapping code, forget about it. Or automate the
wrapper coding, using RTTI and code generators. You have to weight the PROs and
the CONs, like always...
Even legacy
code and existing projects will benefit for it.
Finding so-called
seams, along with isolating your core domain, can be extremely valuable
when using DDD techniques to refactor and tighten the highest value parts of
your code.
Feedback is welcome on our forum, as usual.