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.
Global
Design
Principles
The Quest for

Matthias Noback
Stability
Change
Stability versus change
• Backwards compatibility of APIs

• Semantic versioning

• HTTP status codes

• XML schemas
For internal APIs
only!
The key is in the concept of
communication
ReceiverSender
What's being communicated?
Message
Type ("BookHotel")

Value (“Golden Tulip", “15-10-2015", “16-10-2015”)
Message flavours
Command Event
Imperative
"BookHotel"
Informational
"HotelWasBooked"
Message flavours
Query Document
Interrogatory
"GetRecentBookings"
Neutral
"BookingsCollection"
Sender
Construct the message
Message
Translate 

Construct

(Prepare for

transport)
Receiver
Deconstruct the message
(Unwrap)

Translate

Construct
Global design
principles can be
discovered
if we recognise the fact that
communication between objects and
applications ar...
Communication between
objects
• Calling a function is like sending a message

• The function and its parameters are the me...
class AccountService
{
public function deposit($accountId, Money $amount)
{
...
}
}
Opportunity for
sending a
message
Inte...
$money = new Money(20000, 'EUR');
$userId = 123;
$accountService->deposit(
$userId,
$money
);
Translation
The sender prepa...
Communication between
applications
• An HTTP request is a message

• The HTTP method and URI are the type of the
message

