Hexagonal Architecture

Patrick Jahns

14/03/2017 - #008 /dev/night -

Architecture what?!

I use {{ favourite framework }}!!

How do you start a new project?

  • Select framework
  • Install base/skeleton project
  • Remove demo/initial project files
  • Auto generate controller
  • Auto generate Entities
  • Done

Why talk about Architecture?

class TodoController extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{

    public function addToDoAction(Request $request)
    {
        $todo = new ToDo();
        $form = $this->createForm(new TodoType(), $todo);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
        	
            $em = $this->getDoctrine()->getManager();
            $list = $em->find(TodoList::class, $request->get('listId));
            if(!$list) { throw new NotFoundException(); }
            $todo->setTodoList($list);
            $em->persist($todo);
            $em->flush();
            $this->mailer->sendMessage('A new ToDo has been added');
            return $this->redirectToRoute('todo_show', ['id' => $todo->getId()]);
        }
        return $this->render('todo/new.html.twig', [
            'todo' => $task,
            'form' => $form->createView(),
        ]);
    }
}

Convenience

vs

Maintainability

convenient code

maintainable code

class TodoController extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{

    public function addToDoAction(Request $request)
    {
        $todo = new ToDo();
        $form = $this->createForm(new TodoType(), $todo);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
        	
            $em = $this->getDoctrine()->getManager();
            $list = $em->find(TodoList::class, $request->get('listId));
            if(!$list) { throw new NotFoundException(); }
            $todo->setTodoList($list);
            $em->persist($todo);
            $em->flush();
            $this->mailer->sendMessage('A new ToDo has been added');
            return $this->redirectToRoute('todo_show', ['id' => $todo->getId()]);
        }
        return $this->render('todo/new.html.twig', [
            'todo' => $task,
            'form' => $form->createView(),
        ]);
    }
}

ToDo Application

How can we test the Application?

  • Need full Application/Stack
  • Database
  • Can only test outside-in

SLOW and Difficult

Reusability?

ToDoList

WEB

CLI

REST

class TodoController extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{

    public function addToDoAction(Request $request)
    {
        $todo = new ToDo();
        $form = $this->createForm(new TodoType(), $todo);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
        	
            $em = $this->getDoctrine()->getManager();
            $list = $em->find(TodoList::class, $request->get('listId));
            if(!$list) { throw new NotFoundException(); }
            $todo->setTodoList($list);
            $em->persist($todo);
            $em->flush();
            $this->mailer->sendMessage('A new ToDo has been added');
            return $this->redirectToRoute('todo_show', ['id' => $todo->getId()]);
        }
        return $this->render('todo/new.html.twig', [
            'todo' => $task,
            'form' => $form->createView(),
        ]);
    }
}

ToDo Application

  • Coupling to a framework
  • Coupling to a delivery mechanism
  • Slow tests
  • Lack of Intent in the code

Hexagonal Architecture

Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.

Alistair Cockburn

The Essence of Software

Application

Outside

The heart of software is its ability to solve domain-related problems for its users.

 

All other features, vital though they may be, support this basic purpose

Eric Evans, Domain Driven Design

Outside

Framework?

WEB

CLI

Message

DB

Files

Email

HEART

Domain

Ports 

HTTP Port

DB Port

Rabbit MQ Port

CLI Port

HTTP Port

Entities

Model

Repository

Form

Controller

Request

HTTP
REQUEST

Translate .  message

Ports & Adapters

== hexagonal architecture

Ports

Adapters

allow for communcation to happen

translate message from outside

Communication Flow

=rules for dependencies

?

Entity

Manager

Request

Form

HTTP
REQUEST

Add ToDo Service

Add ToDo

Service

Repository

Entity Manager

Inside

Outside

Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions
 
 
Abstractions should not depend on details. Details should depend on abstractions.

Service

RepositoryInterface

Doctrine Repository

Inside

Outside

Layer vs. Hexagonal Architectur

Presentation

Domain

Persistence

Layer vs. Hexagonal Architectur

Presentation

Domain

Persistence

Related Concepts

CQRS

EVENT Sourcing

BDD

Modeling by 

Example

TDD

Resourcen

From MVC

to

Hexagonal Architecture

A ToDo List

class TodoController extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{

    public function addToDoAction(Request $request)
    {
        $todo = new ToDo();
        $form = $this->createForm(new TodoType(), $todo);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
        	
            $em = $this->getDoctrine()->getManager();
            $list = $em->find(TodoList::class, $request->get('listId));
            if(!$list) { throw new NotFoundException(); }
            $todo->setTodoList($list);
            $em->persist($todo);
            $em->flush();
            $this->mailer->sendMessage('A new ToDo has been added');
            return $this->redirectToRoute('todo_show', ['id' => $todo->getId()]);
        }
        return $this->render('todo/new.html.twig', [
            'todo' => $task,
            'form' => $form->createView(),
        ]);
    }
}

Model

A ToDo List

class TodoController extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{

    public function addToDoAction(Request $request)
    {
        $todo = new ToDoDTO();
        $form = $this->createForm(new TodoType(), $todo);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            
            $addTodoCommand = new AddTodoCommand($todo, $request->get('listId));
            $this->commandHandler->handle($addTodoCommand);
            return $this->redirectToRoute('todo_show', ['id' => $todo->getId()]);
        }
        return $this->render('todo/new.html.twig', [
            'todo' => $task,
            'form' => $form->createView(),
        ]);
    }
}

DTO

Intention

Parking

 

The code is maintainable when the framework lets you make the choices

A framework is "convenient" when it makes choices for you

Hexagonal Architecture

By Patrick Jahns

Hexagonal Architecture

  • 447