In a system
architecture using Domain Driven Design (DDD) you typically find a few typical
building blocks (stereotypes) - Entities, Value Objects, Repositories and
Domain Services – where the first two are stateful objects and the rest are
stateless services implemented either as infrastructure services outside the
domain (Repositories) or inside the domain containing only business logic
(Domain Services). A common question is “When is it appropriate to design a
service instead of placing the business logic in an entity or value object?”
For
developers more used to building procedural designs, rather than object-oriented
ones, it seems to be more natural to place logic in stateless services and have
them operate on objects that are no more than data containers. This is what is
commonly referred to as an “anemic domain model”. It is considered an
anti-pattern in the DDD community since it decouples data from behavior and
thereby produces a much less expressive and knowledge tense domain model.
I'm not a
fan of stateless services in the domain model. Instead I try to favor bringing
business logic into the Entity or Value Object that holds the information
needed - in OO-terms it is called the "information expert". In most
cases it isn’t that hard, especially if functionality is decomposed into short
methods, each allocated to the object representing the concept at heart of the
functionality.
A specific
type of functionality that might be trickier to handle is entity creation. Who
is to be responsible? For sure, it can’t be the entity itself. In general my
experience is that there will be some sort of hierarchy between concepts. E.g.
a SalaryPeriod might be connected to an existing RegistrationPeriod, which also
contains submitted Timesheets. Then it is a good fit to have the
RegistrationPeriod handle creation of the SalaryPeriod. So in general it is almost
always possible to find a good place for rules regarding creation of an entity
in a parent concept. The same goes for functionality that has to operate over
several entities of a given type.
However,
there might be cases where no suitable parent concept exists in the domain.
That is one of the cases where I find designing a domain service appropriate.
And there are others. Here is a small extract from a previous blog post of mine
where I briefly describe another situation where I think domain services are a
good choice:
"In general I think you could talk about
two types of systems, or parts of systems; those mostly concerned with changes
in object state and those mostly concerned with processing data streaming
through the system. In the
first case entities, aggregates and repositories are a natural fit, in the
second I think transaction scripts (in DDD context called domain services,
since they do only concern domain logic, no infrastructure code [..]) are a
nice fit. When the most important feature is to crunch some data, perhaps
modify it and then route it further to some recipient (like another system or
some persistent store) I think it is the "processing pipeline", i.e.
the stateless service code, which should be emphasized. So in those cases the
internals of the data isn't very interesting and might be better left in some
simple DTO format."
To make the
list of appropriate service design complete I’ll end with adding a few lines on
external services. These services get injected into the domain. In the domain I
would have only an interface describing the service in terms of the domain. This
is the way to integrate with surrounding infrastructure or other systems. It is
the same pattern as with Repositories, in fact a Repository is just a specialized
service.
Another example
of an external service is when some part of the business logic, e.g. a
calculation is broken out into a separate service, implemented using another
programming language or paradigm. From a domain point-if-view it is as if we
get that calculation service from another system instead of doing it ourselves.
The reason might be performance or it might be that the logic already exists
and we want to continue using it instead of re-implementing. However, each time
this happens the maintenance burden is increased a bit, so I think it should be
done with careful consideration.
To sum up,
favor business logic implementations in the domain objects, not in services. It
leads to a more elaborate and therefore more useful domain model which is
easier to keep consistent than logic spread over disparate services. I’m
convinced in the long run this approach makes maintenance of the system easier.
No comments:
Post a Comment