Clean architecture with ddd layering in php

25,045 views

Published on

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

Published in: Internet, Technology, Business
3 Comments
118 Likes
Statistics
Notes
No Downloads
Views
Total views
25,045
On SlideShare
0
From Embeds
0
Number of Embeds
338
Actions
Shares
0
Downloads
460
Comments
3
Likes
118
Embeds 0
No embeds

No notes for slide

Clean architecture with ddd layering in php

  1. 1. Clean Architecture using DDD layering in PHP Leonardo Proietti @_leopro_
  2. 2. 1. Clean Architecture
  3. 3. Definition of Clean Architecture
  4. 4. Definition of Clean Architecture Independent of Frameworks Testable Independent of UI Independent of Database Independent of any external agency
  5. 5. Definition of Clean Architecture Independent of Frameworks Testable Independent of UI Independent of Database Independent of any external agency
  6. 6. Definition of Clean Architecture Independent of Frameworks Testable Independent of UI Independent of Database Independent of any external agency
  7. 7. Definition of Clean Architecture Independent of Frameworks Testable Independent of UI Independent of Database Independent of any external agency
  8. 8. Definition of Clean Architecture Independent of Frameworks Testable Independent of UI Independent of Database Independent of any external agency
  9. 9. Definition of Clean Architecture Independent of Frameworks Testable Independent of UI Independent of Database Independent of any external agency
  10. 10. Hey bro, I respect your opinion but ...
  11. 11. It isn't just my opinion
  12. 12. Do you know "Uncle Bob", isn't it?
  13. 13. I’m just another dwarf.
  14. 14. The Clean Architecture
  15. 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. 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)
  17. 17. 2. Domain Driven Design
  18. 18. What is Domain Driven Design?
  19. 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. 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. 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. 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. 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. 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. 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)
  26. 26. Mmhh interesting … but what does it mean?
  27. 27. Make yourself comfortable
  28. 28. 3. DDD Core
  29. 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. 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. 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")
  32. 32. Sounds familiar?
  33. 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. 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")
  35. 35. Next it’s maybe the most important thing in DDD
  36. 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")
  37. 37. Ubiquitous Language It’s a shared jargon between domain experts and developers, based on Domain Model
  38. 38. Take care of Ubiquitous Language
  39. 39. What does “coffee” mean? Alberto Brandolini AKA ziobrando
  40. 40. Ubiquitous Language “changes to the language will be recognized as changes in the domain model” (Eric Evans, "Domain Driven Design")
  41. 41. Context “The setting in which a word or statement appears that determines its meaning.”
  42. 42. 4. DDD Building Blocks
  43. 43. Entity An object with “clear identity and a life-cycle with state transitions that we care about.” (http://dddsample.sourceforge.net/characterization.html)
  44. 44. Are these entities?
  45. 45. It depends.
  46. 46. It depends. “We don't assign seats on our flights, so feel free to sit in any available seat”
  47. 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. 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)
  49. 49. Are these value objects?
  50. 50. In most of the contexts, but ...
  51. 51. Beware about Anemic Domain Model
  52. 52. Beware about Anemic Domain Model Both Entity and Value Object should have data and behaviours. (http://www.martinfowler.com/bliki/AnemicDomainModel.html)
  53. 53. Few other concepts Repository Aggregate Domain Event
  54. 54. 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")
  55. 55. Repository “All repositories provide methods that allow client to request objects matching some criteria” (Eric Evans, "Domain Driven Design")
  56. 56. 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")
  57. 57. 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)
  58. 58. Aggregate “DDD aggregates are domain concepts (order, clinic visit, playlist), while collections are generic.” (http://martinfowler.com/bliki/DDD_Aggregate.html)
  59. 59. Domain Event “Captures the memory of something interesting which affects the domain” (http://martinfowler.com/eaaDev/DomainEvent.html)
  60. 60. How long does it take?
  61. 61. 5. DDD Layering
  62. 62. 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")
  63. 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. 64. Layering (http://guptavikas.wordpress.com/2009/12/01/domain-driven-design-an-introduction/)
  65. 65. Layering (http://dddsample.sourceforge.net/architecture.html)
  66. 66. Domain “The domain layer is the heart of the software, and this is where the interesting stuff happens.” (http://dddsample.sourceforge.net/architecture.html)
  67. 67. 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)
  68. 68. Interface “This layer holds everything that interacts with other systems” (http://dddsample.sourceforge.net/architecture.html)
  69. 69. Interface “This layer holds everything that interacts with other systems” (http://dddsample.sourceforge.net/architecture.html) Controller Form View API
  70. 70. 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)
  71. 71. Separation of concerns
  72. 72. Services
  73. 73. Services “Sometimes, it just isn’t a thing.” (Eric Evans, "Domain Driven Design")
  74. 74. Services Domain Services Application Services Infrastructural Services
  75. 75. 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")
  76. 76. 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")
  77. 77. 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")
  78. 78. 6. Code First
  79. 79. 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/)
  80. 80. YAGNI You aren't gonna need it. You don’t need a Database or a Framework to modelling the Domain
  81. 81. YAGNI You aren't gonna need it. You don’t need a Database or a Framework to modelling the Domain
  82. 82. Where should I start then?!?
  83. 83. Understanding the Domain
  84. 84. Talking with domain experts
  85. 85. DDD is Agile, we should be iterative (http://dddsample.sourceforge.net/architecture.html)
  86. 86. 7. Let’s code
  87. 87. 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.
  88. 88. Code Here, a complete sample code: https://github.com/leopro/trip-planner You can follow the building steps, starting from the first commit.
  89. 89. Code Let’s focus on some steps
  90. 90. composer.json { "name": "my trip planner", "autoload": { "psr-0": { "": "src/" } }, "require": { "php": ">=5.3.3", "doctrine/collections": "v1.2", }, "require-dev": { "phpunit/phpunit": "4.0.*" }, "config": { "bin-dir": "bin" } }
  91. 91. composer.json { "name": "my trip planner", "autoload": { "psr-0": { "": "src/" } }, "require": { "php": ">=5.3.3", "doctrine/collections": "v1.2", }, "require-dev": { "phpunit/phpunit": "3.7.*" }, "config": { "bin-dir": "bin" } } I don't need anything more to start
  92. 92. composer.json { "name": "my trip planner", "autoload": { "psr-0": { "": "src/" } }, "require": { "php": ">=5.3.3", "doctrine/collections": "v1.2", }, "require-dev": { "phpunit/phpunit": "3.7.*" }, "config": { "bin-dir": "bin" } } Ok, I have a dependency on doctrine/collections ...
  93. 93. composer.json { "name": "my trip planner", "autoload": { "psr-0": { "": "src/" } }, "require": { "php": ">=5.3.3", "doctrine/collections": "v1.2", }, "require-dev": { "phpunit/phpunit": "3.7.*" }, "config": { "bin-dir": "bin" } } … the missing (SPL) Collection/Array/OrderedMap interface
  94. 94. composer.json { "name": "my trip planner", "autoload": { "psr-0": { "": "src/" } }, "require": { "php": ">=5.3.3", "doctrine/collections": "v1.2", }, "require-dev": { "phpunit/phpunit": "3.7.*" }, "config": { "bin-dir": "bin" } } Anyway, you can put a boundary
  95. 95. <?php namespace LeoproTripPlannerDomainContract; use DoctrineCommonCollectionsCollection as DoctrineCollection; interface Collection extends DoctrineCollection {}
  96. 96. <?php namespace LeoproTripPlannerDomainAdapter; use DoctrineCommonCollectionsArrayCollection as DoctrineArrayCollection; use LeoproTripPlannerDomainContractCollection; class ArrayCollection extends DoctrineArrayCollection implements Collection {}
  97. 97. Domain
  98. 98. 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.
  99. 99. <?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()); } }
  100. 100. <?php namespace LeoproTripPlannerDomainEntity; use LeoproTripPlannerDomainAdapterArrayCollection; class Trip { private $name, private $routes; private function __construct($name, Route $route) { $this->name = $name; $this->routes = new ArrayCollection(array($route)); } public function create($name) { return new self($name, new Route); } public function getRoutes() { return $this->routes; } }
  101. 101. <?php namespace LeoproTripPlannerDomainEntity; class Route { }
  102. 102. 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.
  103. 103. <?php namespace LeoproTripPlannerDomainTests; use LeoproTripPlannerDomainEntityRoute; class RouteTest extends PHPUnit_Framework_TestCase { public function testCreateRouteAddingALeg() { $route = Route::create('my first trip'); $route->addLeg('06-06-2014'); $this->assertEquals(1, $route->getLegs()->count()); }
  104. 104. <?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); } //...
  105. 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); } //... Wait, we really want two legs with the same date?
  106. 106. 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.
  107. 107. /** * @expectedException ...DateAlreadyUsedException */ public function testNoDuplicationDateForTheSameRoute() { $route = Route::create('my first trip'); $route->addLeg('06-06-2014'); $route->addLeg('06-06-2014'); }
  108. 108. <?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); } //...
  109. 109. 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.
  110. 110. <?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()); } }
  111. 111. <?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) ); } //..
  112. 112. <?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; } }
  113. 113. <?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)); } }
  114. 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)); } } Value Object getCartographicDistance() getApproximateRoadDistance()
  115. 115. <?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)); }
  116. 116. public function getCartographicDistance(Point $point) { $earthRadius = 3958.75; $dLat = deg2rad($point->getLatitude() - $this->latitude); $dLng = deg2rad($point->getLongitude() - $this->longitude); $a = sin($dLat / 2) * sin($dLat / 2) + cos(deg2rad($this->latitude)) * cos(deg2rad($point->getLatitude())) * sin($dLng / 2) * sin($dLng / 2); $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); $dist = $earthRadius * $c; $meterConversion = 1.609344; $geopointDistance = $dist * $meterConversion; return round($geopointDistance, 0); }
  117. 117. public function getCartographicDistance(Point $point) { $earthRadius = 3958.75; $dLat = deg2rad($point->getLatitude() - $this->latitude); $dLng = deg2rad($point->getLongitude() - $this->longitude); $a = sin($dLat / 2) * sin($dLat / 2) + cos(deg2rad($this->latitude)) * cos(deg2rad($point->getLatitude())) * sin($dLng / 2) * sin($dLng / 2); $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); $dist = $earthRadius * $c; $meterConversion = 1.609344; $geopointDistance = $dist * $meterConversion; return round($geopointDistance, 0); } Got the point?
  118. 118. Application
  119. 119. <?php namespace LeoproTripPlannerApplicationCommand; use LeoproTripPlannerApplicationUseCaseUseCaseInterface; class CommandHandler { private $useCases; public function registerCommands(array $useCases) { foreach ($useCases as $useCase) { if ($useCase instanceof UseCaseInterface) { $this->useCases[$useCase->getManagedCommand()] = $useCase; } else { throw new LogicException(‘...'); } } } //...
  120. 120. <?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; } } }
  121. 121. <?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
  122. 122. <?php namespace LeoproTripPlannerApplicationCommand; use LeoproTripPlannerApplicationContractCommandInterface; use LeoproTripPlannerDomainAdapterArrayCollection; class CreateTripCommand implements CommandInterface { private $name; public function __construct($name) { $this->name = $name; } public function getRequest() { return new ArrayCollection( array( 'name' => $this->name ) ); } }
  123. 123. <?php namespace LeoproTripPlannerApplicationUseCase; class CreateTripUseCase extends AbstractUseCase implements UseCaseInterface { private $tripRepository; public function __construct(TripRepository $tripRepository) { $this->tripRepository = $tripRepository; } public function run(CommandInterface $command) { $this->exceptionIfCommandNotManaged($command); $request = $command->getRequest(); $trip = Trip::createWithFirstRoute(new TripIdentity(uniqid()), $request->get('name')); $this->tripRepository->add($trip); return $trip; } }
  124. 124. <?php namespace LeoproTripPlannerApplicationUseCase; class CreateTripUseCase extends AbstractUseCase implements UseCaseInterface { private $tripRepository; public function __construct(TripRepository $tripRepository) { $this->tripRepository = $tripRepository; } public function run(CommandInterface $command) { $this->exceptionIfCommandNotManaged($command); $request = $command->getRequest(); $trip = Trip::createWithFirstRoute(new TripIdentity(uniqid()), $request->get('name')); $this->tripRepository->add($trip); return $trip; } } Defining a TripRepository interface ...
  125. 125. <?php namespace LeoproTripPlannerDomainContract; use LeoproTripPlannerDomainEntityTrip; use LeoproTripPlannerDomainValueObjectTripIdentity; interface TripRepository { /** * @param TripIdentity $identity * @return LeoproTripPlannerDomainEntityTrip */ public function get(TripIdentity $identity); /** * @param Trip $trip * @return void */ public function add(Trip $trip); }
  126. 126. <?php namespace LeoproTripPlannerDomainContract; use LeoproTripPlannerDomainEntityTrip; use LeoproTripPlannerDomainValueObjectTripIdentity; interface TripRepository { /** * @param TripIdentity $identity * @return LeoproTripPlannerDomainEntityTrip */ public function get(TripIdentity $identity); /** * @param Trip $trip * @return void */ public function add(Trip $trip); } … and interfaces for Validator and Event Dispatcher
  127. 127. <?php namespace LeoproTripPlannerApplicationContract; interface Validator { /** * @param $value * @return LeoproTripPlannerDomainContractCollection */ public function validate($value); } interface EventDispatcher { /** * @param array $listeners * @return EventListener[] */ public function registerListeners(array $listeners); /** * @param $event */ public function notify($name, $event); }
  128. 128. About validation In DDD, entities should be always valid.
  129. 129. About validation But if you ask “where do I put validation?” you'll get different answers.
  130. 130. About validation If you are using commands, validate the command itself, is a good trade-off.
  131. 131. Infrastructure
  132. 132. Framework's revenge
  133. 133. composer.json "require": { "php": ">=5.3.3", "doctrine/collections": "v1.2", "symfony/symfony": "~2.4", "doctrine/dbal": "dev-master", "doctrine/orm": "dev-master", "doctrine/doctrine-bundle": "dev-master", "twig/extensions": "~1.0", "symfony/assetic-bundle": "~2.3", "symfony/swiftmailer-bundle": "~2.3", "symfony/monolog-bundle": "~2.4", "sensio/distribution-bundle": "~2.3", "sensio/framework-extra-bundle": "~3.0", "sensio/generator-bundle": "~2.3", "incenteev/composer-parameter-handler": "~2.0", "doctrine/data-fixtures": "dev-master", "doctrine/migrations": "dev-master", "doctrine/doctrine-migrations-bundle": "dev-master", "doctrine/doctrine-fixtures-bundle": "dev-master" },
  134. 134. Mapping entities
  135. 135. app/config/config.yml orm: auto_generate_proxy_classes: "%kernel.debug%" auto_mapping: false mappings: TripPlannerDomain: type: yml prefix: LeoproTripPlannerDomainEntity dir: %kernel.root_dir%/../src/Leopro/TripPlanner/InfrastructureBundle/Resources/config/doctrine/entity is_bundle: false TripPlannerDomainValueObjects: type: yml prefix: LeoproTripPlannerDomainValueObject dir: %kernel.root_dir%/../src/Leopro/TripPlanner/InfrastructureBundle/Resources/config/doctrine/value_object is_bundle: false
  136. 136. InfrastructureBundle/Resources/config/entity/Route.orm.yml LeoproTripPlannerDomainEntityTrip: type: entity table: trip embedded: identity: class: LeoproTripPlannerDomainValueObjectTripIdentity fields: name: type: string length: 250 manyToMany: routes: targetEntity: LeoproTripPlannerDomainEntityRoute joinTable: name: trip_routes joinColumns: link_id: referencedColumnName: identity_id inverseJoinColumns: report_id: referencedColumnName: internalIdentity cascade: ["persist"]
  137. 137. Validate commands
  138. 138. InfrastructureBundle/Resources/config/validation.yml LeoproTripPlannerApplicationCommandCreateTripCommand: properties: name: - NotBlank: ~ LeoproTripPlannerApplicationCommandAddLegToRouteCommand: properties: tripIdentity: - NotBlank: ~ routeIdentity: - NotBlank: ~ date: - NotBlank: ~ dateFormat: - NotBlank: ~ latitude: - NotBlank: ~ longitude: - NotBlank: ~
  139. 139. Configuring services
  140. 140. src/LeoPro/TripPlanner/InfrastructureBundle/Resources/config/services.xml <services> <!-- Exposed Services --> <service id="trip_repository" alias="trip_repository.doctrine"></service> <service id="command_handler" class="%application.command_handler.class%"> <argument type="service" id="infrastructure.validator"/> <argument type="service" id="application.event_dispatcher"/> </service> </services>
  141. 141. src/LeoPro/TripPlanner/InfrastructureBundle/Resources/config/services.xml <services> <!-- Not Exposed Services --> <service id="application.event_dispatcher" public="false" class="%application.event_dispatcher.class%"> </service> <service id="use_case.create_trip" public="false" class="...CreateTripUseCase"> <argument type="service" id="trip_repository"/> <tag name="use_case"/> </service> <service id="use_case.add_leg_to_route" public="false" class="LeoproTripPlannerApplicationUseCaseAddLegToRouteUseCase"> <argument type="service" id="trip_repository"/> <tag name="use_case"/> </service> <service id="use_case.update_location" public="false" class="LeoproTripPlannerApplicationUseCaseUpdateLocationUseCase"> <argument type="service" id="trip_repository"/> <tag name="use_case"/> </service> </services>
  142. 142. src/LeoPro/TripPlanner/InfrastructureBundle/Resources/config/services.xml <services> <!-- Adapter --> <service id="infrastructure.validator" public="false" class="%infrastructure.validator.class%"> <argument type="service" id="validator"/> </service> <service id="infrastructure.event_dispatcher_adapter" public="false" class="%infrastructure.event_dispatcher_adapter.class%"> <argument type="service" id="event_dispatcher"/> <tag name="event_dispatcher_listener"/> </service> <!-- Concrete Implementations --> <service id="trip_repository.doctrine" public="false" class="%infrastructure.trip_repository.doctrine.class%"> <argument type="service" id="doctrine.orm.entity_manager"/> </service> </services>
  143. 143. Adapter Ops … some parts of the frameworks do not fit our interfaces.
  144. 144. <?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; } }
  145. 145. Repository
  146. 146. <?php namespace LeoproTripPlannerInfrastructureBundleRepository; use DoctrineORMEntityManager; use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface; class TripRepository implements TripRepositoryInterface { private $em; public function __construct(EntityManager $em) { $this->em = $em; } public function get(TripIdentity $identity) { $qb = $this->em->createQueryBuilder() ->select('t') ->from("TripPlannerDomain:Trip", 't') ->where('t.identity.id = :identity'); $qb->setParameter('identity', $identity); return $qb->getQuery()->getOneOrNullResult(); }
  147. 147. <?php namespace LeoproTripPlannerInfrastructureBundleRepository; use DoctrineORMEntityManager; use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface; class TripRepository implements TripRepositoryInterface { public function add(Trip $trip) { $this->em->persist($trip); $this->em->flush(); } }
  148. 148. <?php namespace LeoproTripPlannerInfrastructureBundleRepository; use DoctrineORMEntityManager; use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface; class TripRepository implements TripRepositoryInterface { public function add(Trip $trip) { $this->em->persist($trip); $this->em->flush(); } } Then it’s like a Doctrine repository?!?
  149. 149. <?php namespace LeoproTripPlannerInfrastructureBundleRepository; use DoctrineORMEntityManager; use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface; class TripRepository implements TripRepositoryInterface { public function add(Trip $trip) { $this->em->persist($trip); $this->em->flush(); } } No, it’s quite different
  150. 150. <?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); }
  151. 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); } It’s another possible Repository implementation
  152. 152. Presentation
  153. 153. <?php namespace LeoproTripPlannerPresentationBundleController; use LeoproTripPlannerPresentationBundleFormTypeCreateTripType; class ApiController extends Controller { /** * @Route("/", name="create_trip") * @Template */ public function createTripAction(Request $request) { $form = $this->createForm(new CreateTripType()); $form->handleRequest($request); if ($form->isValid()) { $trip = $this->get('command_handler')->execute($form->getData()); return new Response('ok'); } return array( 'form' => $form->createView(), ); } }
  154. 154. <?php namespace LeoproTripPlannerPresentationBundleFormType; use LeoproTripPlannerApplicationCommandCreateTripCommand; class CreateTripType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('name') ->add('save', 'submit'); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'LeoproTripPlannerApplicationCommandCreateTripCommand', 'empty_data' => function (FormInterface $form) { $command = new CreateTripCommand( $form->get('name')->getData() ); return $command; }, )); } }
  155. 155. One step to the finish line
  156. 156. What have I learned?
  157. 157. A clean architecture helps in avoiding the Big Ball of Mud.
  158. 158. Also starting with a very simple domain
  159. 159. Iteration by iteration
  160. 160. Complexity could grow
  161. 161. If our system is tightly coupled
  162. 162. and the domain is scattered
  163. 163. we are losing the chance of responding to changes.
  164. 164. Talking about testing ...
  165. 165. … independence from frameworks, database, UI ...
  166. 166. … means talking about business.
  167. 167. Let the code speak the language of the business
  168. 168. First, taking care of the model
  169. 169. Then choosing the right tool
  170. 170. ...
  171. 171. We reached the finish line, well done.
  172. 172. Thank you :-)
  173. 173. Credits ● Eric Evans, - "Domain Driven Design" ● “Uncle Bob” - http://blog.8thlight.com/uncle-bob/archive.html and books ● http://williamdurand.fr/2013/08/20/ddd-with-symfony2-making-things-clear/ ● http://williamdurand.fr/2013/08/07/ddd-with-symfony2-folder-structure-and- code-first/ ● http://verraes.net/2013/04/decoupling-symfony2-forms-from-entities/ ● http://www.whitewashing. de/2012/08/22/building_an_object_model__no_setters_allowed.html ● http://nicolopignatelli.me/valueobjects-a-php-immutable-class-library/ ● http://welcometothebundle.com/domain-driven-design-and-symfony-for- simple-app/ ● http://www.slideshare.net/SteveRhoades2/implementing-ddd-concepts-in- php
  174. 174. Credits ● http://devlicio.us/blogs/casey/archive/2009/02/16/ddd-aggregates-and- aggregate-roots.aspx ● http://www.slideshare.net/perprogramming/application-layer-33335917 ● http://lostechies.com/jimmybogard/2008/08/21/services-in-domain-driven- design/ ● http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design- ddd/ ● http://www.slideshare.net/jeppec/agile-ddd-cqrs ● http://www.slideshare.net/thinkddd/practical-domain-driven-design-cqrs- and-messaging-architectures ● http://www.codeproject.com/Articles/339725/Domain-Driven-Design-Clear- Your-Concepts-Before-Yo ● http://lostechies.com/jimmybogard/2008/05/21/entities-value-objects- aggregates-and-roots/
  175. 175. Credits ● http://www.slideshare.net/ziobrando/gestire-la-complessit-con-domain- driven-design ● http://lostechies.com/jimmybogard/2009/02/15/validation-in-a-ddd-world/ ● http://lostechies.com/jimmybogard/2009/09/03/ddd-repository- implementation-patterns/ ● http://www.sapiensworks.com/blog/post/2012/04/18/DDD-Aggregates- And-Aggregates-Root-Explained.aspx ● http://www.udidahan.com/2009/06/29/dont-create-aggregate-roots/ ● http://jblewitt.com/blog/?p=241 ● http://www.sapiensworks.com/blog/post/2013/10/18/Modelling-Aggregate- Roots-Relationships.aspx ● http://www.sapiensworks.com/blog/post/2013/01/15/Domain-Driven- Design-Aggregate-Root-Modelling-Fallacy.aspx
  176. 176. Credits ● http://lostechies.com/jamesgregory/2009/05/09/entity-interface-anti-pattern ● http://www.slideshare.net/piotrpelczar/cqrs-28299581 ● http://richarddingwall.name/2009/10/13/life-inside-an-aggregate-root-part- 1/ ● http://lostechies.com/jimmybogard/2010/02/24/strengthening-your-domain- aggregate-construction/ ● http://guptavikas.wordpress.com/2009/12/21/domain-driven-design- creating-domain-objects/ ● http://gorodinski.com/blog/2012/05/19/validation-in-domain-driven-design- ddd/ ● http://verraes.net/2013/12/related-entities-vs-child-entities/ ● http://devlicio.us/blogs/casey/archive/2009/02/20/ddd-the-repository- pattern.aspx ● http://gojko.net/2009/09/30/ddd-and-relational-databases-the-value-object- dilemma/

×