xpug milano - hexagonal architecture

61
Hexagonal Architecture Gabriele Tondi - @racingDeveloper

Upload: gabriele-tondi

Post on 21-Feb-2017

738 views

Category:

Technology


0 download

TRANSCRIPT

Hexagonal Architecture

Gabriele Tondi - @racingDeveloper

Metwitter: @racingDeveloper mail: [email protected] work: agile software developer @ love: metodologie agili (XP), OOD, TDD… and races

Cos’è un’architettura?

Il web é un’architettura?

Perché è importante l’architettura?

due valori del software

Valore Secondario rispetta gli attuali requisiti

Valore Primario tollerare e facilitare i requisiti futuri

MVC è un’architettura?

Cosa fa questa applicazione?

Aiutino

Cosa fa questa applicazione?

VISITORS?

CONTACTS?

Cos’è?

CHIESAAAAA!

L’architettura ci deve dire COSA fa

non COME lo fa

MVC è un’architettura?

MVC è un PATTERN

"Model-View-Controller is the concept introduced by Smalltalk's inventors (Trygve

Reenskaug and others) of encapsulating some data together with its processing (the model)

and isolate it from the manipulation (the controller) and presentation (the view) part that

has to be done on a UserInterface. reason."

http://c2.com/cgi/wiki?ModelViewController

il web é un’architettura?

NO! E’ un I/O Device

"Software architecture is not about databases, web servers, dependency injection, Rails, Hibernate, JSF, Struts, Spring, or any other

framework or tool. Architecture is about intent."

"When you see a web-based accounting system, the architecture of that system should scream accounting at you. The fact that it's a web

based system should be unnoticeable. After all, the web is just a delivery mechanism; and we don't want our system

architecture polluted with delivery mechanisms, databases, and other low level tools and concerns."

[https://cleancoders.com/episode/clean-code-episode-7/show]

public function changeUserPassword($userId, $newPassword) {

$entityManager = $this->getDoctrine()->getRepository('AppBundle:Post')->getEntityManager; $query = $entityManager->createNativeQuery('SELECT * FROM users WHERE id = ?'); $query->setParameter(1, 'xpug');

$user = $query->getResult();

if ($user->getPassword() == $newPassword) { return "You should change you password"; }

if (strlen($newPassword) < 8) { return "Password must be at least 8 chars long"; } $user->setPassword($newPassword); $entityManager->store($user);

return "Thank you, password updated”;

}

Esempio tipico

Logica di BusinessDatabase

Interfaccia Utente

SMTP

public function changeUserPassword($userId, $newPassword) {

$entityManager = $this->getDoctrine()->getRepository('AppBundle:Post')->getEntityManager; $query = $entityManager->createNativeQuery('SELECT * FROM users WHERE id = ?'); $query->setParameter(1, 'xpug');

$user = $query->getResult();

if ($user->getPassword() == $newPassword) { return "You should change you password"; }

if (strlen($newPassword) < 8) { return "Password must be at least 8 chars long"; } $user->setPassword($newPassword); $entityManager->store($user);

return "Thank you, password updated”;

}

Per quali motivi può cambiare?

public function changeUserPassword($userId, $newPassword) {

$entityManager = $this->getDoctrine()->getRepository('AppBundle:Post')->getEntityManager; $query = $entityManager->createNativeQuery('SELECT * FROM users WHERE id = ?'); $query->setParameter(1, 'xpug');

$user = $query->getResult();

if ($user->getPassword() == $newPassword) { return "You should change you password"; }

if (strlen($newPassword) < 8) { return "Password must be at least 8 chars long"; } $user->setPassword($newPassword); $entityManager->store($user);

return "Thank you, password updated”;

}

Per quali motivi può cambiare?

Almeno tre:• Meccanismo di persistenza

• Regole di business

• Formato di output

Single Responsibility Principle (SRP)

"Gather together the things that change for the same reasons. Separate those things that change for different reasons.”

Layered architecture

User Interface Layer

Application Layer

Domain Layer

Infrastructure Layer

Dependency Inversion Principle (DIP)

"High-level modules should not depend on low-level modules. Both should depend on abstractions.

Abstractions should not depend upon details. Details should depend upon abstraction."

Hexagonal Architecture

http://alistair.cockburn.us/Hexagonal+architecture

Application• È il cuore dell’esagono

• Contiene la logica di dominio

• È totalmente indipendente dal meccanismo di delivery (console application, REST endpoint…)

• È totalmente indipendente dai servizi di supporto (database, SMTP, AMPQ…)

Application - Dominio

• Aggregati, entità, valori, servizi di dominio, eventi di dominio vivono all’interno dell’esagono

• Vengono coordinati dagli use-case (Application Service) per compiere gli scopi dall'applicazione

Application - Porte Primarie• aka use-case, application service

• usate per guidare l’applicazione dall'esterno

• non conoscono la natura del client che le userà

• il focus è sullo scopo della conversazione, non sulla tecnologia utilizzata

esempio - portapublic class BookRatingUseCase implements UseCase{ private final BookCatalog catalog; private final BookRatingRepository bookRatingRepository; public BookRatingUseCase(BookCatalog catalog, BookRatingRepository bookRatingRepository) { this.catalog = catalog; this.bookRatingRepository = bookRatingRepository; } public void execute(BookRatingRequest request) { Book book = catalog.bookWithId(new BookId(request.getBookId())); guardBookNotFound(book); BookRating rate = book.rate(Rating.value(request.getRating())); bookRatingRepository.add(rate); } private void guardBookNotFound(Book book) { if (book == null) throw new BookNotFoundException(); } }

esempio - adapter@RestController@RequestMapping(value = "/book/{bookId}/rating") public class BookRatingController{ private final UseCase useCase; @Inject public BookRatingController(UseCase bookRatingUseCase) { this.useCase = bookRatingUseCase; } @RequestMapping(method = POST) @ResponseStatus(value = CREATED) public void rateBook(@PathVariable String bookId, @RequestBody BookRatingDTO bookRatingDTO) { useCase.execute(new BookRatingRequest(bookId, bookRatingDTO.getRating())); } @ExceptionHandler(value = BookNotFoundException.class) @ResponseStatus(value = NOT_FOUND) public void exceptionHandler(BookNotFoundException e) { }}

Application - Porte Secondarie

• usate dagli abitanti dell’esagono per comunicare con il mondo esterno

• vengono definite come interfacce

• il focus deve essere sullo scopo della conversazione, e non sulla tecnologia sottostante

esempio - portapublic interface BookCatalog{ Book bookWithId(BookId bookId); }

esempio - adapterpublic class InMemoryBookCatalog implements BookCatalog{ private final List<Book> books; public InMemoryBookCatalog(Book... books) { this.books = asList(books); } @Override public Book bookWithId(BookId bookId) { for (Book book : books) { if (book.hasId(bookId)) return book; } return null; } }

Demo time!

Hexagonal Books

Application

REST

In Memory

Cons

ole

Mysql

Vediamo un po’ di codice

Vantaggi• È possibile concentrarsi da subito sul dominio del problema

• Differire le decisioni riguardo I/O devices

• Testare in unità il dominio

• Erogare l’applicazione su canali differenti (rest, console, acceptance

test, stream-processing ecc)

Vantaggi / 2

• Non occorre sposare il framework MVC

• Maggiore espressività nell'esagono

Q&A

Grazie!