Advertisement
Advertisement

More Related Content

Advertisement
Advertisement

The quest for global design principles - PHP Benelux 2016

  1. Global Design Principles The Quest for Matthias Noback
  2. Stability Change
  3. Stability versus change • Backwards compatibility of APIs • Semantic versioning • HTTP status codes • XML schemas
  4. For internal APIs only!
  5. The key is in the concept of communication ReceiverSender
  6. What's being communicated? Message Type ("BookHotel") Value (“Ter Elst", “29-01-2016“, “30-01-2016”)
  7. Message flavours Command Event Imperative "BookHotel" Informational "HotelWasBooked"
  8. Message flavours Query Document Interrogatory "GetRecentBookings" Neutral "BookingsCollection"
  9. Sender Construct the message Message Translate Construct (Prepare for
 transport)
  10. Receiver Deconstruct the message (Unwrap) Translate Construct
  11. Global design principles can be discovered if we recognise the fact that communication between objects and applications are (more or less) equal
  12. Communication between objects • Calling a function is like sending a message • The function and its parameters are the message type • The arguments constitute the value of the message
  13. class AccountService { public function deposit($accountId, Money $amount) { ... } } Opportunity for sending a message Inter-object communication
  14. $money = new Money(20000, 'EUR'); $userId = 123; $accountService->deposit( $userId, $money ); Translation The sender prepares the message for the receiver Prepare the message Send the message
  15. Communication between applications • An HTTP request is a message • The HTTP method and URI are the type of the message • The HTTP request body constitutes the value of the message Or AMQP, Stomp, Gearman, ...
  16. PUT /accounts/deposit/ HTTP/1.1 Host: localhost { "accountId": "123", "currency": "EUR", "amount": 20000 } Inter-application communication Opportunity for sending a message
  17. $uri ='/accounts/deposit/'; $data = json_encode([ 'accountId' => 123 'currency' => 'EUR', 'amount' => 20000 ]); $httpClient ->createRequest('PUT', $uri, $data) ->send(); Translation Prepare the message Send the message
  18. /** @Route("/accounts/deposit") */ function depositAction(Request $request) { $request = json_decode($request->getContent()); $money = new Money( $request['amount'], $request['currency'] ); $this->get('account_service')->deposit( $request['accountId'], $money ); return new Response(null, 200); } Translation Prepare the message Send the message
  19. Global design principles Should be applicable to both inter-object and inter-application communication
  20. Part I Stability III Implementation II Information
  21. Sender Receiver Communication Dependency relation!
  22. Stable Unstable What is safer? Unstable Stable OR
  23. To be stable "Steady in position or balance; firm"
  24. Irresponsible What makes something unstable? 1. Nothing depends on it It doesn't need to stay the same for anyone
  25. Dependent What makes something unstable? 2. It depends on many things Any of the dependencies could change at any time
  26. Stability is not a quality of one thing It emerges from the environment of that thing
  27. Responsible What makes something stable? 1. Many things depend on it It has to stay the same, because change would break dependents
  28. Independent What makes something stable? 2. It depends on nothing It is not affected by changes in anything
  29. –The Stable dependencies principle Depend in the direction of stability
  30. Stable package Unstable package What to do when this happens?
  31. Stable package Depend on something that you own Unstable package Stable package
  32. –The Dependency inversion principle Always depend on abstractions, not on concretions
  33. Dependency inversion for objects • Depend on an interface instead of a class • Separate a task from its implementation
  34. Stable thing Communication between systems External application (unstable)
  35. Mediator Stable thing Slightly better External application (unstable)
  36. ConsumerApplication Communication between systems Message queue External application (unstable)
  37. Dependency inversion for systems • Depend on your own (messaging) system • Communicate with other applications through a message queue
  38. Part II Information I Stability III Implementation
  39. Knowledge, data, duplication
  40. Nameplate order system Nameplate machine Business automation for creating nameplates
  41. <h1>You are about to order a nameplate!</h1> <p>Name on the plate: {{ name }}<br/> Width of the plate: {{ 12*(name|length) }} cm.</p> Calculating the width of a nameplate Knowledge
  42. class Nameplate { private $name; function __construct($name) { $this->name = $name; } function widthInCm() { return strlen($this->name) * 12; } } Data object Knowledge is close to the subject
  43. <h1>You are about to order a nameplate!</h1> <p>Name on the plate: {{ nameplate.name }}<br/> Width of the plate: {{ nameplate.widthInCm) }} cm.</p> No knowledge in the template
  44. <nameplate> <name>Ibuildings</name> </nameplate> Serialized Nameplate object
  45. // calculate the width of the nameplate $nameplate = deserialize($xml); $name = $nameplate['name']; $width = strlen($name); $widthPerCharacterInCm = 12; $widthInCm = $width * $widthPerCharacterInCm; // configure the nameplate machine ;) Accepting messages Duplication of knowledge
  46. <nameplate> <name>Ibuildings</name> <width>120</width> </nameplate> Deduplication Knowledge is in one place, facts can be everywhere
  47. $nameplate = deserialize($xml); $width = $nameplate['width']; Accepting messages No duplicate knowledge anymore
  48. –The Don't repeat yourself principle “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” Even across applications!
  49. "Don't repeat yourself" • Doesn't mean you can't repeat data • It means you can't have knowledge in multiple locations
  50. Mutability
  51. What's the difference between... class Money { private $amount; private $currency; public function setAmount($amount) { $this->amount = $amount; } public function getAmount() { return $this->amount; } ... }
  52. ... and this class Money { public $amount; public $currency; }
  53. Inconsistent data $savings = new Money(); $savings->setAmount(1000); // what's the currency at this point? $savings->setCurrency('USD'); // only now do we have consistent data $savings->setCurrency('EUR'); // we have a lot more money now! $savings->setAmount('Amsterdam');
  54. Making something better of this class Money { private $amount; private $currency; public function __construct($amount, $currency) { $this->setAmount($amount); } private function setAmount($amount) { if (!is_int($amount) || $amount < 0) { throw new InvalidArgumentException(); } $this->amount = $amount; } } Private Required
  55. Immutability • Once created, can not be modified • Can only be replaced
  56. Consistent data! $savings = new Money(1000, 'USD'); // we already have consistent data // we can't change anything anymore Immutable data!
  57. Using immutable values • Prevents bugs • Prevents invalid state
  58. What about API messages? <money> <amount>1000</amount> <currency>USD</currency> </money> PUT /savings/ <money> <currency>EUR</currency> </money> POST /savings/
  59. Large object graphs <user> <first-name/> <last-name/> <mobile-phone-number/> <email-address/> <homepage/> <orders> <order/> ... </orders> <tickets> <ticket/> ... </tickets> <payments> <payment/> ... </payments> </user>
  60. Forms & Doctrine ORM $form = $this->createForm(new MoneyType()); $form->handleRequest($request); if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($form->getData()); // calculates change set and executes queries $em->flush(); }
  61. If you allow your users to change every field at any time • You end up with inconsistent data • You loose the why of a change • You end up with the what of only the last change • You ignore the underlying real-world scenario
  62. Commands and events Register ConfirmRegistration SendMessage RegistrationConfirmed MessageSent UserRegistered Application
  63. True • Messages should serve actual use cases instead of patch operations • After processing them, data should be in a valid state
  64. Part III Implementation I Stability II Information IV Conclusion
  65. Implementation
  66. Leaking implementation details
  67. class Person { /** * @return PhoneNumber[] */ public function getPhoneNumbers() { return $this->phoneNumbers; } } Initial implementation
  68. class Person { /** * @return ArrayCollection */ public function getPhoneNumbers() { return $this->phoneNumbers; } } Implementation leakage (Doctrine)
  69. class Person { /** * @return PhoneNumber[] */ public function getPhoneNumbers() { return $this->phoneNumbers->toArray(); } } Hiding implementation
  70. class NameplateController { function getAction($id) { $nameplate = $this ->getDoctrine() ->getManager() ->getRepository(Nameplate::class) ->findOneBy(['id' => $id]); if ($nameplate === null) { throw new NotFoundHttpException(); } ... } } More implementation hiding Actual field names! null or false? "find"?
  71. class NameplateRepository { function byId($id) { $nameplate = $this ->findOneBy(['id' => $id]); if ($nameplate === null) { throw new NameplateNotFound($id); } return $nameplate; } } Push it out of sight Domain-specific exception Hide specific return value No "find"
  72. class NameplateController { function getAction($id) { try { $nameplate = $this ->nameplateRepository ->byId($id); } catch (NameplateNotFound $exception) { throw new NotFoundHttpException(); } ... } } Respect layers Convert domain exception to web specific exception
  73. Basically just plain old OOP • Encapsulation • Abstraction
  74. Implementation details and usability
  75. <ticket> ... <priority type="integer">1</priority> ... Assembla API
  76. <ticket> ... <priority key="highest"> <label>Highest</label> </priority> ... Why not...
  77. <ticket> ... <is-story type="boolean">false</is-story> <total-estimate type="float">0.0</total-estimate> ... Assembla API
  78. <story> ... <total-estimate type="float">0.0</total-estimate> ... Why not...
  79. Design your messages in such a way that • You hide your implementation • Clients won't need to reimplement your logic • Clients get the information they need
  80. API discovery
  81. /** * @param string $password * @param integer $algo * @param array $options */ function password_hash($password, $algo, array $options = array()); Undiscoverable API What are my options here? And here?
  82. class Algorithm extends SplEnum { const __default = self::BCRYPT; const BCRYPT = 1; } Allow no mistakes
  83. [ 'salt' => '...' 'cost' => 10 ] Options for bcrypt hashing
  84. class Bcrypt implements HashingStrategy { /** * @param integer $cost * @param string|null $salt */ public function __construct($cost, $salt = null) { ... } } Inject a strategy
  85. function password_hash( $password, HashingStrategy $hashingStrategy ); Inject a strategy More explicit and... discoverable!
  86. Discoverability of an API • Full Reflection capabilities :) • Basic knowledge of English
  87. // I've got this password I want to hash… $password = ...; // Look, I found a function for this: password_hash() password_hash($password, HashingStrategy $hashingStrategy); // It requires an argument: a password (string) // I already got a password right here: password_hash($password); // Wait, it requires a hashing strategy (a HashingStrategy object) // I just found a class implementing that interface: $hashingStrategy = new BcryptStrategy(); // That doesn't work, BcryptStrategy needs a cost $hashingStrategy = new BcryptStrategy(10); password_hash($password, $hashingStrategy); Example of API discovery Who is talking? How stupid are they? How do you find out a valid range?
  88. /** * @param array $options */ function some_function(array $options); /** * @param integer $type */ function some_other_function($type); /** * @param object $command */ function handle($command); Undiscoverable APIs
  89. Any kind of API should be maximally discoverable
  90. Everything should be an object A class is a type
  91. Define lots of interfaces An interface defines the public API of functions
  92. <?xml version="1.0" encoding="UTF-8"?> <ticket> <reporter> <id>43</id> <link rel="self" href="/api/reporters/43" /> <link rel="index" href="/api/reporters/" /> </reporter> ... HATEOAS Links are a way to explain an "object"
  93. Summary/Hypothesis • Objects are just like applications • Try to apply the same rules to them
  94. Think about • Stable dependencies • Duplication of facts, not knowledge • Immutability over mutability • No leakage of implementation details • Everything should be maximally discoverable
  95. –Chris Hadfield “… I should do things that keep me moving in the right direction, just in case — and I should be sure those things interest me, so whatever happens, I’m happy.”
  96. Questions? Feedback? joind.in/talk/f3b34 Thanks!
Advertisement