The slides of my talk at PUGRoma.
Here, a complete sample code
https://github.com/leopro/trip-planner
Presentation is also here: http://t.co/5EK56yYBmQ
15. The Dependency Rule
“This rule says that code dependencies can
only point inwards. Nothing in an inner circle
can know anything at all about something in an
outer circle.”
(http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html)
16. The Dependency Rule
“This rule says that code dependencies can
only point inwards. Nothing in an inner circle
can know anything at all about something in an
outer circle.”
(http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html)
19. What is Domain Driven Design?
“it is a way of thinking and a set of priorities,
aimed at accelerating software projects that
have to deal with complicated domains”
(Eric Evans, "Domain Driven Design")
20. What is Domain Driven Design?
“it is a way of thinking and a set of priorities,
aimed at accelerating software projects that
have to deal with complicated domains”
(Eric Evans, "Domain Driven Design")
21. What is Domain Driven Design?
“it is a way of thinking and a set of priorities,
aimed at accelerating software projects that
have to deal with complicated domains”
(Eric Evans, "Domain Driven Design")
22. What is Domain Driven Design?
“is a collection of principles and patterns that
help developers craft elegant object systems”
(http://msdn.microsoft.com/en-us/magazine/dd419654.aspx)
23. What is Domain Driven Design?
“is a collection of principles and patterns that
help developers craft elegant object systems”
(http://msdn.microsoft.com/en-us/magazine/dd419654.aspx)
24. What is Domain Driven Design?
“is an approach to software development for
complex needs by connecting the
implementation to an evolving model”
(http://en.wikipedia.org/wiki/Domain-driven_design)
25. What is Domain Driven Design?
“is an approach to software development for
complex needs by connecting the
implementation to an evolving model”
(http://en.wikipedia.org/wiki/Domain-driven_design)
29. Domain
“Every software program relates to some
activity or interest of its user. That subject area
to which the user applies the program is the
domain of the software”
(Eric Evans, "Domain Driven Design")
30. Model
“A model is a simplification. It is an
interpretation of reality that abstracts the
aspects relevant to solving problem at hand
and ignores extraneous detail.”
(Eric Evans, "Domain Driven Design")
31. Model
“A model is a simplification. It is an
interpretation of reality that abstracts the
aspects relevant to solving problem at hand
and ignores extraneous detail.”
(Eric Evans, "Domain Driven Design")
33. Model
“A domain model [...] is not just the knowledge
in a domain expert’s head; it is a rigorously
organized and selective abstraction of that
knowledge.”
(Eric Evans, "Domain Driven Design")
34. Model
“A domain model [...] is not just the knowledge
in a domain expert’s head; it is a rigorously
organized and selective abstraction of that
knowledge.”
(Eric Evans, "Domain Driven Design")
36. Ubiquitous Language
“the domain model can provide the backbone
for that common language [...]. The vocabulary
of that UBIQUITOUS LANGUAGE includes the
names of classes and prominent operations”
(Eric Evans, "Domain Driven Design")
43. Entity
An object with “clear identity and a life-cycle
with state transitions that we care about.”
(http://dddsample.sourceforge.net/characterization.html)
46. It depends.
“We don't assign seats on our flights,
so feel free to sit in any available seat”
47. Value Object
“An object that contains attributes but has no
conceptual identity. They should be treated as
immutable.”
(http://en.wikipedia.org/wiki/Domain-driven_design)
48. Value Object
“A small simple object, like money or a date
range, whose equality isn't based on identity.”
(http://martinfowler.com/eaaCatalog/valueObject.html)
53. Beware about Anemic Domain Model
Both Entity and Value Object
should have data and behaviours.
(http://www.martinfowler.com/bliki/AnemicDomainModel.html)
55. Repository
“A REPOSITORY represents all objects of a
certain type as a conceptual set. It acts like a
collection, except with more elaborate querying
capability”
(Eric Evans, "Domain Driven Design")
57. Repository
“Although most queries return an object or a
collection of objects, it also fits within the
concept to return some types of summary
calculation”
(Eric Evans, "Domain Driven Design")
58. Aggregate
“A DDD aggregate is a cluster of domain
objects that can be treated as a single unit.”
(http://martinfowler.com/bliki/DDD_Aggregate.html)
59. Aggregate
“DDD aggregates are domain concepts (order,
clinic visit, playlist), while collections are
generic.”
(http://martinfowler.com/bliki/DDD_Aggregate.html)
60. Domain Event
“Captures the memory of something interesting
which affects the domain”
(http://martinfowler.com/eaaDev/DomainEvent.html)
63. Layering
“We need to decouple the domain objects from
other functions of the system, so we can avoid
confusing the domain concepts wiht other
concepts”
(Eric Evans, "Domain Driven Design")
64. Layering
“We need to decouple the domain objects from
other functions of the system, so we can avoid
confusing the domain concepts wiht other
concepts”
(Eric Evans, "Domain Driven Design")
67. Domain
“The domain layer is the heart of the software,
and this is where the interesting stuff happens.”
(http://dddsample.sourceforge.net/architecture.html)
68. Application
“The application layer is responsible for driving
the workflow of the application, matching the
use cases at hand”
(http://dddsample.sourceforge.net/architecture.html)
69. Interface
“This layer holds everything that interacts with
other systems”
(http://dddsample.sourceforge.net/architecture.html)
70. Interface
“This layer holds everything that interacts with
other systems”
(http://dddsample.sourceforge.net/architecture.html)
Controller Form
View API
71. Infrastructure
“In simple terms, the infrastructure consists of everything
that exists independently of our application: external
libraries, database engine, application server, messaging
backend and so on.”
(http://dddsample.sourceforge.net/architecture.html)
76. Domain Services
“If a SERVICE were devised to make
appropriate debits and credits for a found
transfer, that capability would belong in the
domain layer”
(Eric Evans, "Domain Driven Design")
77. Application Services
“if the banking application can convert and
export our transactions into a spreadsheet file
[...] that export is an application SERVICE”
(Eric Evans, "Domain Driven Design")
78. Infrastructural Services
“a bank might have an application that sends
an e-mail [...]. The interface that encapsulates
the email system, [...] is a SERVICE in the
infrastructure layer”
(Eric Evans, "Domain Driven Design")
80. Persistence Ignorance
“In DDD, we don't consider any databases.
DDD is all about the domain, not about the
database, and Persistence Ignorance (PI) is a
very important aspect of DDD”
(http://williamdurand.fr/2013/08/20/ddd-with-symfony2-making-things-clear/)
81. YAGNI
You aren't gonna need it.
You don’t need a Database or a Framework to
modelling the Domain
82. YAGNI
You aren't gonna need it.
You don’t need a Database or a Framework to
modelling the Domain
88. Our starting domain
I need a tool to plan my trips.
Every trip must have at least one route
and every route has one or more leg.
A leg has one date and one location.
89. Code
Here, a complete sample code:
https://github.com/leopro/trip-planner
You can follow the building steps, starting from the
first commit.
99. Our starting domain
I need a tool to plan my trips.
Every trip must have at least one route
and every route has one or more leg.
A leg has one date and one location.
100. <?php
namespace LeoproTripPlannerDomainTests;
use LeoproTripPlannerDomainEntityTrip;
class TripTest extends PHPUnit_Framework_TestCase
{
public function testCreateTripReturnATripWithFirstRoute()
{
$trip = Trip::create('my first planning');
$this->assertInstanceOf('LeoproTripPlannerDomainEntityTrip', $trip);
$this->assertEquals(1, $trip->getRoutes()->count());
}
}
103. Our starting domain
I need a tool to plan my trips.
Every trip must have at least one route
and every route has one or more leg.
A leg has one date and one location.
105. <?php
namespace LeoproTripPlannerDomainEntity;
class Route
{
private $name;
private $legs;
private function __construct($name)
{
$this->name = $name;
$this->legs = new ArrayCollection();
}
public static function create($tripName)
{
return new self('first route for trip: ' . $tripName);
}
public function addLeg($date)
{
$leg = Leg::create($date);
$this->legs->add($leg);
}
//...
106. <?php
namespace LeoproTripPlannerDomainEntity;
class Route
{
private $name;
private $legs;
private function __construct($name)
{
$this->name = $name;
$this->legs = new ArrayCollection();
}
public static function create($tripName)
{
return new self('first route for trip: ' . $tripName);
}
public function addLeg($date)
{
$leg = Leg::create($date);
$this->legs->add($leg);
}
//...
Wait, we really want two legs
with the same date?
107. The model is changing
I need a tool to plan my trips.
Every trip must have at least one route
and every route has one or more leg
and two leg with the same date for the
same route are not allowed .
A leg has one date and one location.
109. <?php
namespace LeoproTripPlannerDomainEntity;
class Route
{
//...
public function addLeg($date)
{
$leg = Leg::create($date);
$dateAlreadyUsed = function($key, $element) use($leg) {
return $element->getDate() == $leg->getDate();
};
if ($this->legs->exists($dateAlreadyUsed)) {
throw new DateAlreadyUsedException($date . ' already used');
}
$this->legs->add($leg);
}
//...
110. Our starting domain
I need a tool to plan my trips.
Every trip must have at least one route
and every route has one or more leg.
A leg has one date and one location.
111. <?php
namespace LeoproTripPlannerDomainTests;
use LeoproTripPlannerDomainEntityLeg;
class LegTest extends PHPUnit_Framework_TestCase
{
public function testCreateLegReturnsALegWithDateAndLocation()
{
$leg = Leg::create('01/01/2014', 'd/m/Y', -3.386665, 36.736908);
$this->assertInstanceOf('LeoproTripPlannerDomainEntityLeg', $leg);
$location = $leg->getLocation();
$this->assertInstanceOf('LeoproTripPlannerDomainEntityLocation', $location);
$point = $location->getPoint();
$this->assertInstanceOf('LeoproTripPlannerDomainValueObjectPoint', $point);
$this->assertEquals(-3.386665, $point->getLatitude());
$this->assertEquals(36.736908, $point->getLongitude());
}
}
112. <?php
namespace LeoproTripPlannerDomainEntity;
use LeoproTripPlannerDomainValueObjectDate;
class Leg
{
private $date;
private $location;
private function __construct(Date $date, Location $location)
{
$this->date = $date;
$this->location = $location;
}
public static function create($date, $dateFormat, $latitude, $longitude)
{
$date = new Date($date, $dateFormat);
return new self(
$date,
Location::create($date->getFormattedDate(), $latitude, $longitude)
);
}
//..
113. <?php
namespace LeoproTripPlannerDomainEntity;
use LeoproTripPlannerDomainValueObjectPoint;
class Location
{
private $name;
private $point;
private function __construct($name, Point $point)
{
$this->name = $name;
$this->point = $point;
}
public static function create($name, $latitude, $longitude)
{
return new self($name, new Point($latitude, $longitude)
);
}
public function getPoint()
{
return $this->point;
}
}
114. <?php
namespace LeoproTripPlannerDomainTests;
use LeoproTripPlannerDomainValueObjectPoint;
class PointTest extends PHPUnit_Framework_TestCase
{
public function testDistance()
{
$firstPoint = new Point(-3.386665, 36.736908);
$secondPoint = new Point(-3.428112, 35.932846);
$this->assertEquals(89, $firstPoint->getCartographicDistance($secondPoint));
$this->assertEquals(98, $firstPoint->getApproximateRoadDistance($secondPoint));
}
}
115. <?php
namespace LeoproTripPlannerDomainTests;
use LeoproTripPlannerDomainValueObjectPoint;
class PointTest extends PHPUnit_Framework_TestCase
{
public function testDistance()
{
$firstPoint = new Point(-3.386665, 36.736908);
$secondPoint = new Point(-3.428112, 35.932846);
$this->assertEquals(89, $firstPoint->getCartographicDistance($secondPoint));
$this->assertEquals(98, $firstPoint->getApproximateRoadDistance($secondPoint));
}
}
Value Object
getCartographicDistance()
getApproximateRoadDistance()
116. <?php
namespace LeoproTripPlannerDomainValueObject;
class Point
{
private $latitude;
private $longitude;
public function __construct($latitude, $longitude)
{
$this->latitude = $latitude;
$this->longitude = $longitude;
}
//..
public function getApproximateRoadDistance(Point $point, $degreeApproximation = 10)
{
$distance = $this->getCartographicDistance($point);
return round($distance + $distance * ($degreeApproximation / 100));
}
122. <?php
namespace LeoproTripPlannerApplicationCommand;
use LeoproTripPlannerApplicationUseCaseUseCaseInterface;
class CommandHandler
{
//...
public function execute($command)
{
try {
$commandClass = get_class($command);
if (!array_key_exists($commandClass, $this->useCases)) {
throw new LogicException($commandClass . ' is not a managed command');
}
$this->useCases[get_class($command)]->run($command);
} catch (Exception $e) {
throw $e;
}
}
}
You can move the state of the
domain, through commands
145. <?php
namespace LeoproTripPlannerInfrastructureBundleAdapter;
use LeoproTripPlannerApplicationContractValidator as ApplicationValidatorInterface;
use LeoproTripPlannerDomainAdapterArrayCollection;
use SymfonyComponentValidatorValidatorValidatorInterface;
class Validator implements ApplicationValidatorInterface
{
private $validator;
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
public function validate($value)
{
$applicationErrors = new ArrayCollection();
$errors = $this->validator->validate($value);
foreach ($errors as $error) {
$applicationErrors->set($error->getPropertyPath(), $error->getMessage());
}
return $applicationErrors;
}
}
151. <?php
namespace LeoproTripPlannerInfrastructureBundleRepository;
use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface;
class TripRepository implements TripRepositoryInterface
{
private $myThirdPartApiClient;
public function __construct(ApiClient $myThirdPartApiClient)
{
$this->myThirdPartApiClient = $myThirdPartApiClient;
}
public function get(TripIdentity $identity)
{
$this->myThirdPartApiClient->get($identity);
}
public function add(Trip $trip)
{
$this->myThirdPartApiClient->store($trip);
}
152. <?php
namespace LeoproTripPlannerInfrastructureBundleRepository;
use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface;
class TripRepository implements TripRepositoryInterface
{
private $myThirdPartApiClient;
public function __construct(ApiClient $myThirdPartApiClient)
{
$this->myThirdPartApiClient = $myThirdPartApiClient;
}
public function get(TripIdentity $identity)
{
$this->myThirdPartApiClient->get($identity);
}
public function add(Trip $trip)
{
$this->myThirdPartApiClient->store($trip);
}
It’s another possible Repository
implementation