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

Immutability to Save an Ever-Changing World - PHP Benelux 2018

Immutability to Save an Ever-Changing World - PHP Benelux 2018

https://conference.phpbenelux.eu/2018/sessions/immutability-to-save-an-ever-changing-world/

Want to build software that is more testable, easier to modify, and has fewer lines of code? Architecting with more immutable objects that are always in a valid state is the most important lesson I have learned in building better software applications. Using immutable value objects will lead to less checking, fewer bugs, more DRY code, and help avoid the “spooky action at a distance” problem in PHP. We will also learn how to use immutable objects and immutable collections to improve design of our mutable entities. Lastly, we’ll see how immutable objects and functional programming can reduce complexity.

F4bb45b2a18ee44c4a28b1664de150bd?s=128

Andrew Cassell

January 27, 2018
Tweet

Transcript

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

  2. None
  3. None
  4. None
  5. None
  6. None
  7. Immutability & Invariants & Encapsulation & Behavior

  8. Good Code?

  9. None
  10. $car->bark(); $dog->drive(); It was not supposed to be about cars

    barking or dogs driving
  11. None
  12. BEER

  13. None
  14. Immutability & Invariants & Encapsulation & Behavior

  15. None
  16. value Mutable

  17. new value Mutable

  18. value Immutable

  19. value Immutable

  20. value Immutable

  21. value Immutable

  22. value Immutable

  23. Mutable Immutable Read-Only Read & Write

  24. None
  25. None
  26. None
  27. None
  28. None
  29. None
  30. None
  31. None
  32. None
  33. Passed in by Reference / Pointer

  34. None
  35. None
  36. None
  37. None
  38. Spooky Action at a Distance

  39. None
  40. None
  41. None
  42. None
  43. None
  44. None
  45. None
  46. Mutable objects depend on time.

  47. Mutable = Value(t) Immutable = Value()

  48. $mutable->function(a,b,c) { global $time; //… }

  49. None
  50. What should be Immutable?

  51. Event

  52. Event History

  53. Request

  54. Command

  55. Almost Everything

  56. HOW?

  57. Immutability in PHP

  58. Plain Old PHP Objects (POPOs)

  59. • POPOs • No setters, all parameters are passed into

    constructor • Declare all class properties as private • No properties that are mutable objects Immutable Objects in PHP
  60. None
  61. None
  62. None
  63. None
  64. None
  65. Composite Immutable Objects

  66. None
  67. None
  68. None
  69. None
  70. None
  71. None
  72. None
  73. None
  74. None
  75. Reflection API

  76. More Classes & Files

  77. Performance & Memory Usage

  78. Immutability & Invariants & Encapsulation & Behavior

  79. Immutability & Invariants & Encapsulation & Behavior

  80. OBJECTS FOR ALL THE THINGS

  81. Not Just Static Typing https://github.com/Fiedzia/type-system-research/blob/master/README.md

  82. Value Objects

  83. None
  84. • POPOs • No setters, all parameters are passed into

    constructor • Declare all class properties as private • No properties that are mutable objects Immutable Objects in PHP
  85. • Immutable Objects • Always in a valid state •

    Validate in constructor • Behavioral methods return new object Value Objects in PHP
  86. Always Valid Value Object Immutable No Identity (Only Values) Recipe

    Name Weight Temperature Entity Identifiable Mutable Lifecycle Contains Value Objects Line Item Brewer Recipe
  87. First Name Last Name Full Name Email Address Recipe Name

    Date Brewed On Recipe ID Temperature Liters Gallons Kilograms Pounds
  88. Value Objects

  89. None
  90. “Gateway Drug to Test Driven Development” Value Objects

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

  92. RecipeName Example

  93. None
  94. None
  95. None
  96. 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.
  97. None
  98. 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. .EE 3 / 3 (100%) Time: 1.56 seconds, Memory: 4.00MB There were 2 errors: 1) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testGetter Error: Call to undefined method Beeriously\Domain\Recipe\RecipeName::getValue() /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:21 2) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testToString Object of class Beeriously\Domain\Recipe\RecipeName could not be converted to string /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:27 ERRORS! Tests: 3, Assertions: 2, Errors: 2. cassell:beeriously cassell$
  99. None
  100. 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. ..E 2 / 3 (67%) Time: 292 ms, Memory: 4.00MB There was 1 error: 1) Beeriously\Tests\Unit\Domain\Recipe\RecipeNameTest::testToString Object of class Beeriously\Domain\Recipe\RecipeName could not be converted to string /app/src/Tests/Unit/Domain/Recipe/RecipeNameTest.php:27 ERRORS! Tests: 35, Assertions: 78, Errors: 1. make: *** [unit] Error 2 cassell:beeriously cassell$
  101. None
  102. 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)
  103. None
  104. None
  105. BrewedOn Example

  106. None
  107. Pounds Example

  108. None
  109. None
  110. None
  111. <?php var_dump(-0.0 > 0); // false var_dump(-0.0 < 0); //

    false var_dump(-0.0 === 0.0); // true (float) var_dump(-0.0 === 0); // false (int) var_dump(-0.0 == false); // true (bool) var_dump(-0.0 == null); // true (bool) var_dump(-0.0 == null); // true (string) var_dump(-0.0 == "negatviezero"); // true (string)
  112. Immutability & Invariants & Encapsulation & Behavior

  113. Static Constructors

  114. Natural Language Constructors

  115. None
  116. None
  117. Eliminates Checking Value Objects

  118. $poundA->reduceBy($poundB);

  119. if(!is_numeric($a)) { throw new \Exception(…) } if(!is_numeric($b)) { throw new

    \Exception(…) } if($a < $b) { throw new \Exception(…) } $pounds = $a - $b;
  120. None
  121. Factories $weight = $factory->create( ‘2 lbs’, $dependency1, $dependency2);

  122. Don’t Extend

  123. class Pounds extends FloatValue { //… }

  124. class EmailAddress extends StringObject { //… }

  125. None
  126. None
  127. Composite Value Objects

  128. Immutability & Invariants & Encapsulation & Behavior

  129. Full Name First Name + Last Name

  130. None
  131. Full Name Salutation + First Name Middle Name(s) + Last

    Name + Suffix
  132. Encoding Business Logic in Value Objects

  133. Alcohol by Volume

  134. None
  135. Sugar + Yeast = Alcohol + CO 2 C H

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

  137. Sugar + Yeast = Alcohol + CO2

  138. Sugar - Sugar = Alcohol START FINISH

  139. Sugar - Sugar = Alcohol START FINISH Volume Volume

  140. Hydrometer

  141. learntomoonshine.com

  142. None
  143. Michael L. Hall - Zymurgy, Summer 1995, vol. 18, no.

    2
  144. None
  145. None
  146. Calibration Temperature Minimum Gravity Reading Maximum Gravity Reading

  147. None
  148. None
  149. None
  150. None
  151. None
  152. None
  153. None
  154. None
  155. None
  156. None
  157. None
  158. None
  159. None
  160. None
  161. None
  162. None
  163. None
  164. None
  165. None
  166. None
  167. None
  168. None
  169. None
  170. None
  171. None
  172. None
  173. None
  174. None
  175. Immutable Value Objects Typical RecipeName String Temperature Float(7,3) DateBrewed DateTime

    Gravity Float(7,3) ABV Function + Validation?
  176. Immutability & Invariants & Encapsulation & Behavior

  177. Entities

  178. Identifiable Have State and are Mutable (Lifecycle) Operate using Value

    Objects No Security or Permission Checks *(Ideally) Storage Agnostic *(Ideally) Never in an Invalid State Entities
  179. BEHAVIOR FIRST! STORAGE SECOND! “Storage Agnostic”

  180. SETTERS ARE BAD

  181. None
  182. None
  183. None
  184. None
  185. Calling two setters in a row on the same object

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

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

    Concept?
  188. None
  189. None
  190. None
  191. DO AS MUCH AS YOU CAN IN VALUE OBJECTS!

  192. None
  193. Doctrine

  194. None
  195. None
  196. What is actually an entity?

  197. None
  198. None
  199. None
  200. None
  201. None
  202. None
  203. Immutable “Entity”

  204. Immutable Book Mutable Price

  205. $book->setPrice(16.96); $price = new Price($book) $price->setAmount(16.96); Mutable Immutable

  206. $book->setPrice(16.96); $amount = new Amount(16.96, new Currency(‘USD’); $price = new

    BookPrice($book, $amount); $price->change(new Amount(15.99, new Currency(‘USD’)); Mutable Immutable
  207. Immutable Collections

  208. None
  209. None
  210. None
  211. Value Object

  212. None
  213. “Immutable” Collection of Entities

  214. None
  215. None
  216. None
  217. None
  218. None
  219. None
  220. None
  221. None
  222. None
  223. None
  224. Functional Programming

  225. Procedural Programming

  226. $recipe = new Recipe(//…); $recipe->addThisIngredient(//…); $recipe->addWater(//…); $recipe->addThatIngredient(//…); $recipe->boil(//…); $recipe->getEstimatedABV(); //…

    === inputs
  227. $recipe = new Recipe($time + //…); $recipe->addThisIngredient($time + //…); $recipe->addWater($time

    + //…); $recipe->addThatIngredient($time + //…); $recipe->boil($time + //…); $recipe->getEstimatedABV($time);
  228. $recipe = new Recipe($history + //…); $recipe->addThisIngredient($history + //…); $recipe->addWater($history

    + //…); $recipe->addThatIngredient($history + //…); $recipe->boil($history + //…); $recipe->getEstimatedABV($history);
  229. Functional Programming

  230. None
  231. None
  232. Functional programming with mutable objects is more difficult than with

    immutable objects.
  233. Don’t Do It.

  234. Functional Programming?

  235. Procedural with Immutable Objects

  236. Final Thoughts

  237. None
  238. homebrewersassociation.org

  239. None
  240. PHP BEER BREWING SOFTWARE FOR BITTER DEVELOPERS WHO WANT TO

    BREW EVEN MORE BITTER BEERS And never heard of not-invented-here and wanna learn and do other good stuff too.
  241. None
  242. None
  243. Think Immutable First

  244. None
  245. https://www.youtube.com/watch?v=O3T9-FilHVI

  246. None
  247. https://joind.in/talk/aabc8 Thank You!