Qafoo GmbH - passion for software quality
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:Author: Benjamin Eberlei
:Date: Sat, 27 Feb 2016 11:01:20 +0100
:Revision: 7
:Copyright: All rights reserved
===========================================================
Teaching and Learning Domain-Driven Design without Patterns
===========================================================
:Description:
When development teams start to use Domain-Driven Design (DDD) in work
projects, then one or more developers read the blue book by Eric Evans and
start to apply patterns such as Entity, Repository, Services and Value
Objects. In my experience with teams using DDD it can be very distracting
to think in patterns. Read about a more practical way to get started…
:Keywords:
ddd, odd, object oriented, teaching
:Abstract:
When development teams start to use Domain-Driven Design (DDD) in work
projects, then one or more developers read the blue book by Eric Evans and
start to apply patterns such as Entity, Repository, Services and Value
Objects. In my experience with teams using DDD it can be very distracting
to think in patterns. Read about a more practical way to get started…
When development teams start to use Domain-Driven Design (DDD) in work
projects, by focussing on patterns such as Entity, Repository, Services and
Value Objects, then there is trouble ahead. In my experience with teams using
DDD the pattern-thinking can be very distracting and dangerous. An approach
without patterns can bring the essentials across much better. Especially, less
experienced developers have a hard time to follow DDD by pattern because the
"Why and How are we doing this" is not easy to teach.
For this post, I am assuming you are starting to use DDD as a developer team
without real backing of a domain expert – which seems to be the widespread
default. Don't forget to remind yourself that you are missing a critical part,
the domain expert to help you get the critical nuances of your code right.
In my experience, when you apply very strict DDD in this scenario, the domain
expert will change or specify the requirements at some point and leave you
helpless with the wrong model. This can be a huge setback and cause a lot of
friction and code changes. CRUD (Anemic) models are much more resilient here,
because you can easily adapt or work around all the already existing business
logic.
So lets say you are using a mostly CRUD-centric style to application
development and still want to benefit from some of the achievements of DDD,
that is - you would like business rules to be:
1. using the language of the domain.
2. enforced and translated into code as stricly as possible.
3. independent of technical details and frameworks.
The technical DDD patterns are just one means to those ends. They are not
an end on their own, just applying the patterns doesn't make any sense.
We can try to bring those properties to live by relentlessly applying the SOLID
principles or the `four rules of simple design`__.
__ http://martinfowler.com/bliki/BeckDesignRules.html
Take the following (extremely simplified) code example performing some money
calculations and then storing the result in a database table. It is written
from a CRUD perspective using the transaction script pattern to implement
the "discount" use-case::
1) {
throw new \InvalidArgumentException("Percent must be float between 0 and 1");
}
$orderTotal = $this->selectOrderTotal($orderId);
$orderTotal = (1 - $percent) * $orderTotal;
$sql = 'UPDATE order SET total = ? WHERE id = ?';
$this->connection->executeQuery($sql, [$orderTotal, $orderId]);
}
}
Please ignore the consistency problems here – order items and taxes are simply
ignored in this simple example.
The business rules in this snippet cannot live up to the DDD practices:
- Business rules are not strictly enforced for Percent and Money values, they
are implemented only when they are needed, operating on primitive tpyes like
floats and integers.
- No independence of technical details, the business rules about discount
handling, percent and money calculations are mixed with SQL statements.
Without DDD patterns, we start with applying SOLID principles. The single
responsibility principle teaches us, that no class should have more than one
responsibility, but here ``OrderService`` knows how to validate percentage
values and how to multiply money with percentages.
So lets refactor and introduce a class Percent and a class Money::
1) {
throw new \InvalidArgumentException("Percent must be float between 0 and 1");
}
$this->value = $value;
}
public function asFloat()
{
return $this->value;
}
}
class Money
{
private $value;
public function __construct($value)
{
$this->value = $value;
}
public function reduceBy(Percent $percent)
{
return new Money($this->value * (1 - $percent->asFloat()));
}
public function asFloat()
{
return $this->value;
}
}
class OrderService
{
public function discount($orderId, Percent $percent)
{
$orderTotal = $this->selectOrderTotal($orderId);
$orderTotal = $orderTotal->reduceBy($percent);
$sql = 'UPDATE order SET total = ? WHERE id = ?';
$this->connection->executeQuery($sql, [$orderTotal->asFloat(), $orderId]);
}
}
Much better! We have separated the responsibilities. Please ignore the fact
Percent and Money should probably not use floats internally, there is much
more to improve in the code example, which is outside of this blog post.
But we are not done with using SOLID as an improvement method to refactor towards
DDD principled code. The dependency inversion principle states that high level code
should never depend on low level code, but our high level business logic depends
on low level database code here.
Let's introduce an interface that abstracts the data access::
totalGateway->fetch($orderId);
$orderTotal = $orderTotal->reduceBy($percent);
$this->totalGateway->update($orderId, $orderTotal);
}
}
Now we can implement this interface outside the ``MyShop\Domain`` namespace,
where only high level (business logic) is located, not database code.
::
namespace MyShop\Implementations\Doctrine;
use MyShop\Domain\Money;
use MyShop\Domain\OrderTotalGateway;
class DbalOrderTotalGateway implements OrderTotalGateway
{
public function fetch($orderId) { // ... }
public function update($orderId, Money $total)
{
$sql = 'UPDATE order SET total = ? WHERE id = ?';
$this->connection->executeQuery($sql, [$total->asFloat(), $orderId]);
}
}
Now only high-level code is located in the ``OrderService`` and details such as
the money being stored as a float in the database has been pushed down into a
low level implementation.
Without ever knowing about DDD patterns we have introduced Value Objects
(``Percent`` and ``Money``) and a class loosely resembling Repository
(``OrderTotalGateway``). The business logic is independent from technical details
and enforced in a much stricter way then before using encapsulation.
The similar result can be achieved by applying the four rules of simple design,
in this case "No duplication" and "Reveals intent" can help achieving the same
result without knowing about the SOLID principles.
Another approach to achieve yet the same result is to view this problem from an
anti-pattern or code-smell perspective: The original OrderService class
exhibits a severe case of feature-envy, drawing too much logic onto itself. One
reason for this is the primitive obsession using floats for Money and Percent.
Getting rid of this problem also leads to introduction of the ``Money`` and
``Percent`` classes.
.. note::
Start with a `refactoring workshop`__ by Qafoo to get your team started.
__ /services/workshops/refactoring.html
Don't make the mistake to implement DDD by thinking by the blue books building
blocks and technical patterns too much. Using a refactoring based approach combined
with SOLID principles or the Four Rules of Simple Design lets you move towards
DDD in much smaller and sustainable way. If you want to implement technical DDD
practices in your team, start with SOLID and Refactoring instead. This is much easier
to teach and learn. Gradually showing team members how the code improves and
how simple these small steps goes a long way to convince the team to start
using more challenging DDD patterns.
..
Local Variables:
mode: rst
fill-column: 79
End:
vim: et syn=rst tw=79
Trackbacks
==========
Comments
========
- Daniel Lima at Fri, 26 Feb 2016 20:30:18 +0100
typo here "am without real backing of a domain expErt –"
- Kore at Sat, 27 Feb 2016 11:01:41 +0100
Fixed, thanks.
- Mike at Thu, 03 Mar 2016 15:46:46 +0100
Hello,
Typo here "primitive tpyes". Or should I say tpyo ? ;)