Hexagonal architecture - message-oriented software design (Symfony Live Berlin 2015)

matthiasnoback 2,601 views 76 slides Oct 16, 2015
Slide 1
Slide 1 of 76
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69
Slide 70
70
Slide 71
71
Slide 72
72
Slide 73
73
Slide 74
74
Slide 75
75
Slide 76
76

About This Presentation

Commands, events, queries - different types of messages that travel through your application. Some originate from the web, some from the command-line. Your application sends some of them to a database, or a message queue. What is the ideal infrastructure for an application to support this on-going s...


Slide Content

HEXAGONAL ARCHITECTURE
Message oriented software design
By Matthias Noback

ARCHITECTURE
What's the problem?

Nice app

Sad app

Your brain can't handle it
M V C ?

Coupling to frameworks
and libraries

How do you start a new
project?
Pick a framework
Install a skeleton project
Remove demo stu"!
Auto-generate entities
Auto-generate CRUD controllers
Done
"It's a Symfony project!"

That's actually outside in
The boring stuff
The interesting stuff
Symfony
Doctrine
RabbitMQ
Redis
Angular

Slow tests
DB
Browser
Message
queue
Key-
value
Filesystem

Why do frameworks not solve this for us?
Because they can't ;)

Frameworks are about
encapsulation

Low-level API
$requestContent = file_get_contents('php://input');
$contentType = $_SERVER['CONTENT_TYPE'];
if ($contentType === 'application/json') {
$data = json_decode($requestContent, true);
} elseif ($contentType === 'application/xml') {
$xml = simplexml_load_string ($requestContent);
...
}

Nicely hides the details
$data = $serializer->deserialize(
$request->getContent(),
$request->getContentType()
);

Low-level API
$stmt = $db->prepare(
'SELECT * FROM Patient p WHERE p.anonymous = ?'
);
$stmt->bindValue(1, true);
$stmt->execute();
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
$patient = Patient::reconstituteFromArray ($result);

Hides a lot of details
$patient = $repository->createQueryBuilder('p')
->where('p.anonymous = true' )
->getQuery()
->getResult();

What about abstraction?

$patient = $repository->createQueryBuilder('p')
->where('p.anonymous = true' )
->getQuery()
->getResult();
Concrete
Concrete
Concrete

$patients = $repository->anonymousPatients();
Abstract
Nice
DIY

Coupling to the delivery
mechanism

public function registerPatientAction (Request $request)
{
$patient = new Patient();
$form = $this->createForm(new RegisterPatientForm(), $patient);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($patient);
$em->flush();
return $this->redirect($this->generateUrl('patient_list'));
}
return array(
'form' => $form->createView()
);
}
Request and Form are web-specific
EntityManager is ORM, i.e.
relational DB-specific

Reusability: impossible

Some
functionality
The web
The CLI

Some
functionality
Run it

Lack of intention-revealing code

data
data
data

public function updateAction(Request $request)
{
$patient = new Patient();
$form = $this->createForm(new PatientType(), $patient);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($patient);
$em->flush();
return $this->redirect($this->generateUrl('patient_list'));
}
return array(
'form' => $form->createView()
);
}
from the HTTP request
copied into an entity
then stored in the database
What exactly
changed?!
And... why?

R.A.D.
Rapid Application Development
B.A.D.
B.A.D. Application Development

In summary
Coupling to a framework
Coupling to a delivery mechanism (e.g. the web)
Slow tests
Lack of intention in the code

THE ESSENCE
of your application

The essence
Other things

The "heart"?

"The heart of software is its ability to solve
domain-related problems for its users.
–Eric Evans, Domain Driven Design
All other features, vital though they may be,
support this basic purpose."

What's essential?
Domain model
Interaction with it
Use cases

What's not essential?

“The database is an implementation detail”
–Cool software architect

The core doesn't need to
know about it

What about interaction?

The core doesn't need to
know about it