...
PUT /accounts/deposit/ HTTP/1.1
Host: localhost
{
"accountId": "123",
"currency": "EUR",
"amount": 20000
}
Inter-applicati...
$uri ='/accounts/deposit/';
$data = json_encode([
'accountId' => 123
'currency' => 'EUR',
'amount' => 20000
]);
$httpClien...
/** @Route("/accounts/deposit") */
function depositAction(Request $request)
{
$request = json_decode($request->getContent(...
Global design
principles
Should be applicable to both inter-object and inter-application communication
Part I
Stability
III Implementation
II Information
Sender Receiver
Communication
Dependency
relation!
Stable Unstable
What is safer?
Unstable Stable
OR
To be stable
"Steady in position or balance;
firm"
Irresponsible
What makes something
unstable?
1. Nothing depends on it
It doesn't need to stay the same for
anyone
Dependent
What makes something
unstable?
2. It depends on many things
Any of the dependencies could change at
any time
Stability is not a
quality of one thing
It emerges from the environment
of that thing
Responsible
What makes something
stable?
1. Many things depend on it
It has to stay the same, because change would break d...
Independent
What makes something
stable?
2. It depends on nothing
It is not affected by changes in
anything
–The Stable dependencies principle
Depend in the
direction of stability
Stable package
Unstable
package
What to do when this
happens?
Stable package
Depend on something
that you own
Unstable
package
Stable package
–The Dependency inversion principle
Always depend on abstractions,
not on concretions
Dependency inversion for
objects
• Depend on an interface instead of a class

• Separate a task from its implementation
Stable thing
Communication
between systems
External
application
(unstable)
Mediator
Stable thing
Slightly better
External
application
(unstable)
ConsumerApplication
Communication
between systems
Message
queue
External
application
(unstable)
Dependency inversion for
systems
• Depend on your own (messaging) system

• Communicate with other applications through a
...
Part II
Information
I Stability
III Implementation
Knowledge, data, duplication
Nameplate order
system
Nameplate
machine
Business automation for
creating nameplates
<h1>You are about to order a nameplate!</h1>
<p>Name on the plate: {{ name }}<br/>
Width of the plate: {{ 12*(name|length)...
class Nameplate
{
private $name;
function __construct($name) {
$this->name = $name;
}
function widthInCm() {
return strlen...
<h1>You are about to order a nameplate!</h1>
<p>Name on the plate: {{ nameplate.name }}<br/>
Width of the plate: {{ namepl...
<nameplate>
<name>Ibuildings</name>
</nameplate>
Serialized Nameplate object
// calculate the width of the nameplate
$nameplate = deserialize($xml);
$name = $nameplate['name'];
$width = strlen($name)...
<nameplate>
<name>Ibuildings</name>
<width>120</width>
</nameplate>
Deduplication
Knowledge is in one place,
facts can be ...
$nameplate = deserialize($xml);
$width = $nameplate['width'];
Accepting messages
No duplicate knowledge
anymore
–The Don't repeat yourself principle
“Every piece of knowledge must
have a single, unambiguous,
authoritative representati...
"Don't repeat yourself"
• Doesn't mean you can't repeat data

• It means you can't have knowledge in multiple
locations
Mutability
What's the difference
between...
class Money
{
private $amount;
private $currency;
public function setAmount($amount) {
$t...
... and this
class Money
{
public $amount;
public $currency;
}
Inconsistent data
$savings = new Money();
$savings->setAmount(1000);
// what's the currency at this point?
$savings->setCu...
Making something better of this
class Money
{
private $amount;
private $currency;
public function __construct($amount, $cu...
Immutability
• Once created, can not be modified

• Can only be replaced
Consistent data!
$savings = new Money(1000, 'USD');
// we already have consistent data
// we can't change anything anymore...
Using immutable values
• Prevents bugs

• Prevents invalid state
What about API messages?
<money>
<amount>1000</amount>
<currency>USD</currency>
</money>
PUT /savings/
<money>
<currency>E...
Large object graphs
<user>
<first-name/>
<last-name/>
<mobile-phone-number/>
<email-address/>
<homepage/>
<orders>
<order/...
Forms & Doctrine ORM
$form = $this->createForm(new MoneyType());
$form->handleRequest($request);
if ($form->isValid()) {
$...
If you allow your users to
change every field at any time
• You end up with inconsistent data

• You loose the why of a ch...
Commands and events
Register ConfirmRegistration
SendMessage
RegistrationConfirmed
MessageSent
UserRegistered
Application
Tomorrow!
True
• Messages should serve actual use cases instead
of patch operations

• After processing them, data should be in a va...
Part III
Implementation
I Stability
II Information
IV Conclusion
Implementation
Leaking implementation
details
class Person
{
/**
* @return PhoneNumber[]
*/
public function getPhoneNumbers() {
return $this->phoneNumbers;
}
}
Initial ...
class Person
{
/**
* @return ArrayCollection
*/
public function getPhoneNumbers() {
return $this->phoneNumbers;
}
}
Implem...
class Person
{
/**
* @return PhoneNumber[]
*/
public function getPhoneNumbers() {
return $this->phoneNumbers->toArray();
}...
class NameplateController
{
function getAction($id) {
$nameplate = $this
->getDoctrine()
->getManager()
->getRepository(Na...
class NameplateRepository
{
function byId($id) {
$nameplate = $this
->findOneBy(['id' => $id]);
if ($nameplate === null) {...
class NameplateController
{
function getAction($id) {
try {
$nameplate = $this
->nameplateRepository
->byId($id);
} catch ...
Plain old OOP
• Encapsulation

• Abstraction
Limited by implementation
details
<ticket>
...
<priority type="integer">1</priority>
...
Assembla API
We do "switch ($priority)"
<ticket>
...
<priority key="highest">
<label>Highest</label>
</priority>
...
Why not...
<ticket>
...
<is-story type="boolean">false</is-story>
<total-estimate type="float">0.0</total-estimate>
...
Assembla API
...
<story>
...
<total-estimate type="float">0.0</total-estimate>
...
Why not...
Design your messages in such
a way that
• You hide your implementation

• Clients won't need to reimplement your applicati...
API discovery
/**
* @param string $password
* @param integer $algo
* @param array $options
*/
function password_hash($password, $algo, a...
class Algorithm extends SplEnum
{
const __default = self::BCRYPT;
const BCRYPT = 1;
}
Allow no mistakes
[
'salt' => '...'
'cost' => 10
]
Options for bcrypt hashing
class Bcrypt implements HashingStrategy
{
/**
* @param integer $cost
* @param string|null $salt
*/
public function __const...
function password_hash(
$password,
HashingStrategy $hashingStrategy
);
Inject a strategy
More explicit and...
discoverable!
Discoverability of an API
• Full Reflection capabilities :)

• Basic knowledge of English
// I've got this password I want to hash…
$password = ...;
// Look, I found a function for this: password_hash()
password_...
/**
* @param array $options
*/
function some_function(array $options);
/**
* @param integer $type
*/
function some_other_f...
Any kind of API should be
maximally discoverable
Everything should be an object
A class is a type
Define lots of interfaces
An interface defines the
public API of functions
<?xml version="1.0" encoding="UTF-8"?>
<ticket>
<reporter>
<id>43</id>
<link rel="self" href="/api/reporters/43" />
<link ...
Summary/Hypothesis
• Objects are just like applications

• Try to apply the same rules to them
Think about
• Stable dependencies

• Duplication of facts, not knowledge

• Immutability over mutability

• No leakage of ...
Questions? Feedback?
joind.in/15060 Thanks!
The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)
Nächste SlideShare
Wird geladen in …5
×

The quest for global design principles (SymfonyLive Berlin 2015)

3.223 Aufrufe

Veröffentlicht am

If you’re a programmer you make design decisions every second. You need to think, and often think hard, about everything. Luckily there are many useful design principles and patterns 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 form the basis of proper object-oriented design. After attending this talk you will be able to make better design decisions by reflecting on the stream of messages that is flowing from object to object, and from application to application.

Veröffentlicht in: Software
  • Als Erste(r) kommentieren

The quest for global design principles (SymfonyLive Berlin 2015)

  1. 1. Global Design Principles The Quest for Matthias Noback
  2. 2. Stability Change
  3. 3. Stability versus change • Backwards compatibility of APIs • Semantic versioning • HTTP status codes • XML schemas
  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 (“Golden Tulip", “15-10-2015", “16-10-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 • 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. 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/deposit/ HTTP/1.1 Host: localhost { "accountId": "123", "currency": "EUR", "amount": 20000 } Inter-application communication Opportunity for sending a message
  17. 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. 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. 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!
  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. –The Stable dependencies principle Depend in the direction of stability
  30. 30. Stable package Unstable package What to do when this happens?
  31. 31. Stable package Depend on something that you own Unstable package Stable package
  32. 32. –The Dependency inversion principle Always depend on abstractions, not on concretions
  33. 33. Dependency inversion for objects • Depend on an interface instead of a class • Separate a task from its implementation
  34. 34. Stable thing Communication between systems External application (unstable)
  35. 35. Mediator Stable thing Slightly better External application (unstable)
  36. 36. ConsumerApplication Communication between systems Message queue External application (unstable)
  37. 37. Dependency inversion for systems • Depend on your own (messaging) system • Communicate with other applications through a message queue
  38. 38. Part II Information I Stability III Implementation
  39. 39. Knowledge, data, duplication
  40. 40. Nameplate order system Nameplate machine Business automation for creating nameplates
  41. 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. 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. 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. 44. <nameplate> <name>Ibuildings</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 = $width * $widthPerCharacterInCm; // configure the nameplate machine ;) Accepting messages Duplication of knowledge
  46. 46. <nameplate> <name>Ibuildings</name> <width>120</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 applications!
  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. Mutability
  51. 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. 52. ... and this class Money { public $amount; public $currency; }
  53. 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. 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. 55. Immutability • Once created, can not be modified • Can only be replaced
  56. 56. Consistent data! $savings = new Money(1000, 'USD'); // we already have consistent data // we can't change anything anymore Immutable data!
  57. 57. Using immutable values • Prevents bugs • Prevents invalid state
  58. 58. What about API messages? <money> <amount>1000</amount> <currency>USD</currency> </money> PUT /savings/ <money> <currency>EUR</currency> </money> POST /savings/
  59. 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. 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. 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. 62. Commands and events Register ConfirmRegistration SendMessage RegistrationConfirmed MessageSent UserRegistered Application
  63. 63. Tomorrow!
  64. 64. True • Messages should serve actual use cases instead of patch operations • After processing them, data should be in a valid state
  65. 65. Part III Implementation I Stability II Information IV Conclusion
  66. 66. Implementation
  67. 67. Leaking implementation details
  68. 68. class Person { /** * @return PhoneNumber[] */ public function getPhoneNumbers() { return $this->phoneNumbers; } } Initial implementation
  69. 69. class Person { /** * @return ArrayCollection */ public function getPhoneNumbers() { return $this->phoneNumbers; } } Implementation leakage (Doctrine)
  70. 70. class Person { /** * @return PhoneNumber[] */ public function getPhoneNumbers() { return $this->phoneNumbers->toArray(); } } Hiding implementation
  71. 71. 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"?
  72. 72. 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"
  73. 73. 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
  74. 74. Plain old OOP • Encapsulation • Abstraction
  75. 75. Limited by implementation details
  76. 76. <ticket> ... <priority type="integer">1</priority> ... Assembla API We do "switch ($priority)"
  77. 77. <ticket> ... <priority key="highest"> <label>Highest</label> </priority> ... Why not...
  78. 78. <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"
  79. 79. <story> ... <total-estimate type="float">0.0</total-estimate> ... Why not...
  80. 80. Design your messages in such a way that • You hide your implementation • Clients won't need to reimplement your application • Clients get the information they need
  81. 81. API discovery
  82. 82. /** * @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?
  83. 83. class Algorithm extends SplEnum { const __default = self::BCRYPT; const BCRYPT = 1; } Allow no mistakes
  84. 84. [ 'salt' => '...' 'cost' => 10 ] Options for bcrypt hashing
  85. 85. class Bcrypt implements HashingStrategy { /** * @param integer $cost * @param string|null $salt */ public function __construct($cost, $salt = null) { ... } } Inject a strategy
  86. 86. function password_hash( $password, HashingStrategy $hashingStrategy ); Inject a strategy More explicit and... discoverable!
  87. 87. Discoverability of an API • Full Reflection capabilities :) • Basic knowledge of English
  88. 88. // 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?
  89. 89. /** * @param array $options */ function some_function(array $options); /** * @param integer $type */ function some_other_function($type); /** * @param object $command */ function handle($command); Undiscoverable APIs
  90. 90. Any kind of API should be maximally discoverable
  91. 91. Everything should be an object A class is a type
  92. 92. Define lots of interfaces An interface defines the public API of functions
  93. 93. <?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"
  94. 94. Summary/Hypothesis • Objects are just like applications • Try to apply the same rules to them
  95. 95. Think about • Stable dependencies • Duplication of facts, not knowledge • Immutability over mutability • No leakage of implementation details • Everything should be maximally discoverable
  96. 96. Questions? Feedback? joind.in/15060 Thanks!

×