Domain-Driven Design: part 4
One year ago, we already made a quick presentation of
Domain-Driven Design, in the context of our mORMot
After one year of real-world application of those patterns, it is now time to give more light to DDD.
Let's continue with part 4, which will define Domain-Driven Design as could be implemented with our Synopse mORMot framework
Before going a bit deeper into the low-level stuff, here are some key sentences we should better often refer to:
- I shall collaborate with domain experts;
- I shall focus on the ubiquitous language;
- I shall not care about technical stuff or framework, but about modeling the Domain;
- I shall make the implicit explicit;
- I shall use end-user scenarios to get real and concrete;
- I shall not be afraid of defining one model per context;
- I shall focus on my Core Domain;
- I shall let my Domain code uncoupled to any external influence;
- I shall separate values and time in state;
- I shall reduce statefulness to the only necessary;
- I shall always adapt my model as soon as possible, once it appears inadequate.
As a consequence, you will find in mORMot no magic powder to build your DDD, but all the tools you need to focus on your business, without loosing time in re-inventing the wheel, or fixing technical details.
Defining objects in Delphi
How to implement all those DDD concepts in an object-oriented language like
Let's go back to the basics. Objects are defined by a state, a behavior and an identity. A factory helps creating objects with the same state and behavior.
In Delphi and most Object-Oriented languages (OOP - including C# or Java)
class instance (always inheriting from TObject):
- State is defined by all its property / member values;
- Behavior are defined by all its methods;
- Identity is defined by reference, i.e.
a=bis true only if
brefers to the same object;
- Factory is in fact the
classtype definition itself, which will force each instance to have the same members and methods.
In Delphi, the
record type (and deprecated
type for older versions of the compiler) has an alternative behavior:
- State is also defined by all its property / member values;
- Behavior are also defined by all its methods;
- But identity is defined by content, i.e.
RecordEquals(a,b)is true only if
bhave the same exact property values;
- Factory is in fact the
objecttype definition itself, which will force each instance to have the same members and methods.
We propose to use either one of the two kinds of object types, depending on the behavior expected by DDD patterns.
Defining DDD objects in mORMot
DDD's Value Objects are probably meant to be defined as
record, with methods (i.e. in this case as
older versions of Delphi). You may also use
TSQLRecord classes, ensuring the
do not have setters but just
read F... definition, to make them
read-only, and, at the same time, directly serializable.
If you use
object types, you may need to
customize the JSON
serialization when targeting AJAX clients (by default,
are serialized as binary + Base64 encoding, but you can define easily the
record serialization e.g. from text). Note that since
object defines in Delphi by-value types (whereas
class defines by-reference types - see previous
paragraph), they are probably the cleanest way of defining Value
DDD's Entity objects could be either regular Delphi classes, or
- Using PODOs (Plain Old Delphi Object - see so-called POJO or POCO
for Java or C#) has some advantages. Since your domain has to be uncoupled from
the rest of your code, using plain
classhelps keeping your code clean and maintainable.
- Inheriting from
TSQLRecordwill give it access to a whole set of methods supplied by mORMot. It will implement the "Layer Supertype" pattern, as explained by Martin Fowler.
DDD's Aggregates may either benefit of using mORMot's 3, or you can use a repository service.
- In the first case, your aggregate roots will be defined as
TSQLRecord, and you will benefit of all CRUD methods made available by the framework;
- Otherwise, you should define a dedicated persistence service, then use
plain DTO (like Delphi
record) or even publish the
TSQLRecordtypes, and benefit of their automated serialization.
In all cases, when defining domain objects, we should always make the
implicit explicit, i.e. defining one type (either
class) per reality in the
Thanks to Delphi's strong typing, it will ensure that the Domain Ubiquitous language will appear in the code.
DDD's DTO may also be defined as
record, and directly
serialized as JSON via text-based serialization. Don't be afraid of writing
some translation layers between
TSQLRecord and DTO records or,
more generally, between your Application layer and your
Presentation layer. It will be very fast, on the server side. If your
service interfaces are cleaner, do not hesitate. But if it tends to enforce you
writing a lot of wrapping code, forget about it, and expose your Value
Objects or even your Entities, as stated above. Or automate the
wrapper coding, using RTTI and code generators. You have to weight the PROs and
the CONs, like always...
DDD's Events should be defined also as
like regular DTOs. Note that in the close future, it is planned that
mORMot will allow such events to be defined as
in a KISS implementation.
BATCH support is a convenient implementation of the Unit of Work
pattern (i.e. regrouping all update / delete / insert operations in a single
stream, with global
Note that the current implementation of
Batch* methods in
mORMot, which focuses on Client side, should be enhanced to be more
convenient and available on the server side, i.e. in the Application
In practice, mORMot's Client-Server architecture may be used as such:
Services via methods can be used to publish methods corresponding to
your aggregate roots defined as
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.
Both methods will allow proper customization, and, especially for the second, offer both integrated and automated process, e.g. RESTful access, JSON marshalling, session, security, logging, multi-threading.
Building a Clean architecture
A common DDD architecture is expressed as in the following model, which may look like a regular multi-Tier design at first, but should be implemented as a Clean Architecture.
|Presentation||MVC UI generation and reporting|
|Application||Services and high-level adapters|
|Domain Model||Where business logic remains|
|Data persistence||ORM and external services|
|Cross-Cutting||Horizontal aspects shared by other layers|
Physically, it involves a common n-Tier representation
splitting the classical Logic Tier into two layers, i.e.
Application layer and Domain Model layer. At logical
level, DDD will try to uncouple the Domain Model layer from other
layers, so the code itself will rely on
dependency injection to let the core Domain focus on the
business logic, not on implementation details (e.g. persistence or
This is what we called a Clean Architecture, defined as such:
The RESTful SOA components of our Synopse mORMot framework can therefore define such an Architecture:
As we already stated, the main point of this Clean Architecture is
to control coupling, and isolate the Domain core from the outer
layers. In Delphi, unit dependencies (as displayed e.g. by our
SynProject tool) will be a good testimony of proper objects
uncoupling: in the units defining your domain, you may split it between
Domain Model and Domain Services (the 2nd using the first,
and not vice-versa), and you should never have any dependency to a
particular DB unit, just to the framework's core units, i.e.
mORMot.pas. Inversion of Control - via
services or at ORM initialization level - will ensure that your code is
uncoupled from any low-level technical dependency. It will also allow proper
testing of your application workflows, e.g. stubbing the database if
In fact, since SOA tends to ensure that services comprise unassociated,
loosely coupled units of functionality that have no calls to each other
embedded in them, we may define two levels of services, implemented by two
interface factories, using their own hosting and
- One set of services at Application layer, to define the uncoupled contracts available from Client applications;
- One set of services at Domain Model layer, which will allow all involved domains to communicate with each other, without exposing it to the remote clients.
In order to provide the better scaling of the server side, cache can be easily implemented at every level, and hosting can be tuned in order to provide the best response time possible: one central server, several dedicated servers for application, domain and persistence layers...
Due to the SOLID design of mORMot you can use as many Client-Server services layers as needed in the same architecture (i.e. a Server can be a Client of other processes), in order to fit your project needs, and let it evolve from the simplest architecture to a full scalable Domain-Driven design.