the quest for global design principles

106
The Quest for Global Design Principles Matthias Noback Zürich, April 20 th , 2015

Upload: matthiasnoback

Post on 20-Aug-2015

3.755 views

Category:

Design


1 download

TRANSCRIPT

Page 1: The Quest for Global Design Principles

The Quest for

Global Design

Principles

Matthias Noback Zürich, April 20th, 2015

Page 2: The Quest for Global Design Principles

Stability

Change

Page 3: The Quest for Global Design Principles

Stability versus change

Backwards compatibility of APIs

Semantic versioning

Status or error codes

XML schemas

Filesystem abstraction

Page 4: The Quest for Global Design Principles

For internal APIs

only!

Page 5: The Quest for Global Design Principles
Page 6: The Quest for Global Design Principles

The key is in the concept of communication

ReceiverSender

Page 7: The Quest for Global Design Principles

What's being communicated?

Message

Type ("BookHotel")

Value ("Casa Heinrich", "19-04-2015", "21-04-2015")

Page 8: The Quest for Global Design Principles

Message flavours

Command Event

Imperative "BookHotel"

Informational "HotelWasBooked"

Page 9: The Quest for Global Design Principles

Message flavours

Query Document

Interrogatory "GetRecentBookings"

Neutral "BookingsCollection"

Page 10: The Quest for Global Design Principles

Sender

Construct the message

Message

Translate

Construct

(Prepare fortransport)

Page 11: The Quest for Global Design Principles

Receiver

Deconstruct the message

(Unwrap)

Translate

Construct

Page 12: The Quest for Global Design Principles

Global design principles can be

discovered

if we recognise the fact that communication between objects and applications are (more or less) equal

Page 13: The Quest for Global Design Principles

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

Page 14: The Quest for Global Design Principles

class AccountService!{!! public function deposit($accountId, Money $amount) ! {!! ! ...!! }!}

Opportunity for sending a message

Inter-object communication

Page 15: The Quest for Global Design Principles

$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

Page 16: The Quest for Global Design Principles

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, ...

Page 17: The Quest for Global Design Principles

PUT /accounts/123/deposit/ HTTP/1.1!Host: localhost!!{!! "currency": "EUR",!! "amount": 20000!}

Inter-application communication

Opportunity for sending a message

Page 18: The Quest for Global Design Principles

$uri = sprintf('/accounts/%s/deposit/', $userId);!!$data = json_encode([!! 'amount' => 20000,!! 'currency' => 'EUR'!]);!!$httpClient->createRequest('PUT', $data);

Translation

Prepare the message

Send the message

Page 19: The Quest for Global Design Principles

