Hexagonal architecture - message-oriented software design (PHP Barcelona 2015)
-
Upload
matthiasnoback -
Category
Software
-
view
1.155 -
download
2
Transcript of Hexagonal architecture - message-oriented software design (PHP Barcelona 2015)
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
Symfony
Doctrine
RabbitMQ
Redis
Angular
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();
$patient = $repository->createQueryBuilder('p') ->where('p.anonymous = true') ->getQuery() ->getResult();
Concrete
Concrete
Concrete
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
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?
In summary
Coupling to a framework
Coupling to a delivery mechanism (e.g. the web)
Slow tests
Lack of intention in the code
"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."
Mmm... layers Layers allow you to separate stuff
Layers allow you to
allocate things
Layers have boundaries
Rules for crossing them
The dependency rule
–Robert Martin, Screaming Architecture
MessagessomeFunctionCall( $arguments, $prepared, $for, $the, $receiver );
$message = new TypeOfMessage( $some, $relevant, $arguments ); handle($message);
How does an app allow incoming messages at all?
By exposing input ports
Routes Console commands
A WSDL file for a SOAP API
HTTP Request
Form
Request
Controller
Entity
Value object
Web p
ort
Tran
slate
the re
quest
Repository
"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
RegisterPatient- Handler
Domain
Infrastr
ucture
Applicatio
n
Domain
PatientRepository (uses MySQL)
EntityManager
UnitOfWork
QueryBuilder
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
CommandCommand
Command handler
Command handler
Stand-alone use cases
Command
Command handler
Intention-revealing
Reusable
This is all very much supportive of...
See also: Modelling by Example
DDD
TDD
BDD
CQRS