Hexagonal Architecture
Patrick Jahns
18/03/2017 - eCommerceCamp Jena
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(),
]);
}
}
ToDo Application
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(),
]);
}
}
How can we test the Application?
How can we test the Application?
- Need full Application/Stack
- Database
- Can only test outside-in
SLOW and Difficult
Reusability?
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(),
]);
}
}
class TodoCommand extends Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand
{
protected function execute(InputInterface $input, OutputInterface $output)
{
$em = $this->getContainer()->get('default_entitymanager');
$list = $em->find(TodoList::class, $input->getArgument('listId'));
if(!$list) { throw new NotFoundException(); }
$todo = new ToDo();
$todo->setName($input->getArgument('name'));
$todo->setDescription($input->getArgument('description'));
$todo->setTodoList($list);
$em->persist($todo);
$em->flush();
$mailer = $this->getContainer()->get('swift_mailer');
mailer->sendMessage('A new ToDo has been added');
}
}
class TodoController extends FOS\RestBundle\Controller\FOSRestController
{
public function addToDoAction(ParamFetcher $paramFetcher)
{
$todo = new ToDo();
$em = $this->getDoctrine()->getManager();
$list = $em->find(TodoList::class, $paramFetcher->get('listId));
if(!$list) { throw new NotFoundException(); }
$todo = new ToDo();
$todo->setName($paramFetcher->getArgument('name'));
$todo->setDescription($paramFetcher->getArgument('description'));
$todo->setTodoList($list);
$em->persist($todo);
$em->flush();
$this->mailer->sendMessage('A new ToDo has been added');
return $this->handleView($this-view($todo));
}
}
- 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
Domain
Presentation
Persistence
Clean Architecture
by Uncle Bob
Related Concepts
CQRS
EVENT Sourcing
BDD
Modeling by
Example
TDD
DDD
Resourcen
Hexagonal/Onion/Clean Architecture
- http://alistair.cockburn.us/Hexagonal+architecture
- http://fideloper.com/hexagonal-architecture
- https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
- http://culttt.com/2014/12/31/hexagonal-architecture/
Domain Driven Design
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 - e-commerce camp
By Patrick Jahns
Hexagonal Architecture - e-commerce camp
- 426