/** @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

Page 20: The Quest for Global Design Principles

Global design principles

Should be applicable to both inter-object and inter-application communication

Page 21: The Quest for Global Design Principles

Part IStability

III Implementation

II Information

Page 22: The Quest for Global Design Principles

Sender Receiver

Communication

Dependency relation!

Unidirectional!

Page 23: The Quest for Global Design Principles

Stable Unstable

What is safer?

Unstable Stable

OR

Page 24: The Quest for Global Design Principles

To be stable"Steady in position or balance;

firm"

Page 25: The Quest for Global Design Principles

Irresponsible

What makes something unstable?

1. Nothing depends on it

It doesn't need to stay the same for anyone

Page 26: The Quest for Global Design Principles

Dependent

What makes something unstable?

2. It depends on many things

Any of the dependencies could change at any time

Page 27: The Quest for Global Design Principles

Stability is not a quality of one thing

It emerges from the environment of that thing

Page 28: The Quest for Global Design Principles

Responsible

What makes something stable?

1. Many things depend on it

It has to stay the same, because change would break dependents

Page 29: The Quest for Global Design Principles

Independent

What makes something stable?

2. It depends on nothing

It is not affected by changes in anything

Page 30: The Quest for Global Design Principles

Stable Unstable

Again: What is safer?

Unstable Stable

OR

Page 31: The Quest for Global Design Principles

–The Stable dependencies principle

Depend in the direction of stability

Page 32: The Quest for Global Design Principles

Stable Unstable

What to do when this happens?

Depending on an unstable thing ruins your stability

Page 33: The Quest for Global Design Principles

Also stable

Half stableStable thing

Unstable

Depend on something that you own

Page 34: The Quest for Global Design Principles

–The Dependency inversion principle

Always depend on abstractions, not on concretions

Page 35: The Quest for Global Design Principles

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

Page 36: The Quest for Global Design Principles

Stable thing External system (unstable)

Communication between systems

Page 37: The Quest for Global Design Principles

Mediator

Stable thing

External system

Slightly better

Page 38: The Quest for Global Design Principles

WorkerApplication

External system

Communication between systems

Message queue

Page 39: The Quest for Global Design Principles

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

Page 40: The Quest for Global Design Principles

Part IIInformation

I Stability

III Implementation

Page 41: The Quest for Global Design Principles

Knowledge, data, duplication

Page 42: The Quest for Global Design Principles

Nameplate order system

Nameplate machine

Business automation for creating nameplates

Page 43: The Quest for Global Design Principles

<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

Page 44: The Quest for Global Design Principles

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

Page 45: The Quest for Global Design Principles

<nameplate>!! <name>Liip AG</name>!</nameplate>

Serialized Nameplate object

Page 46: The Quest for Global Design Principles

// 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

Page 47: The Quest for Global Design Principles

<nameplate>!! <name>Liip AG</name>!! <width>84</width>!</nameplate>

Deduplication

Knowledge is in one place, facts can be everywhere

Page 48: The Quest for Global Design Principles

$nameplate = deserialize($xml);!!$width = $nameplate['width'];

Accepting messages

No duplicate knowledge anymore

Page 49: The Quest for Global Design Principles

–The Don't repeat yourself principle

“Every piece of knowledge must have a single, unambiguous, authoritative representation

within a system.”

Even across systems!

Page 50: The Quest for Global Design Principles

"Don't repeat yourself"

Doesn't mean you can't repeat data

It means you can't have knowledge in multiple locations

Page 51: The Quest for Global Design Principles

Some more considerations

Page 52: The Quest for Global Design Principles

Who's responsible for keeping that knowledge?

?Nameplate order system

Nameplate machine

Page 53: The Quest for Global Design Principles

Nameplate order system

Nameplate machine

How do these machines communicate?

GetWidthOfNameplate WidthOfNameplate

CreateNameplate

NameplateOrderPlaced Reactive/event driven

Page 54: The Quest for Global Design Principles

Mutability

Page 55: The Quest for Global Design Principles

What's the difference between...

class Money!{!! private $amount;!! private $currency;!!! public function setAmount($amount) {!! ! $this->amount = $amount;!! }!!! public function getAmount() {!! ! return $this->amount;!! }!!! ...!}

Page 56: The Quest for Global Design Principles

... and this

class Money!{!! public $amount;!! public $currency;!}

Page 57: The Quest for Global Design Principles

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');

Page 58: The Quest for Global Design Principles

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

Page 59: The Quest for Global Design Principles

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!)

Page 60: The Quest for Global Design Principles

Consistent data!

$savings = new Money(1000, 'USD');!// we already have consistent data!!// we can't change anything anymore

Immutable data!

Page 61: The Quest for Global Design Principles

Using immutable values

Prevents bugs

Prevents invalid state

Page 62: The Quest for Global Design Principles

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?

Page 63: The Quest for Global Design Principles

Large object graphs<user>!! <first-name/>!! <last-name/> !! <mobile-phone-number/> !! <email-address/> !! <homepage/> !! <orders>!! ! <order/>!! ! ...!! </orders>!! <tickets>!! ! <ticket/>!! ! ...!! </tickets>!! <payments>!! ! <payment/>!! ! ...!! </payments>!</user>

Page 64: The Quest for Global Design Principles

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();!}

Page 65: The Quest for Global Design Principles

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

Page 66: The Quest for Global Design Principles

Serializer & Doctrine ORM

$entity = $this!! ->get('jms_serializer')!! ->deserialize(!! ! $request->getContent(), '...', 'xml')!! );!!$em = $this->getDoctrine()->getManager();!$em->persist($entity );!$em->flush();!

Page 67: The Quest for Global Design Principles

Commands and events

Register ConfirmRegistration

SendMessage

RegistrationConfirmed

MessageSent

UserRegisteredApplication

Page 68: The Quest for Global Design Principles

phparchitecturetour.com

TOMORROW!

in Zürich

Page 69: The Quest for Global Design Principles

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.

Page 70: The Quest for Global Design Principles

Part IIIImplementation

I StabilityII Information

IV Conclusion

Page 71: The Quest for Global Design Principles

Implementation

Page 72: The Quest for Global Design Principles

Leaking implementation details

Page 73: The Quest for Global Design Principles

class Person!{!! /**!! * @return ArrayCollection!! */!! public function getPhoneNumbers() {!! ! return $this->phoneNumbers;!! }!}

Implementation leakage

Page 74: The Quest for Global Design Principles

class Person!{!! /**!! * @return PhoneNumber[]!! */!! public function getPhoneNumbers() {!! ! return $this->phoneNumbers->toArray();!! }!}