Infrastructure
The world outside
Web browser
Terminal
Database
Messaging
Filesystem
(E)mail

Mmm... layers Layers
allow you to
separate
Layers
allow you to
allocate
Layers
have
boundaries
Rules
for crossing

Rules about
communication
Actually: rules about dependencies

The dependency rule
–Robert Martin, Screaming Architecture

What crosses layer
boundaries?
Message

Messages
someFunctionCall(
$arguments,
$prepared,
$for,
$the,
$receiver
);
$message = new TypeOfMessage(
$some,
$relevant,
$arguments
);
handle($message);

What about the application
boundary?
The app
Message
The world outside

How does an app allow
incoming messages at all?
By exposing input ports
Routes
Console commands
A WSDL file for a SOAP API

Ports use protocols for
communication
Each port has a language of its own

Web (HTTP)

Messaging (AMQP)

HTTP
Request
Form
Request
Controller
Entity
Value object
Web port
Translate the request
Repository

Adapters
The translators are called: adapters

"Ports and adapters"
Ports: allow for communication to happen
Adapters: translate messages from the world outside
== Hexagonal architecture
Alistair Cockburn

An example
Plain HTTP
message
$_POST,
$_GET,
$_SERVER,
Request
POST /patients/ HTTP/1.1
Host: hospital.com
name=Matthias&email=matthiasn
[email protected]
Command
$command = new RegisterPatient(
$request->get('name'),
$request->get('email')
);

Command
$command = new RegisterPatient(
$request->get('name'),
$request->get('email')
);
Expresses
intention
Implies
change
Independent
of delivery
mechanism
Only the
message

class RegisterPatientHandler
{
public function handle(RegisterPatient $command)
{
$patient = Patient::register(
$command->name(),
$command->email()
);
$this->patientRepository-> add($patient);
}
}
Command
Command
handler

Command
Command
handler A
Command
bus
Command
handler B
Command
handler C

HTTP
Request
Form
Request
Controller
Patient
(entity)
Web port
PatientRepository
RegisterPatient-
Handler
RegisterPatient
(command)
Infrastructure
Application
Domain

Change
New entity
(Patient)
Entity-
Manager
UnitOf-
Work
$patient = Patient::register(
$command->name(),
$command->email()
);
$this->patientRepository
->add($patient);
Insert
query (SQL)
INSERT INTO patients SET
name='Matthias',
email='matthiasnoback@gmai
l.com';

SQL query
EntityManager
UnitOfWork
QueryBuilder
Persistence port
Prepare for persistence
PatientRepository
Core
Infrastructure

Messaging (AMQP)
Persistence (MySQL)

What often goes wrong:
we violate boundary rules...

EntityManager
UnitOfWork
QueryBuilder
PatientRepository
Core
Infrastructure

RegisterPatient-
Handler
Domain
Infrastructure
Application
Domain
PatientRepository
(uses MySQL)
EntityManager
UnitOfWork
QueryBuilder

PatientRepository
(uses MySQL)
Domain
Infrastructure
Application
Domain
RegisterPatient-
Handler

Domain
PatientRepository
(interface)
Dependency
inversion
PatientRepository
(uses MySQL)
RegisterPatient-
Handler

Domain
InMemory-
PatientRepository
Speedy
alternative
RegisterPatient-
Handler
PatientRepository
(uses MySQL)
PatientRepository
(interface)

"A good software architecture allows decisions [...]
to be deferred and delayed."
–Robert Martin, Screaming Architecture

IN CONCLUSION
what did we get from all of this?

Separation of concerns
Core
Infrastructure

Command
Command
Command
handler
Command
handler
Stand-alone use cases
Command
Command
handler
Intention-
revealing
Reusable

Infrastructure stand-ins
Regular
implementation
Interface
Stand-in, fast
implementation

This is all very much
supportive of...
See also: Modelling by Example
DDD
TDD
BDD
CQRS

QUESTIONS?
joind.in/15075
FEEDBACK?