Slide 1

Slide 1 text

Hexagonal Architecture Chris Fidao (hek-sag-uh-nl)

Slide 2

Slide 2 text

@fideloper

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Implementing Laravel Real-world implementation of testable and maintainable code. (hopefully)

Slide 5

Slide 5 text

Vaprobash Vagrant Provisioning Bash Scripts

Slide 6

Slide 6 text

Servers for Hackers.com

Slide 7

Slide 7 text

Why / What Ports / Adapters Boundary Layers /

Slide 8

Slide 8 text

WHY Architecture

Slide 9

Slide 9 text

Maintainability Technical Debt Time

Slide 10

Slide 10 text

What is it?

Slide 11

Slide 11 text

So…What is it?

Slide 12

Slide 12 text

The Hexagon Core Domain Application Domain Framework

Slide 13

Slide 13 text

(Core) Domain Core Domain Application Domain Framework

Slide 14

Slide 14 text

Behavior

Slide 15

Slide 15 text

Constraints

Slide 16

Slide 16 text

Application Core Domain Application Domain Framework

Slide 17

Slide 17 text

Framework Core Domain Application Domain Framework

Slide 18

Slide 18 text

Outside Core Domain Application Domain Framework

Slide 19

Slide 19 text

Ports Adapters /

Slide 20

Slide 20 text

Ports & Adapters Core Domain Application Domain Framework

Slide 21

Slide 21 text

Inside/Outside Core Domain Application Domain CommandBus Framework HTTP Use Case Repo DBAL Database Events Dispatcher Service Impl

Slide 22

Slide 22 text

Core Domain Application Domain Framework Dependencies

Slide 23

Slide 23 text

interface Notifier { ! public function send(Message $message); } class SesNotifier implements Notifier { ! public function send(Message $message) { // Details } }

Slide 24

Slide 24 text

Use-Case Driven Development

Slide 25

Slide 25 text

All the Contexts •Web •API •CLI •Queue •Event Handler

Slide 26

Slide 26 text

Use Cases: CommandBus CommandBus executes( ) Command Handler handles( ) Command

Slide 27

Slide 27 text

// Class SimpleCommandBus ! public function execute( $command ) { return $this->getHandler( $command ) ->handle( $command ); } Simple CommandBus

Slide 28

Slide 28 text

Core Domain Application Domain CommandBus Framework HTTP Use Case Repo DBAL Database Events Dispatcher Service Impl

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

Boundaries

Slide 31

Slide 31 text

Domain/Application Boundary Core Domain Application Domain Framework Use Case Repo Events

Slide 32

Slide 32 text

interface CommandBusInterface { ! public function execute( $command ); } interface HandlerInterface { ! public function handle( $command ); }

Slide 33

Slide 33 text

Core Domain Application Domain Framework Use Case Repo Events

Slide 34

Slide 34 text

interface TicketRepositoryInterface { ! public function getStaffOpenTickets( Staffer $staffer, $limit=10); ! ! public function save(Ticket $model); }

Slide 35

Slide 35 text

Application Domain CommandBus Framework DBAL Dispatcher The Application/External Boundary

Slide 36

Slide 36 text

interface Notifier { ! public function send(Message $message); } interface Validator { ! public function passes(Array $data); ! public function getErrors(); } interface Dispatcher { ! public function dispatch(Array $events); }

Slide 37

Slide 37 text

Framework Core Domain Application Domain Framework HTTP Database Service Impl

Slide 38

Slide 38 text

Identify the aspects that vary and separate them from what stays the same

Slide 39

Slide 39 text

Layers

Slide 40

Slide 40 text

The Domain Core Domain Application Domain Framework Use Case Repo Events

Slide 41

Slide 41 text

categories->contains( $this->category ) ) { throw new DomainException("Staffer can't be assigned to ".$this->category); } ! $this->staffer()->associate($staffer); // Set Relationship ! return $this; } ! public function setCategory(Category $category) { if( $this->staffer instanceof Staffer && ! $this->staffer->categories->contains( $category ) ) { // Unset staffer if can't be assigned to set category $this->staffer = null; } ! $this->category()->associate($category); // Set Relationship ! return $this; } }

Slide 42

Slide 42 text

class Ticket extends Model { ! /* ... Other logic ... */ ! public function save(array $options = array()) { /* Integrity Checks, and then: */ ! if( ! $this->exists ) { $this->raise( new TicketCreatedEvent($this) ); } ! return parent::save($options); } }

Slide 43

Slide 43 text

class CreateTicketCommand { ! protected $data; ! public function __construct($data) { $this->data = $data; } ! public function __get($property) { // Simplified example return $this->data[$property]; } }

Slide 44

Slide 44 text

The Application Application Domain CommandBus Framework DBAL Dispatcher

Slide 45

Slide 45 text

// Class SimpleCommandBus ! public function execute( $command ) { return $this->getHandler( $command ) ->handle( $command ); }

Slide 46

Slide 46 text

! class CreateTicketHandler implements HandlerInterface { ! ! public function handle($command) { $this->validate($command); // Throw ValidationException $this->save($command); } ! protected function save($command) { $ticket = new Ticket; /* Some other setters... */ $ticket->setCategory( $this->catRepo->find($command->category_id) ); $ticket->setStaffer( $this->staffRepo->find($command->staffer_id) ); $ticket->addMessage( $ticket->addMessage($command->message); ); ! $this->ticketRepo->save($ticket); // Use Repositories ! $this->dispatcher->dispatch( $ticket->flushEvents() ); // Fire Events } }

Slide 47

Slide 47 text

class DbTicketRepository implements RepositoryInterface { ! public function getStaffOpenTickets(Staffer $staffer, $limit=10) { return $this->ticket->where('staff_id', $staffer->id) ->take($limit)->get(); } ! public function save(Ticket $ticket) { $ticket->save(); } }

Slide 48

Slide 48 text

Framework Core Domain Application Domain Framework HTTP Database Service Impl

Slide 49

Slide 49 text

class TicketController extends BaseController { ! public function createTicket() { $command = new CreateTicketCommand( Input::all() ); ! try { $this->bus->execute($command); } catch(ValidationException $e) { return Redirect::to('/tickets/new') ->withErrors( $e->getErrors() ); } catch(DomainException $e) { return Redirect::to('/tickets/new') ->withErrors( $e->getErrors() ); } return Redirect::to(‘/tickets'); } }

Slide 50

Slide 50 text

class SesEmailNotifier implements NotifierInterface { ! public function __construct(SesClient $client) { $this->client = $client; } ! public function send(Message $message) { $to = [$message->to()]; $message = ['Data' => $message->message()]; ! $this->client->sendEmail([ 'Destination' => ['ToAddresses' => $to], 'Message' => ['Body' => ['Html' => $message]] ]); } }

Slide 51

Slide 51 text

use Illuminate\Events\Dispatcher as EventDispatcher; ! class LaravelDispatcher implements Dispatcher { ! public function __construct(EventDispatcher $dispatcher) { $this->dispatcher = $dispatcher; } ! public function dispatch(Array $events) { foreach( $events as $event ) { $this->dispatcher->fire( $event->name(), $event ); } } ! }

Slide 52

Slide 52 text

TDD is DEAD (and other myths)

Slide 53

Slide 53 text

Identify the aspects that vary and separate them from what stays the same

Slide 54

Slide 54 text

Thanks