Hiding implementation

Page 75: The Quest for Global Design Principles

class Person!{!! public function addPhoneNumber(PhoneNumber $phoneNumber) {!! ! $phoneNumber->setPerson($person);!!! ! $this->phoneNumbers->add($phoneNumber);!! }!}

Model pollution

Page 76: The Quest for Global Design Principles

class PhoneNumber!{!! private $countryCode;!! private $areaCode;!! private $lineNumber;!! private $person;!!! public function setPerson(Person $person) {!! ! $this->person = $person;!! }!}

Model pollution

Page 77: The Quest for Global Design Principles

Don't make your model suffer from the database backend

Person id

PersonPhoneNumber person_id

phoneNumber_id !

PhoneNumber id

countryCode areaCode

lineNumber Entity

Value

Page 78: The Quest for Global Design Principles

class Person!{!! public function addPhoneNumber(PhoneNumber $phoneNumber) {!! ! $this->phoneNumbers->add(!! ! ! new PersonPhoneNumber(!! ! ! ! $this,!! ! ! ! $phoneNumber!! ! ! )!! ! );!! }!}

Hide ORM requirements

Page 79: The Quest for Global Design Principles

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

Page 80: The Quest for Global Design Principles

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"?

Page 81: The Quest for Global Design Principles

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"

Page 82: The Quest for Global Design Principles

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

Page 83: The Quest for Global Design Principles

Limited by implementation details

Page 84: The Quest for Global Design Principles

<?xml version="1.0" encoding="UTF-8"?>!<ticket>! <id type="integer">10</id>! ...! <status>New</status>! ...

Assembla API

We do "strcasecmp()" ;)

Page 85: The Quest for Global Design Principles

<ticket>! ...! <priority type="integer">1</priority>! ...

Assembla API

We do "switch ($priority)"

Page 86: The Quest for Global Design Principles

<ticket>! ...! <priority key="highest">! <label>Highest</label>! </priority>! ...

Why not...

Page 87: The Quest for Global Design Principles

...! <custom-fields>! <Team-yell>We love X-M-L!</Team-yell>! </custom-fields>!</ticket>

Assembla API

We do "SELECT * FROM custom_fields"

Page 88: The Quest for Global Design Principles

...! <team-yell>We love X-M-L!</team-yell>!</ticket>

Why not...

Page 89: The Quest for Global Design Principles

<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"

Page 90: The Quest for Global Design Principles

<story>! ...! <total-estimate type="float">0.0</total-estimate>! ...

Why not...

Page 91: The Quest for Global Design Principles

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

Page 92: The Quest for Global Design Principles

/**! * @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?

Page 93: The Quest for Global Design Principles

class Algorithm extends \SplEnum!{!! const __default = self::BCRYPT;!!! const BCRYPT = 1;!}

Use an enum?

Page 94: The Quest for Global Design Principles

[!! 'salt' => '...'!! 'cost' => 10!]

Options for bcrypt hashing

Page 95: The Quest for Global Design Principles

class Bcrypt implements HashingStrategy!{!! /**! * @param integer $cost! * @param string|null $salt! */!! public function __construct($cost, $salt = null) {!! ! ...!! }!}

Inject a strategy

Page 96: The Quest for Global Design Principles

function password_hash(!! $password, !! HashingStrategy $hashingStrategy!);

Inject a strategy

More explicit and... discoverable!

Page 97: The Quest for Global Design Principles

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)

Page 98: The Quest for Global Design Principles

// 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?

Page 99: The Quest for Global Design Principles

/**! * @param array $options! */!function some_function(array $options);!!/**! * @param integer $type! */!function some_other_function($type);!!/**! * @param object $command! */!function handle($command);

Undiscoverable APIs

Page 100: The Quest for Global Design Principles

“Any kind of API should be maximally discoverable”

Page 101: The Quest for Global Design Principles

“Everything should be an object”

A class is a type

Page 102: The Quest for Global Design Principles

“Define lots of interfaces”

An interface defines the public API of functions

Page 103: The Quest for Global Design Principles

<?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"

Page 104: The Quest for Global Design Principles

In summary

Objects are just like applications

Apply the same rules to them

Page 105: The Quest for Global Design Principles

Think about

Stable dependencies

Duplication of facts, not knowledge

Immutability over mutability

No leakage of implementation details

Everything should be maximally discoverable

Page 106: The Quest for Global Design Principles

Computer: http://commons.wikimedia.org/wiki/File:Ordinateur_table_1990.svg

Nameplate: http://pixabay.com/p-46212/