Date post: | 03-Aug-2015 |
Category: |
Software |
Upload: | matthiasnoback |
View: | 929 times |
Download: | 5 times |
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 with frameworks and libraries
!!!
How do you start a new project?
Pick a framework
Install a skeleton project
Remove demo stuff
Auto-generate a CRUD controller
Auto-generate an entity
Done
"It's a Symfony project!"
That's actually outside inThe boring stuff
The interesting stuff
Slow tests
DB
Browser
Message queue
Key-value
Frameworks taught us 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
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
why?
why?
why?
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
InconsistenciesGET /balance/ { "currency": "USD", "amount" 20 }
PUT /balance/ { "currency": "EUR", } !
Lack of maintainability
It's hard to find out what happens where
UserController"ProductController"CommentController"CategoryController"AccountController"ProfileController"..."
Web controllers are not the only actions
In summary
Coupling with a framework
Coupling with 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
Terminal
Database
Messaging
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
What crosses layer boundaries?
Message
MessagessomeFunctionCall( $arguments, $prepared, $for, $the, $receiver );
$command = new TypeOfMessage( $values, $for, $this, $message ); handle($command);
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
REST endpoints A WSDL file for a SOAP API
Ports use protocols for communication
Each port has a language of its own
Web (HTTP)
CLI (SAPI, STDIN, etc.)
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
An example
Plain HTTP message
Request object
POST /users/ HTTP/1.1 Host: phpconference.nl !name=Matthias&[email protected]
Command
$command = new RegisterUser( $request->get('name'), $request->get('email') );
Command
$command = new RegisterUser( $request->get('name'), $request->get('email') );
Expresses intention
Implies changeStand-alone
Only the message
class RegisterUserHandler { public function handle(RegisterUser $command) { $user = User::register( $command->name(), $command->email() ); $this->userRepository->add($user); } }
CommandCommand handler
CommandCommand handler A
Command bus
Command handler B
Command handler C
Change
New entity (User)
Calculate changes (UoW)
$user = User::register( $command->name(), $command->email() ); $this->userRepository ->add($user);
Insert query (SQL)
INSERT INTO users SET name='Matthias', email='[email protected]';
SQL query
EntityManager
UnitOfWork
QueryBuilder
Persi
stenc
e port
Prepare
for p
ersist
ence
Repository
Value object
Entity
Messaging (AMQP)
Persistence (MySQL)
UserRepository (uses MySQL)
RegisterUserHandler
MySQL- UserRepository
RegisterUserHandler
Dependency inversion
UserRepository interface
MySQL- UserRepository
RegisterUserHandler
UserRepository interface
InMemory- UserRepository
Speedy alternative
"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
TDDBDD
CQRS
QUESTIONS? FEEDBACK?joind.in/14238