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

Be9645e99392d37fd0e8018f22122fc3?s=128

Andrew Cassell

October 15, 2018
Tweet

Transcript

  1. Domain-Driven Design Workshop Domain-Driven Design Workshop Andrew Cassell @alc277 andrewcassell.com

  2. None
  3. None
  4. None
  5. None
  6. Domain-Driven Design

  7. Domain-Driven Design

  8. None
  9. Common Sense Software Development

  10. None
  11. None
  12. None
  13. None
  14. 14

  15. None
  16. Encapsulation & Immutability & Modeling & Behavior

  17. Domain

  18. Domain

  19. BEER

  20. None
  21. Photo: Kathleen Pierce - Bangor Daily News

  22. None
  23. None
  24. None
  25. None
  26. None
  27. None
  28. Brewing Software

  29. None
  30. None
  31. None
  32. None
  33. recipe hop grain hop_inventory grain_inventory recipe_hop_link user recipe_user_link brew_event ROLE_USER

    ROLE_ADMIN
  34. Source: http://jonclaytonbiz.com/

  35. None
  36. None
  37. People don't want to buy a quarter-inch drill, they want

    a quarter-inch hole. Theodore Levitt
  38. None
  39. None
  40. None
  41. #JBTD (http://jobstobedone.org/)

  42. None
  43. YYYYY

  44. Why? Why? Why? Why? Why?

  45. Search Caluclation List Automation

  46. DDD !=

  47. None
  48. None
  49. None
  50. None
  51. None
  52. http://www.csharpstar.com/

  53. None
  54. DDD CRUD Correctness Testability Usability Maintainability Modifiability

  55. None
  56. DDD BALL OF MUD / CRUD Correctness Testability Usability Maintainability

    Modifiability
  57. DDD CRUD/MUD Recipe Building Inventory Brewhouse Activity Beer Menu /

    Prices Brew Sessions Brewery Website Calclations QA Record Keeping
  58. Who is responsible

  59. None
  60. None
  61. None
  62. None
  63. Ubiquitous Language

  64. Ubiquitous Language Developers Domain Experts

  65. Ubiquitous Language

  66. User Brewer Employee Taster Customer

  67. None
  68. 1. Naming Things 2. Cache Invalidation 3. Off By One

    Errors Top 10 Reasons Programming is Hard
  69. $recipe = BeerRecipe::findOrFail($uuid);
 $hop = HopRepo::where(“name”,”=“,”Simcoe”)->take(1)->get(); 
 if (!$recipe->hopsArray()->contains(‘hop_id’,$hop->id)) {


    
 $recipe->hopsArray()->attach($hop->id, [‘weight' => 1.0]);
 }
 
 
 $recipe->save();
  70. None
  71. Ubiquitous Language $recipe->addGrains($grains); Code: We need to be able to

    add a measured amount of grain to a recipe. Business:
  72. Ubiquitous Language $recipe->getEstimatedAlcoholByVolumeABV(); Code: We need to be able to

    calculate an estimated ABV for a recipe. Business:
  73. Ubiquitous Language $storage = (new StorageFacilities())->findByOneName(“Silo #4”); $storage->howMuchOfGrainType(new GrainType(‘Winter Wheat’));

    Business: Code: We need to know how much Winter Wheat is in our storage silo #4.
  74. 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:
  75. @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“
  76. $recipe->dryHop($hop); $recipe->addIngredient($hop); $recipe->addToBoil($hop); $recipe->addToBoilKettle($hop); $recipe->addToBoiler($hop); $recipe->addFirstWortHop($hop); $recipe->addToFermenter($hop); $recipe->addToMashTun($hop); //fails

  77. $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());
  78. $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());
  79. CRUD/MUD DDD Users Brewers String RecipeName Integer IBU Float(7,3) AlphaAcid

    Array of ORM Objects GrainBill BeerInventoryRepositoryInterface Menu
  80. […], 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.
  81. DDD BALL OF MUD/CRUD Correctness ✓ Testability Usability Maintainability Modifiability

  82. None
  83. The Brewer, designed and engraved in the Sixteenth Century, by

    Jost Amman.
  84. Inventory Marketing Finance Production “Hop”

  85. Quantity at a Location Flavor Expenditure Ingredient “Hop”

  86. Bounded Contexts

  87. Inventory Recipes Brewhouse

  88. Slide: Vaughn Vernon

  89. None
  90. Inventory Recipes Brewhouse

  91. Inventory Recipes Brewhouse

  92. Message Inventory Recipes Brewhouse

  93. Domain Event Inventory Recipes Brewhouse

  94. None
  95. None
  96. None
  97. None
  98. None
  99. Personas

  100. adaptivepath.com

  101. 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.
  102. None
  103. None
  104. None
  105. None
  106. https://blog.intercom.io/using-job-stories-design-features-ui-ux/

  107. None
  108. None
  109. 7 Dirty Words When Meeting With a Domain Expert 1.Session

    2.Repository 3.Abstract 4.Interface 5.Class 6.Database 7.Foreign Key
  110. User Research

  111. Photo: Mathias Verraes

  112. Command Actor Model Domain Event

  113. Domain Event • Something that happens in the software. •

    Past Tense
  114. Domain Event • Beer Was Brewed • Grain Was Added

    • Hops Were Added • Brewer Changed Email Address
  115. None
  116. None
  117. None
  118. None
  119. None
  120. None
  121. None
  122. None
  123. None
  124. Command Actor Model Domain Event

  125. Command • The action that precipitates the event • Written

    in Present Tense
  126. Command • Start Brewing Session • Add Grain to Recipe

    • Add Hops To Fermenter • Change Email Address
  127. None
  128. None
  129. Command Actor Model Domain Event

  130. Model • Command is acted upon this thing • Might

    Include Supporting Models
  131. Model • Recipe • Ingredient • Brewer

  132. None
  133. Command Actor Model Domain Event

  134. Actor • Who did it? • If you only have

    one user you might not need this
  135. None
  136. None
  137. Command Actor Model Domain Event

  138. None
  139. None
  140. None
  141. Source: Jeff Patton

  142. None
  143. business origami

  144. Jess McMullin Business Origami

  145. Jess McMullin Business Origami

  146. Sean Jalleh

  147. None
  148. DDD BALL OF MUD/CRUD Correctness ✓✓ Testability Usability ✓ Maintainability

    Modifiability
  149. Domain Objects Value Objects Domain Events Entities Aggregates

  150. 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
  151. Value Objects

  152. Email Address Recipe Name Date We Brewed On Weight Temperature

    Value Objects
  153. Value Objects Email Address Recipe Name Date We Brewed On

    Weight Temperature Ubiquitous Language
  154. Value Objects EmailAddress RecipeName BrewedOn Pounds DegreesFahrenheit Email Address Recipe

    Name Date We Brewed On Weight Temperature
  155. Not Just Static Typing https://github.com/Fiedzia/type-system-research/blob/master/README.md Value Objects

  156. Immutable

  157. Immutable Avoid Spooky Action at a Distance

  158. ALWAYS VALID Value Objects

  159. Value Objects

  160. “Gateway Drug to Test Driven Development” Value Objects

  161. “Gateway Drug to Test Driven Development” Value Objects #MYTESTSDONTPASS

  162. Plain Old PHP Objects (POPOs) Value Objects

  163. 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
  164. Only depend on scalars or other value objects. Value Objects

  165. They are not dependencies that you inject. Value Objects

  166. RecipeName Example

  167. None
  168. None
  169. None
  170. None
  171. 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.
  172. None
  173. None
  174. None
  175. 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)
  176. None
  177. None
  178. BrewedOn Example

  179. None
  180. None
  181. None
  182. Pounds Example

  183. None
  184. None
  185. None
  186. $poundA->reduceBy($poundB);

  187. Eliminates Checking Value Objects

  188. None
  189. Static Constructors

  190. Natural Language Constructors

  191. None
  192. None
  193. Temperature Example

  194. None
  195. None
  196. None
  197. None
  198. None
  199. Composite Value Objects

  200. Full Name First Name + Last Name

  201. None
  202. Encoding Business Logic in Value Objects

  203. None
  204. None
  205. Amount of Alcohol

  206. None
  207. ABV

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

    O ->2C H OH + 2CO 6 12 6 2 5 2
  209. Sugar + Yeast = Alcohol + CO2

  210. Sugar - Sugar = Alcohol START FINISH

  211. Sugar - Sugar = Alcohol START FINISH Volume Volume

  212. Specific Gravity

  213. Hydrometer

  214. learntomoonshine.com

  215. None
  216. None
  217. None
  218. None
  219. None
  220. None
  221. None
  222. None
  223. None
  224. None
  225. None
  226. None
  227. None
  228. None
  229. None
  230. None
  231. None
  232. None
  233. None
  234. DDD CRUD/MUD RecipeName String Temperature Float(7,3) DateBrewed DateTime Gravity Float(7,3)

    ABV Function + Validation?
  235. DDD BALL OF MUD/CRUD Correctness ✓✓ Testability ✓ Usability ✓

    Maintainability Modifiability
  236. None
  237. Entities

  238. 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
  239. DO AS MUCH AS YOU CAN IN VALUE OBJECTS!

  240. Doctrine Mappings Doctrine Annotations Laravel Eloquent ORM “Storage Agnostic”

  241. BEHAVIOR FIRST! STORAGE SECOND! “Storage Agnostic”

  242. Single Entity Entities Regular Aggregate Contain Other Entities

  243. Brewer Grain Hops BeerOnTap Entity Types Regular Aggregate Invoice Recipe

    BrewSession BeerMenu
  244. 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, …
  245. None
  246. SETTERS ARE BAD

  247. None
  248. None
  249. None
  250. None
  251. Calling two setters in a row on the same object

    Missing a Concept?
  252. Calling two methods in a row on the same object

    Missing a Concept?
  253. Passing more than one parameter to a method Missing a

    Concept?
  254. None
  255. None
  256. Doctrine

  257. None
  258. None
  259. None
  260. None
  261. None
  262. None
  263. None
  264. None
  265. None
  266. None
  267. None
  268. None
  269. None
  270. Bounded Contexts

  271. Inventory Recipes Brewhouse

  272. Inventory Recipes Brewhouse Recipe\Grain Inventory\Grain Brewing\BrewSession\Grain

  273. Inventory Recipes Brewhouse

  274. Inventory Recipes Brewhouse Brewers Supporting Bounded Context

  275. Mathias Verraes - Mathias Verraes - Emergent Boundaries https://www.youtube.com/watch?v=ECM1rPYxvD4

  276. • Entities • Manages Child Entities • Transactional Boundary •

    The Easiest Models to Identify Aggregates
  277. Aggregates Recipe BrewSession BeerMenu

  278. None
  279. None
  280. None
  281. None
  282. None
  283. None
  284. DDD BALL OF MUD/CRUD Correctness ✓✓ Testability ✓✓ Usability ✓

    Maintainability ✓✓ Modifiability ✓
  285. Task Based User Interface

  286. None
  287. None
  288. None
  289. DDD BALL OF MUD/CRUD Correctness ✓✓ Testability ✓✓ Usability ✓✓

    Maintainability ✓✓ Modifiability ✓
  290. None
  291. Security

  292. Domain (Business Logic) Application Services Controller Persistence Event/Command Bus Security

    Templates
  293. Controller Domain Framework

  294. None
  295. None
  296. None
  297. None
  298. Controller Domain Framework

  299. Mathias Verraes - Decoupling the Model from the Framework at

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

    Robert Martin https://www.youtube.com/watch?v=WpkDN78P884
  301. None
  302. Domain (Business Logic) Application Services Controller Persistence Event/Command Bus Security

    Templates
  303. DDD BALL OF MUD/CRUD Correctness ✓✓ Testability ✓✓ Usability ✓✓

    Maintainability ✓✓✓ Modifiability ✓
  304. Domain Events

  305. 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
  306. None
  307. BrewSessionWasPlanned

  308. BrewSessionWasPlanned

  309. None
  310. None
  311. None
  312. None
  313. None
  314. None
  315. None
  316. DDD BALL OF MUD/CRUD Correctness ✓✓✓ Testability ✓✓ Usability ✓✓

    Maintainability ✓✓✓ Modifiability ✓✓
  317. None
  318. Command Query Responsibility Segregation

  319. CQRS

  320. Write Read

  321. Write Model Read Model(s)

  322. Write Domain Model Read Model(s)

  323. Write Domain Model Read (Complicated SQL Queries)

  324. Controller Command Bus Handler Handler Handler

  325. Controller Command Bus Handler Handler Handler Request

  326. Controller Command Bus Handler Handler Handler Command

  327. None
  328. None
  329. Controller Command Bus Handler Handler Handler Command

  330. Controller Command Bus Handler Handler Handler Command

  331. Controller Command Bus Handler Handler Handler Command

  332. Controller Command Bus Handler Handler Handler Command Response

  333. Controller Command Bus Handler Handler Handler Command

  334. Controller Command Bus Handler Handler Handler Response

  335. Controller Command Bus Handler Handler Handler Response

  336. Controller Command Bus Handler Handler Handler Response

  337. https://gnugat.github.io/ 2016/05/11/towards-cqrs- command-bus.html

  338. None
  339. Why Do CQRS?

  340. Controller Domain Framework

  341. Controller API (versions?) Console Commands

  342. Command Bus Command Query Responsibility Segregation

  343. Write Domain Model Read Model(s)

  344. Event Sourcing

  345. None
  346. None
  347. None
  348. Event Sourcing • Object Properties are Not Persisted • Events

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

    Reduces Database Table Counts (Related Tables) • Potentially Reduces Model Counts Event Sourcing
  350. Slide: Microsoft

  351. Libraries • https://github.com/eventsaucephp • https://github.com/broadway • https://github.com/prooph • https://github.com/szjani/predaddy

  352. Recipe Example

  353. Learning More

  354. homebrewersassociation.org

  355. None
  356. None
  357. DEV BOOK CLUB https://www.youtube.com/user/devbookclub

  358. None
  359. None
  360. None
  361. None
  362. None
  363. None
  364. DDDinPHP Google Group http://DDDinPHP.org

  365. Final Thoughts

  366. None
  367. None
  368. None
  369. None
  370. Inventory Recipes Brewhouse

  371. Inventory Recipes Brewhouse Legacy Context

  372. None
  373. Value Objects “Light”

  374. Andrew Cassell @alc277 andrewcassell.com Immutability to Save an Ever-Changing World

  375. None
  376. None
  377. https://joind.in/talk/93cfd DDD Topics Covered •Ubiquitous Language •Event Storming •Modelling •Value

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