Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to Domain-Driven Design in PHP - ZendCon & OpenEnterprise 2018

Introduction to Domain-Driven Design in PHP - ZendCon & OpenEnterprise 2018

Building PHP applications using domain-driven design (DDD) techniques results in code that is easier to modify, maintain, test, and makes for a better user experience. In this hands-on tutorial, you will become versed in the best practices for solving problems in PHP from start to finish.
You will learn to:
– Discover a ubiquitous language and identify changes in the design of PHP classes, methods, and problem solving
– Assemble an incorruptible domain model in PHP by encapsulating business logic in immutable value objects, specifications, and entities
– Employ best practices for persisting and accessing entities and aggregate roots in Doctrine and Zend DB and Hydrator
– Use advanced PHP object-oriented techniques to simplify code and reduce state complexity
– Plan for change by using the Hexagonal Architecture Pattern
– Evaluate when and how to use CQRS techniques
– Write your own DIY event sourcing and use libraries like EventSauce and Prooph
– Introduce DDD to a “legacy” codebase
– Discover recommended resources for learning more about applying DDD in PHP

Andrew Cassell

October 15, 2018

More Decks by Andrew Cassell

Other Decks in Programming


  1. 14

  2. People don't want to buy a quarter-inch drill, they want

    a quarter-inch hole. Theodore Levitt
  3. DDD CRUD/MUD Recipe Building Inventory Brewhouse Activity Beer Menu /

    Prices Brew Sessions Brewery Website Calclations QA Record Keeping
  4. 1. Naming Things 2. Cache Invalidation 3. Off By One

    Errors Top 10 Reasons Programming is Hard
  5. Ubiquitous Language $recipe->addGrains($grains); Code: We need to be able to

    add a measured amount of grain to a recipe. Business:
  6. Ubiquitous Language $recipe->addDryHop($hopQuantity,$schedule); Code: We need to add .2oz of

    12% AA per gallon of Simcoe hops to a recipe 7 days after the boil is complete. This is called dry hopping. $schedule = new DryHopSchedule(‘7 Days’); $hopQuantity = HopQuantity::(“Simcoe”,”12%AA”,”.2oz per gallon”); Business:
  7. @recipe Feature: A user should be able to dry hop

    a recipe Scenario: User adds dry hops to recipe Given I am authenticated as a user And I am on “/recipe/0ef360fd“ And I press "Add Hops” And I fill out form with: | Hop Name | | Simcoe | Then I see “Alpha Acid: 11.5 - 15.0“
  8. $hop = (new HopsSupply(…))->findOneByName(‘Simcoe’); $catalog = new RecipeCatalog(…); $recipe =

    $catalog->findOneRecipeByName(‘iPHPA’); $recipe->addDryHop($hop, new DryHopSchedule(‘7 Days’)); $brewers = new Brewers(…); $brewer = $brewers->findOneByUsername(‘cassell’); $brewSession = $brewer->brew($recipe); $brewSession->dryHopsWereAdded($hop, Datetime::now());
  9. $hop = (new HopsSupplyRepo())->findOneByName(‘Simcoe’); $catalog = new RecipeCatalogRepository(…); $recipe =

    $catalog->findOneRecipeByName(‘iPHPA’); $recipe->addDryHop($hop, new DryHopSchedule(‘7 Days’)); $brewers = new BrewersRepository(…); $brewer = $brewers->findOneByUsername(‘cassell’); $brewSession = $brewer->brew($recipe); $brewSession->dryHopsWereAdded($hop, Datetime::now());
  10. CRUD/MUD DDD Users Brewers String RecipeName Integer IBU Float(7,3) AlphaAcid

    Array of ORM Objects GrainBill BeerInventoryRepositoryInterface Menu
  11. […], we want to establish the idea that a computer

    language is not just a way of getting a computer to perform operations but rather that it is a novel formal medium for expressing ideas about methodology. Thus, programs must be written for people to read, and only incidentally for machines to execute.
  12. Photos: Terry Brown and Gordon Stettinius Alice Shane P.J. Sam

    George Male Age 32 Brewer for 3 Years Got into brewing after the record store he worked at closed. Is very tech savvy and carries an Android. Male Age 50 Brewing for 10 Years Just Promoted to COO Retired civil servant who believes software can make a brewery for regulatory compliant. Female Age 27 New Hire Library science graduate who was just hired away from the local university. iPhone user. Male Age 52 Librarian for 30 years Has been brewing beer as long as he can remember. Loves beer podcasts but is not a fan of technology. Male Age 46 Brewer for 20 Years Loves poetry and writing. Brewing is just a job to pay the bills.
  13. 7 Dirty Words When Meeting With a Domain Expert 1.Session

    2.Repository 3.Abstract 4.Interface 5.Class 6.Database 7.Foreign Key
  14. Domain Event • Beer Was Brewed • Grain Was Added

    • Hops Were Added • Brewer Changed Email Address
  15. Command • Start Brewing Session • Add Grain to Recipe

    • Add Hops To Fermenter • Change Email Address
  16. Actor • Who did it? • If you only have

    one user you might not need this
  17. Value Object Immutable No Identity (Only Values) Hop Name Amount

    Paid Temperature Entity Identifiable Mutable Lifecycle Contains Value Objects Line Item Brewer Water Chemistry Aggregate Entity Responsible For Child Entities Transaction Boundary Invoice Recipe Brew Session
  18. Value Objects Email Address Recipe Name Date We Brewed On

    Weight Temperature Ubiquitous Language
  19. Plain Old PHP Objects (POPOs) • Declare Class Properties as

    Private • No Setters (behaviors will return new) • No References to Mutable Objects • Throw Exceptions in Constructor Value Objects
  20. cassell:beeriously cassell$ docker run --rm --interactive --tty --network beeriously_default --volume

    `pwd`:/app --user : --workdir /app beeriously_php-fpm /app/vendor/bin/phpunit --configuration /app/src/Tests/Unit/ phpunit.xml.dist PHPUnit 6.4.3 by Sebastian Bergmann and contributors. FEE 3 / 3 (100%) Time: 2.34 seconds, Memory: 4.00MB There were 2 errors: 1) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testGetter Error: Class 'Beeriously\Domain\Recipe\RecipeName' not found /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:20 2) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testToString Error: Class 'Beeriously\Domain\Recipe\RecipeName' not found /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:26 -- There was 1 failure: 1) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testEmptyFails Failed asserting that exception of type "Error" matches expected exception "Beeriously\Domain\Recipe\InvalidRecipeNameException". Message was: "Class 'Beeriously\Domain\Recipe\RecipeName' not found" at /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:15 . ERRORS! Tests: 3, Assertions: 1, Errors: 2, Failures: 1.
  21. cassell:beeriously cassell$ docker run --rm --interactive --tty --network beeriously_default --volume

    `pwd`:/app --user : --workdir /app beeriously_php-fpm /app/vendor/bin/phpunit --configuration /app/src/Tests/ Unit/phpunit.xml.dist PHPUnit 6.4.3 by Sebastian Bergmann and contributors. ... 3 / 3 (100%) Time: 192 ms, Memory: 4.00MB OK (3 tests, 4 assertions)
  22. ABV

  23. Sugar + Yeast = Alcohol + CO 2 C H

    O ->2C H OH + 2CO 6 12 6 2 5 2
  24. Identifiable Have State and are Mutable (Lifecycle) Never in an

    Invalid State Operate using Value Objects No Security or Permission Checks *(Ideally) Storage Agnostic Entities
  25. Making an Entity: Brewer Identifiable ID (UUID) Mutable $brewer->changeUsername() $brewer->changeName()

    $brewer->changeEmail() Never in an Invalid State __construct Operate Using Value Objects FullName, EmailAddress, …
  26. Mathias Verraes - Decoupling the Model from the Framework at

    Laracon EU 2014 https://www.youtube.com/watch?v=QaIGN_cTcc8
  27. Ruby Midwest 2011 - Keynote: Architecture the Lost Years by

    Robert Martin https://www.youtube.com/watch?v=WpkDN78P884
  28. Domain Events • Part of the Core Domain • Happened

    In The Past • Important Enough To Record (Persist) • Important Enough To Concern Other Bounded Contexts • Immutable Value Objects • Do Not Contain Entities or Other Mutable Objects • Can Be Created in an Entity
  29. Event Sourcing • Object Properties are Not Persisted • Events

    Are Persisted to 
 Append Only Event Storage
  30. • Avoids Data Mapping • Avoids Object-relational Impedance Mismatch •

    Reduces Database Table Counts (Related Tables) • Potentially Reduces Model Counts Event Sourcing
  31. https://joind.in/talk/93cfd DDD Topics Covered •Ubiquitous Language •Event Storming •Modelling •Value

    Objects •Entities •Aggregates •Hexagonal Architecture •CQRS •Event Sourcing