Hexagonal architecture - message-oriented software design

Post on 03-Aug-2015

934 views 5 download

Transcript of Hexagonal architecture - message-oriented software design

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=matthiasnoback@gmail.com

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='matthiasnoback@gmail.com';

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