the quest for global design principles
TRANSCRIPT
The Quest for
Global Design
Principles
Matthias Noback Zürich, April 20th, 2015
Stability
Change
Stability versus change
Backwards compatibility of APIs
Semantic versioning
Status or error codes
XML schemas
Filesystem abstraction
For internal APIs
only!
The key is in the concept of communication
ReceiverSender
What's being communicated?
Message
Type ("BookHotel")
Value ("Casa Heinrich", "19-04-2015", "21-04-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 fortransport)
Receiver
Deconstruct the message
(Unwrap)
Translate
Construct
Global design principles can be
discovered
if we recognise the fact that communication between objects and applications are (more or less) equal
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
class AccountService!{!! public function deposit($accountId, Money $amount) ! {!! ! ...!! }!}
Opportunity for sending a message
Inter-object communication
$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
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, ...
PUT /accounts/123/deposit/ HTTP/1.1!Host: localhost!!{!! "currency": "EUR",!! "amount": 20000!}
Inter-application communication
Opportunity for sending a message
$uri = sprintf('/accounts/%s/deposit/', $userId);!!$data = json_encode([!! 'amount' => 20000,!! 'currency' => 'EUR'!]);!!$httpClient->createRequest('PUT', $data);
Translation
Prepare the message
Send the message
/** @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
Global design principles
Should be applicable to both inter-object and inter-application communication
Part IStability
III Implementation
II Information
Sender Receiver
Communication
Dependency relation!
Unidirectional!
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 dependents
Independent
What makes something stable?
2. It depends on nothing
It is not affected by changes in anything
Stable Unstable
Again: What is safer?
Unstable Stable
OR
–The Stable dependencies principle
Depend in the direction of stability
Stable Unstable
What to do when this happens?
Depending on an unstable thing ruins your stability
Also stable
Half stableStable thing
Unstable
Depend on something that you own
–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
Your class will be not be sensitive for external changes
Stable thing External system (unstable)
Communication between systems
Mediator
Stable thing
External system
Slightly better
WorkerApplication
External system
Communication between systems
Message queue
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
Part IIInformation
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) }} cm.</p>!
Calculating the width of a nameplate
Knowledge
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
<nameplate>!! <name>Liip AG</name>!</nameplate>
Serialized Nameplate object
// 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
<nameplate>!! <name>Liip AG</name>!! <width>84</width>!</nameplate>
Deduplication
Knowledge is in one place, facts can be everywhere
$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 representation
within a system.”
Even across systems!
"Don't repeat yourself"
Doesn't mean you can't repeat data
It means you can't have knowledge in multiple locations
Some more considerations
Who's responsible for keeping that knowledge?
?Nameplate order system
Nameplate machine
Nameplate order system
Nameplate machine
How do these machines communicate?
GetWidthOfNameplate WidthOfNameplate
CreateNameplate
NameplateOrderPlaced Reactive/event driven
Mutability
What's the difference between...
class Money!{!! private $amount;!! private $currency;!!! public function setAmount($amount) {!! ! $this->amount = $amount;!! }!!! public function getAmount() {!! ! return $this->amount;!! }!!! ...!}
... and this
class Money!{!! public $amount;!! public $currency;!}
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');
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
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!)
Consistent data!
$savings = new Money(1000, 'USD');!// we already have consistent data!!// we can't change anything anymore
Immutable data!
Using immutable values
Prevents bugs
Prevents invalid state
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?
Large object graphs<user>!! <first-name/>!! <last-name/> !! <mobile-phone-number/> !! <email-address/> !! <homepage/> !! <orders>!! ! <order/>!! ! ...!! </orders>!! <tickets>!! ! <ticket/>!! ! ...!! </tickets>!! <payments>!! ! <payment/>!! ! ...!! </payments>!</user>
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();!}
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
Serializer & Doctrine ORM
$entity = $this!! ->get('jms_serializer')!! ->deserialize(!! ! $request->getContent(), '...', 'xml')!! );!!$em = $this->getDoctrine()->getManager();!$em->persist($entity );!$em->flush();!
Commands and events
Register ConfirmRegistration
SendMessage
RegistrationConfirmed
MessageSent
UserRegisteredApplication
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.
Part IIIImplementation
I StabilityII Information
IV Conclusion
Implementation
Leaking implementation details
class Person!{!! /**!! * @return ArrayCollection!! */!! public function getPhoneNumbers() {!! ! return $this->phoneNumbers;!! }!}
Implementation leakage
class Person!{!! /**!! * @return PhoneNumber[]!! */!! public function getPhoneNumbers() {!! ! return $this->phoneNumbers->toArray();!! }!}
Hiding implementation
class Person!{!! public function addPhoneNumber(PhoneNumber $phoneNumber) {!! ! $phoneNumber->setPerson($person);!!! ! $this->phoneNumbers->add($phoneNumber);!! }!}
Model pollution
class PhoneNumber!{!! private $countryCode;!! private $areaCode;!! private $lineNumber;!! private $person;!!! public function setPerson(Person $person) {!! ! $this->person = $person;!! }!}
Model pollution
Don't make your model suffer from the database backend
Person id
PersonPhoneNumber person_id
phoneNumber_id !
PhoneNumber id
countryCode areaCode
lineNumber Entity
Value
class Person!{!! public function addPhoneNumber(PhoneNumber $phoneNumber) {!! ! $this->phoneNumbers->add(!! ! ! new PersonPhoneNumber(!! ! ! ! $this,!! ! ! ! $phoneNumber!! ! ! )!! ! );!! }!}
Hide ORM requirements
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
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"?
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"
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
Limited by implementation details
<?xml version="1.0" encoding="UTF-8"?>!<ticket>! <id type="integer">10</id>! ...! <status>New</status>! ...
Assembla API
We do "strcasecmp()" ;)
<ticket>! ...! <priority type="integer">1</priority>! ...
Assembla API
We do "switch ($priority)"
<ticket>! ...! <priority key="highest">! <label>Highest</label>! </priority>! ...
Why not...
...! <custom-fields>! <Team-yell>We love X-M-L!</Team-yell>! </custom-fields>!</ticket>
Assembla API
We do "SELECT * FROM custom_fields"
...! <team-yell>We love X-M-L!</team-yell>!</ticket>
Why not...
<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"
<story>! ...! <total-estimate type="float">0.0</total-estimate>! ...
Why not...
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
/**! * @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?
class Algorithm extends \SplEnum!{!! const __default = self::BCRYPT;!!! const BCRYPT = 1;!}
Use an enum?
[!! 'salt' => '...'!! 'cost' => 10!]
Options for bcrypt hashing
class Bcrypt implements HashingStrategy!{!! /**! * @param integer $cost! * @param string|null $salt! */!! public function __construct($cost, $salt = null) {!! ! ...!! }!}
Inject a strategy
function password_hash(!! $password, !! HashingStrategy $hashingStrategy!);
Inject a strategy
More explicit and... discoverable!
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)
// 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 discoveryWho is talking?
How stupid are they?How do you find out a valid range?
/**! * @param array $options! */!function some_function(array $options);!!/**! * @param integer $type! */!function some_other_function($type);!!/**! * @param object $command! */!function handle($command);
Undiscoverable APIs
“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 rel="index" href="/api/reporters/" />! </reporter>! ...
HATEOAS
Links are a way to explain an "object"
In summary
Objects are just like applications
Apply the same rules to them
Think about
Stable dependencies
Duplication of facts, not knowledge
Immutability over mutability
No leakage of implementation details
Everything should be maximally discoverable
Computer: http://commons.wikimedia.org/wiki/File:Ordinateur_table_1990.svg
Nameplate: http://pixabay.com/p-46212/