Upload
matthiasnoback
View
2.368
Download
0
Embed Size (px)
Citation preview
HEXAGONAL ARCHITECTURE Message oriented software design
By Matthias Noback
ARCHITECTUREWhat'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 stuff
Auto-generate entities
Auto-generate CRUD controllers
Done
"It's a Symfony project!"
That's actually outside inThe boring stuff
The interesting stuff
SymfonyDo
ctrine
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 ESSENCEof 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
TerminalDatabase
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
MessagessomeFunctionCall( $arguments, $prepared, $for, $the, $receiver );
$message = new TypeOfMessage( $some, $relevant, $arguments ); handle($message);
What about the application boundary?
The app
Messag
e
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 p
ort
Tran
slate
the re
quest
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 protected]
Command
$command = new RegisterPatient( $request->get('name'), $request->get('email') );
Command
$command = new RegisterPatient( $request->get('name'), $request->get('email') );
Expresses intention
Implies changeIndependent
of delivery mechanism
Only the message
class RegisterPatientHandler { public function handle(RegisterPatient $command) { $patient = Patient::register( $command->name(), $command->email() ); $this->patientRepository->add($patient); } }
CommandCommand handler
CommandCommand handler A
Command bus
Command handler B
Command handler C
HTTP Request
Form
Request
Controller
Patient (entity)
Web p
ort
PatientRepository
RegisterPatient- Handler
RegisterPatient (command)
Infras
truc
ture
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='[email protected]';
SQL query
EntityManager
UnitOfWork
QueryBuilder
Persi
stenc
e port
Prepare
for p
ersist
ence
PatientRepository
Core
Infrastruc
ture
Messaging (AMQP)
Persistence (MySQL)
What often goes wrong: we violate boundary rules...
EntityManager
UnitOfWork
QueryBuilder
PatientRepository
Core
Infrastruc
ture
RegisterPatient- Handler
Domain
Infrastr
ucture
Applicatio
n
Domain
PatientRepository (uses MySQL)
EntityManager
UnitOfWork
QueryBuilder
PatientRepository (uses MySQL)
Domain
Infrastr
ucture
Applicatio
n
DomainRegisterPatient-
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 CONCLUSIONwhat did we get from all of this?
Separation of concerns
Core
Infrastructure
CommandCommand
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/14978FEEDBACK?