Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

The Quest for Global Design Principles

5.128 Aufrufe

Veröffentlicht am

If you’re a programmer you make design decisions every second.
Statements, functions, classes, packages, applications, even entire systems: you need to think, and often think hard, about everything. Luckily there are many useful design principles, patterns and best practices that you can apply. But some of them merely expose code smells. Others only help you design your classes. And some are
applicable to packages only. Wouldn’t it be nice to have some more general, always useful, invariably applicable, foundational design principles?

In this talk we’ll look at software from many different perspectives, and while we’re zooming in and out, we’ll discover some of the deeper principles that lie beneath proper object-oriented design. They are the foundation of many of the well-known design patterns and they may even serve as an explanation for code smells.

Veröffentlicht in: Design, Software
  • Loggen Sie sich ein, um Kommentare anzuzeigen.

The Quest for Global Design Principles

  1. 1. The Quest for Global Design Principles Matthias Noback Zürich, April 20th, 2015
  2. 2. Stability Change
  3. 3. Stability versus change Backwards compatibility of APIs Semantic versioning Status or error codes XML schemas Filesystem abstraction
  4. 4. For internal APIs only!
  5. 5. The key is in the concept of communication ReceiverSender
  6. 6. What's being communicated? Message Type ("BookHotel") Value ("Casa Heinrich", "19-04-2015", "21-04-2015")
  7. 7. Message flavours Command Event Imperative "BookHotel" Informational "HotelWasBooked"
  8. 8. Message flavours Query Document Interrogatory "GetRecentBookings" Neutral "BookingsCollection"
  9. 9. Sender Construct the message Message Translate Construct (Prepare for
 transport)
  10. 10. Receiver Deconstruct the message (Unwrap) Translate Construct
  11. 11. Global design principles can be discovered if we recognise the fact that communication between objects and applications are (more or less) equal
  12. 12. Communication between objects A function call is a message The function and its parameters are the message type The arguments constitute the value of the message
  13. 13. class AccountService! {! ! public function deposit($accountId, Money $amount) ! {! ! ! ...! ! }! } Opportunity for sending a message Inter-object communication
  14. 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. 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. 16. PUT /accounts/123/deposit/ HTTP/1.1! Host: localhost! ! {! ! "currency": "EUR",! ! "amount": 20000! } Inter-application communication Opportunity for sending a message
  17. 17. $uri = sprintf('/accounts/%s/deposit/', $userId);! ! $data = json_encode([! ! 'amount' => 20000,! ! 'currency' => 'EUR'! ]);! ! $httpClient->createRequest('PUT', $data); Translation Prepare the message Send the message
  18. 18. /** @Route("/accounts/{accountId}/deposit") */! function depositAction($accountId, Request $request)! {! ! $request = json_decode($request->getContent());! ! $money = new Money(! ! ! $request['amount'],! ! ! $request['currency']! ! );! ! ! $this->get('account_service')->deposit(! ! ! ! $accountId,! ! ! ! $money! ! ! );! ! ! return new Response(null, 200);! }! Translation Prepare the message Send the message
  19. 19. Global design principles Should be applicable to both inter-object and inter-application communication
  20. 20. Part I Stability III Implementation II Information
  21. 21. Sender Receiver Communication Dependency relation! Unidirectional!
  22. 22. Stable Unstable What is safer? Unstable Stable OR
  23. 23. To be stable "Steady in position or balance; firm"
  24. 24. Irresponsible What makes something unstable? 1. Nothing depends on it It doesn't need to stay the same for anyone
  25. 25. Dependent What makes something unstable? 2. It depends on many things Any of the dependencies could change at any time
  26. 26. Stability is not a quality of one thing It emerges from the environment of that thing
  27. 27. Responsible What makes something stable? 1. Many things depend on it It has to stay the same, because change would break dependents
  28. 28. Independent What makes something stable? 2. It depends on nothing It is not affected by changes in anything
  29. 29. Stable Unstable Again: What is safer? Unstable Stable OR
  30. 30. –The Stable dependencies principle Depend in the direction of stability
  31. 31. Stable Unstable What to do when this happens? Depending on an unstable thing ruins your stability
  32. 32. Also stable Half stableStable thing Unstable Depend on something that you own
  33. 33. –The Dependency inversion principle Always depend on abstractions, not on concretions
  34. 34. Dependency inversion for objects Depend on an interface instead of a class Separate a task from its implementation Your class will be not be sensitive for external changes
  35. 35. Stable thing External system (unstable) Communication between systems
  36. 36. Mediator Stable thing External system Slightly better
  37. 37. WorkerApplication External system Communication between systems Message queue
  38. 38. Dependency inversion for systems Depend on your own (messaging) system Overcomes many problems of distributed computing Your application will not be sensitive for external failures and changes
  39. 39. Part II Information I Stability III Implementation
  40. 40. Knowledge, data, duplication
  41. 41. Nameplate order system Nameplate machine Business automation for creating nameplates
  42. 42. <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
  43. 43. 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
  44. 44. <nameplate>! ! <name>Liip AG</name>! </nameplate> Serialized Nameplate object
  45. 45. // calculate the width of the nameplate! ! $nameplate = deserialize($xml);! $name = $nameplate['name'];! ! $width = strlen($name);! ! $widthPerCharacterInCm = 12;! ! $widthInCm = $characters * $widthPerCharacterInCm;! ! // configure the nameplate machine ;) Accepting messages Duplication of knowledge
  46. 46. <nameplate>! ! <name>Liip AG</name>! ! <width>84</width>! </nameplate> Deduplication Knowledge is in one place, facts can be everywhere
  47. 47. $nameplate = deserialize($xml);! ! $width = $nameplate['width']; Accepting messages No duplicate knowledge anymore
  48. 48. –The Don't repeat yourself principle “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” Even across systems!
  49. 49. "Don't repeat yourself" Doesn't mean you can't repeat data It means you can't have knowledge in multiple locations
  50. 50. Some more considerations
  51. 51. Who's responsible for keeping that knowledge? ?Nameplate order system Nameplate machine
  52. 52. Nameplate order system Nameplate machine How do these machines communicate? GetWidthOfNameplate WidthOfNameplate CreateNameplate NameplateOrderPlaced Reactive/event driven
  53. 53. Mutability
  54. 54. What's the difference between... class Money! {! ! private $amount;! ! private $currency;! ! ! public function setAmount($amount) {! ! ! $this->amount = $amount;! ! }! ! ! public function getAmount() {! ! ! return $this->amount;! ! }! ! ! ...! }
  55. 55. ... and this class Money! {! ! public $amount;! ! public $currency;! }
  56. 56. 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('Zürich');
  57. 57. 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)) {! ! ! ! throw new InvalidArgumentException();! ! ! }! ! ! ! $this->amount = $amount;! ! }! } Private Required
  58. 58. Immutability Once created, state can not be modified Can only be replaced (You don't want information to be modified, only replaced by new information!)
  59. 59. Consistent data! $savings = new Money(1000, 'USD');! // we already have consistent data! ! // we can't change anything anymore Immutable data!
  60. 60. Using immutable values Prevents bugs Prevents invalid state
  61. 61. What about API messages? <money>! ! <amount>1000</amount>! ! <currency>USD</currency>! </money> PUT /savings/ <money>! ! <amount>1000</amount>! ! <currency>EUR</currency>! </money> POST /savings/ "Replace" this value?
  62. 62. Large object graphs <user>! ! <first-name/>! ! <last-name/> ! ! <mobile-phone-number/> ! ! <email-address/> ! ! <homepage/> ! ! <orders>! ! ! <order/>! ! ! ...! ! </orders>! ! <tickets>! ! ! <ticket/>! ! ! ...! ! </tickets>! ! <payments>! ! ! <payment/>! ! ! ...! ! </payments>! </user>
  63. 63. 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();! }
  64. 64. If you allow your users to change every field at any time 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 of a change You might as well distribute phpMyAdmin as a CMS
  65. 65. Serializer & Doctrine ORM $entity = $this! ! ->get('jms_serializer')! ! ->deserialize(! ! ! $request->getContent(), '...', 'xml')! ! );! ! $em = $this->getDoctrine()->getManager();! $em->persist($entity );! $em->flush();!
  66. 66. Commands and events Register ConfirmRegistration SendMessage RegistrationConfirmed MessageSent UserRegistered Application
  67. 67. phparchitecturetour.com TOMORROW! in Zürich
  68. 68. True All messages should conform to actual use cases instead of enabling the client to perform patch operations only After processing a message, all data should be in a valid state, and if the system can't guarantee that, it should fail.
  69. 69. Part III Implementation I Stability II Information IV Conclusion
  70. 70. Implementation
  71. 71. Leaking implementation details
  72. 72. class Person! {! ! /**! ! * @return ArrayCollection! ! */! ! public function getPhoneNumbers() {! ! ! return $this->phoneNumbers;! ! }! } Implementation leakage
  73. 73. class Person! {! ! /**! ! * @return PhoneNumber[]! ! */! ! public function getPhoneNumbers() {! ! ! return $this->phoneNumbers->toArray();! ! }! } Hiding implementation
  74. 74. class Person! {! ! public function addPhoneNumber(PhoneNumber $phoneNumber) {! ! ! $phoneNumber->setPerson($person);! ! ! ! $this->phoneNumbers->add($phoneNumber);! ! }! } Model pollution
  75. 75. class PhoneNumber! {! ! private $countryCode;! ! private $areaCode;! ! private $lineNumber;! ! private $person;! ! ! public function setPerson(Person $person) {! ! ! $this->person = $person;! ! }! } Model pollution
  76. 76. Don't make your model suffer from the database backend Person id PersonPhoneNumber person_id phoneNumber_id ! PhoneNumber id countryCode areaCode lineNumber Entity Value
  77. 77. class Person! {! ! public function addPhoneNumber(PhoneNumber $phoneNumber) {! ! ! $this->phoneNumbers->add(! ! ! ! new PersonPhoneNumber(! ! ! ! ! $this,! ! ! ! ! $phoneNumber! ! ! ! )! ! ! );! ! }! } Hide ORM requirements
  78. 78. class Person! {! ! public function getPhoneNumbers() {! ! ! return $this->phoneNumbers->map(! ! ! ! function (PersonPhoneNumber $personPhoneNumber) {! ! ! ! ! return $personPhoneNumber->getPhoneNumber();! ! ! ! }! ! ! )->toArray();! ! }! } Comply to function API Unwrap the collection of PersonPhoneNumbers Return an array
  79. 79. class NameplateController! {! ! function getAction($id) {! ! ! $nameplate = $this! ! ! ! ->nameplateRepository! ! ! ! ->findOneBy(['id' => $id]);! ! ! ! if ($nameplate === null) {! ! ! ! throw new NotFoundHttpException();! ! ! }! ! ! ! ...! ! }! } More implementation hiding Actual field names! null or false? "find"?
  80. 80. class NameplateRepository! {! ! function fromId($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"
  81. 81. class NameplateController! {! ! function getAction($id) {! ! ! try {! ! ! ! $nameplate = $this! ! ! ! ! ->nameplateRepository! ! ! ! ! ->fromId($id);! ! ! } catch (NameplateNotFound $exception) {! ! ! ! throw new NotFoundHttpException();! ! ! }! ! ! ! ...! ! }! } Respect layers Convert domain exception to web specific exception
  82. 82. Limited by implementation details
  83. 83. <?xml version="1.0" encoding="UTF-8"?>! <ticket>! <id type="integer">10</id>! ...! <status>New</status>! ... Assembla API We do "strcasecmp()" ;)
  84. 84. <ticket>! ...! <priority type="integer">1</priority>! ... Assembla API We do "switch ($priority)"
  85. 85. <ticket>! ...! <priority key="highest">! <label>Highest</label>! </priority>! ... Why not...
  86. 86. ...! <custom-fields>! <Team-yell>We love X-M-L!</Team-yell>! </custom-fields>! </ticket> Assembla API We do "SELECT * FROM custom_fields"
  87. 87. ...! <team-yell>We love X-M-L!</team-yell>! </ticket> Why not...
  88. 88. <ticket>! ...! <is-story type="boolean">false</is-story>! <total-estimate type="float">0.0</total-estimate>! ... Assembla API Table "tickets" has a column "is_story"
  89. 89. <story>! ...! <total-estimate type="float">0.0</total-estimate>! ... Why not...
  90. 90. Design your messages in such a way that You don't show to clients how you implemented it Clients won't need to reimplement your application Clients won't need to do a lot of interpreting Clients get the information they need
  91. 91. /**! * @param string $password! * @param integer $algo! * @param array $options! */! function password_hash($password, $algo, array $options = array()); Undiscoverable API What are my options here? What are my, euh, options here?
  92. 92. class Algorithm extends SplEnum! {! ! const __default = self::BCRYPT;! ! ! const BCRYPT = 1;! } Use an enum?
  93. 93. [! ! 'salt' => '...'! ! 'cost' => 10! ] Options for bcrypt hashing
  94. 94. class Bcrypt implements HashingStrategy! {! ! /**! * @param integer $cost! * @param string|null $salt! */! ! public function __construct($cost, $salt = null) {! ! ! ...! ! }! } Inject a strategy
  95. 95. function password_hash(! ! $password, ! ! HashingStrategy $hashingStrategy! ); Inject a strategy More explicit and... discoverable!
  96. 96. Discoverability of an API Assumes knowledge of all class, interface and function names in a project Assumes ability to inspect parent classes and implemented interfaces of any class Assumes ability to inspect function parameters using reflection (Assumes basic knowledge of English)
  97. 97. // I've got this password! $password = ...;! // I want to hash it...! // 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?
  98. 98. /**! * @param array $options! */! function some_function(array $options);! ! /**! * @param integer $type! */! function some_other_function($type);! ! /**! * @param object $command! */! function handle($command); Undiscoverable APIs
  99. 99. “Any kind of API should be maximally discoverable”
  100. 100. “Everything should be an object” A class is a type
  101. 101. “Define lots of interfaces” An interface defines the public API of functions
  102. 102. <?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"
  103. 103. In summary Objects are just like applications Apply the same rules to them
  104. 104. Think about Stable dependencies Duplication of facts, not knowledge Immutability over mutability No leakage of implementation details Everything should be maximally discoverable
  105. 105. Computer: http://commons.wikimedia.org/wiki/ File:Ordinateur_table_1990.svg Nameplate: http://pixabay.com/p-46212/

×