$30 off During Our Annual Pro Sale. View Details »

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.

Andrew Cassell

January 27, 2018
Tweet

More Decks by Andrew Cassell

Other Decks in Technology

Transcript

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

    View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. Immutability &
    Invariants &
    Encapsulation &
    Behavior

    View Slide

  8. Good
    Code?

    View Slide

  9. View Slide

  10. $car->bark();
    $dog->drive();
    It was not supposed to be about cars barking or dogs driving

    View Slide

  11. View Slide

  12. BEER

    View Slide

  13. View Slide

  14. Immutability &
    Invariants &
    Encapsulation &
    Behavior

    View Slide

  15. View Slide

  16. value
    Mutable

    View Slide

  17. new
    value
    Mutable

    View Slide

  18. value
    Immutable

    View Slide

  19. value
    Immutable

    View Slide

  20. value
    Immutable

    View Slide

  21. value
    Immutable

    View Slide

  22. value
    Immutable

    View Slide

  23. Mutable
    Immutable
    Read-Only Read & Write

    View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. View Slide

  30. View Slide

  31. View Slide

  32. View Slide

  33. Passed in by
    Reference /
    Pointer

    View Slide

  34. View Slide

  35. View Slide

  36. View Slide

  37. View Slide

  38. Spooky
    Action at a
    Distance

    View Slide

  39. View Slide

  40. View Slide

  41. View Slide

  42. View Slide

  43. View Slide

  44. View Slide

  45. View Slide

  46. Mutable objects
    depend on time.

    View Slide

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

    View Slide

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

    View Slide

  49. View Slide

  50. What should be
    Immutable?

    View Slide

  51. Event

    View Slide

  52. Event History

    View Slide

  53. Request

    View Slide

  54. Command

    View Slide

  55. Almost
    Everything

    View Slide

  56. HOW?

    View Slide

  57. Immutability
    in PHP

    View Slide

  58. Plain Old PHP Objects
    (POPOs)

    View Slide

  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

    View Slide

  60. View Slide

  61. View Slide

  62. View Slide

  63. View Slide

  64. View Slide

  65. Composite
    Immutable
    Objects

    View Slide

  66. View Slide

  67. View Slide

  68. View Slide

  69. View Slide

  70. View Slide

  71. View Slide

  72. View Slide

  73. View Slide

  74. View Slide

  75. Reflection
    API

    View Slide

  76. More
    Classes &
    Files

    View Slide

  77. Performance &
    Memory Usage

    View Slide

  78. Immutability &
    Invariants &
    Encapsulation &
    Behavior

    View Slide

  79. Immutability &
    Invariants &
    Encapsulation &
    Behavior

    View Slide

  80. OBJECTS FOR
    ALL THE THINGS

    View Slide

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

    View Slide

  82. Value
    Objects

    View Slide

  83. View Slide

  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

    View Slide

  85. • Immutable Objects
    • Always in a valid state
    • Validate in constructor
    • Behavioral methods return new object
    Value Objects in PHP

    View Slide

  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

    View Slide

  87. First Name
    Last Name
    Full Name
    Email Address
    Recipe Name
    Date Brewed On
    Recipe ID
    Temperature
    Liters
    Gallons
    Kilograms
    Pounds

    View Slide

  88. Value Objects

    View Slide

  89. View Slide

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

    View Slide

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

    View Slide

  92. RecipeName
    Example

    View Slide

  93. View Slide

  94. View Slide

  95. View Slide

  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.

    View Slide

  97. View Slide

  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$

    View Slide

  99. View Slide

  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$

    View Slide

  101. View Slide

  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)

    View Slide

  103. View Slide

  104. View Slide

  105. BrewedOn
    Example

    View Slide

  106. View Slide

  107. Pounds
    Example

    View Slide

  108. View Slide

  109. View Slide

  110. View Slide

  111. 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)

    View Slide

  112. Immutability &
    Invariants &
    Encapsulation &
    Behavior

    View Slide

  113. Static
    Constructors

    View Slide

  114. Natural
    Language
    Constructors

    View Slide

  115. View Slide

  116. View Slide

  117. Eliminates Checking
    Value Objects

    View Slide

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

    View Slide

  119. if(!is_numeric($a)) {
    throw new \Exception(…)
    }
    if(!is_numeric($b)) {
    throw new \Exception(…)
    }
    if($a < $b) {
    throw new \Exception(…)
    }
    $pounds = $a - $b;

    View Slide

  120. View Slide

  121. Factories
    $weight = $factory->create(
    ‘2 lbs’,
    $dependency1,
    $dependency2);

    View Slide

  122. Don’t Extend

    View Slide

  123. class Pounds extends FloatValue
    {
    //…
    }

    View Slide

  124. class EmailAddress extends StringObject
    {
    //…
    }

    View Slide

  125. View Slide

  126. View Slide

  127. Composite
    Value Objects

    View Slide

  128. Immutability &
    Invariants &
    Encapsulation &
    Behavior

    View Slide

  129. Full Name
    First Name + Last Name

    View Slide

  130. View Slide

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

    View Slide

  132. Encoding
    Business Logic in
    Value Objects

    View Slide

  133. Alcohol by
    Volume

    View Slide

  134. View Slide

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

    View Slide

  136. Sugar + Yeast =
    Alcohol + CO2

    View Slide

  137. Sugar + Yeast =
    Alcohol + CO2

    View Slide

  138. Sugar - Sugar = Alcohol
    START FINISH

    View Slide

  139. Sugar - Sugar = Alcohol
    START FINISH
    Volume
    Volume

    View Slide

  140. Hydrometer

    View Slide

  141. learntomoonshine.com

    View Slide

  142. View Slide

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

    View Slide

  144. View Slide

  145. View Slide

  146. Calibration Temperature
    Minimum Gravity Reading
    Maximum Gravity Reading

    View Slide

  147. View Slide

  148. View Slide

  149. View Slide

  150. View Slide

  151. View Slide

  152. View Slide

  153. View Slide

  154. View Slide

  155. View Slide

  156. View Slide

  157. View Slide

  158. View Slide

  159. View Slide

  160. View Slide

  161. View Slide

  162. View Slide

  163. View Slide

  164. View Slide

  165. View Slide

  166. View Slide

  167. View Slide

  168. View Slide

  169. View Slide

  170. View Slide

  171. View Slide

  172. View Slide

  173. View Slide

  174. View Slide

  175. Immutable Value Objects Typical
    RecipeName String
    Temperature Float(7,3)
    DateBrewed DateTime
    Gravity Float(7,3)
    ABV Function + Validation?

    View Slide

  176. Immutability &
    Invariants &
    Encapsulation &
    Behavior

    View Slide

  177. Entities

    View Slide

  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

    View Slide

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

    View Slide

  180. SETTERS
    ARE BAD

    View Slide

  181. View Slide

  182. View Slide

  183. View Slide

  184. View Slide

  185. Calling two setters in a row
    on the same object
    Missing a Concept?

    View Slide

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

    View Slide

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

    View Slide

  188. View Slide

  189. View Slide

  190. View Slide

  191. DO AS MUCH AS
    YOU CAN IN
    VALUE OBJECTS!

    View Slide

  192. View Slide

  193. Doctrine

    View Slide

  194. View Slide

  195. View Slide

  196. What is actually
    an entity?

    View Slide

  197. View Slide

  198. View Slide

  199. View Slide

  200. View Slide

  201. View Slide

  202. View Slide

  203. Immutable
    “Entity”

    View Slide

  204. Immutable
    Book
    Mutable
    Price

    View Slide

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

    View Slide

  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

    View Slide

  207. Immutable
    Collections

    View Slide

  208. View Slide

  209. View Slide

  210. View Slide

  211. Value Object

    View Slide

  212. View Slide

  213. “Immutable”
    Collection
    of Entities

    View Slide

  214. View Slide

  215. View Slide

  216. View Slide

  217. View Slide

  218. View Slide

  219. View Slide

  220. View Slide

  221. View Slide

  222. View Slide

  223. View Slide

  224. Functional
    Programming

    View Slide

  225. Procedural
    Programming

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  229. Functional
    Programming

    View Slide

  230. View Slide

  231. View Slide

  232. Functional programming with
    mutable objects is more difficult
    than with immutable objects.

    View Slide

  233. Don’t Do It.

    View Slide

  234. Functional
    Programming?

    View Slide

  235. Procedural with
    Immutable
    Objects

    View Slide

  236. Final Thoughts

    View Slide

  237. View Slide

  238. homebrewersassociation.org

    View Slide

  239. View Slide

  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.

    View Slide

  241. View Slide

  242. View Slide

  243. Think
    Immutable
    First

    View Slide

  244. View Slide

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

    View Slide

  246. View Slide

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

    View Slide