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
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