Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Hexagonal architecture in PHP

5,430 views

Published on

Presentation of Hexagonal Architecture approaches on PHP

Published in: Technology

Hexagonal architecture in PHP

  1. 1. Hexagonal Architecture Paulo Victor Systems Analyst, Open Source Developer, Zend Certified Engineer PHP 5.3. @pv_fusion
  2. 2. VOUCHER: php_conf2015
  3. 3. Presentation licensed by This presentation is free to use under Creative Commons Attribution license. If you use the content and graphic assets (photos, icons, typographies) provided with this presentation you must keep the Credits slide.
  4. 4. Beginnings...
  5. 5. How? ◇ How can I separate Domain from Framework? ◇ How can I decouple the libraries? ◇ How can I make communication between layers? ◇ How can I make a semantic structure? You need to think about Software Architecture
  6. 6. Software Architecture Why do we even talk about Architecture? ◇ High Maintainability ◇ Low Technical Debt ◇ Semantic structure (Layers are responsible for doing what they should do)
  7. 7. Software Architecture The architecture of a software system is a metaphor, analogous to the architecture of a building. Perry, D. E.; Wolf, A. L. (1992). "Foundations for the study of software architecture". 1 #weareallhexagons WhatWhy How
  8. 8. Architectural Styles In order to be able to build complex applications, one of the key aspects is having an architecture design that fits the application needs. Buenosvinos, C., Soronellas C. Akbary K. (2015) "Domain-Driven Design in PHP" 2 #weareallhexagons WhatWhy How
  9. 9. Hexagonal Architecture3 #weareallhexagons WhatWhy How 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. Cockburn, A. (2008). "Hexagonal architecture".
  10. 10. Domain
  11. 11. #weareallhexagons Ports and Adapters
  12. 12. <?php namespace DomainCore; interface MarketRepository extends RepositoryInterface { /** * Find Market By KeyName * @param $keyName * @return Market */ public function byKeyName($keyName); /** * Find Market By KeyName and token * @param $keyName * @param $token * @return Market */ public function byKeyNameAndToken($keyName, $token); /** * Create an Market * @param Market $market * @return Market */ public function add(Market $market); } <?php namespace AppBundleInfrastructureCore; class MarketRepository extends PDORepository implements DomainCoreMarketRepository { public function byKeyName($keyName) { $sql = "select * from market where key_name = $keyName"; return $this->map($this->getRepository() ->query($sql)); } public function byKeyNameAndToken($keyName, $token) { $sql = <<<SQL select * from market where key_name = $keyName and access_token = $token SQL; return $this->map($this->getRepository() ->query($sql)); } ... } PDO Adapter Port
  13. 13. <?php namespace DomainCore; interface MarketRepository extends RepositoryInterface { /** * Find Market By KeyName * @param $keyName * @return Market */ public function byKeyName($keyName); /** * Find Market By KeyName and token * @param $keyName * @param $token * @return Market */ public function byKeyNameAndToken($keyName, $token); /** * Create an Market * @param Market $market * @return Market */ public function add(Market $market); } <?php namespace AppBundleInfrastructureCore; class MarketRepository extends EntityRepository implements DomainCoreMarketRepository { /** * {@inheritdoc} */ public function byKeyName($keyName) { return $this->getRepository() ->findOneByKeyName($keyName); } public function byKeyNameAndToken($keyName, $token) { return $this->getRepository() ->findOneBy(['keyName' => $keyName, 'accessToken' => $token]); } public function add(DomainCoreMarket $market) { $this->getEntityManager()->persist($market); $this->getEntityManager()->flush(); } } Doctrine Adapter Port
  14. 14. <?php namespace DomainCore; interface MarketRepository extends RepositoryInterface { /** * Find Market By KeyName * @param $keyName * @return Market */ public function byKeyName($keyName); /** * Find Market By KeyName and token * @param $keyName * @param $token * @return Market */ public function byKeyNameAndToken($keyName, $token); /** * Create an Market * @param Market $market * @return Market */ public function add(Market $market); } <?php namespace AppBundleInfrastructureCore; class MarketRepository extends SolrRepository implements DomainCoreMarketRepository { public function byKeyName($keyName) { $marketId = $this->elasticSearchRepository()->search('mkt', [$keyName]); $market = new Market(); $market->setId($marketId); $market->setName($this->redisRepository()->get('mkt':'.$keyName.':name')); $market->setKeyName($this->redisRepository()->get('mkt':'.$keyName.':keyname')); $market->setAccessToken($this->redisRepository()->get('mkt':'.$keyName.':token')); return $market; } public function byKeyNameAndToken($keyName, $token) { $marketId = $this->elasticSearchRepository()->search('mkt', [$keyName, $token]); $market = new Market(); $market->setId($marketId); $market->setName($this->redisRepository()->get('mkt':'.$keyName.':name')); $market->setKeyName($this->redisRepository()->get('mkt':'.$keyName.':keyname')); $market->setAccessToken($this->redisRepository()->get('mkt':'.$keyName.':token')); return $market; } } Redis and Solr Adapter Port
  15. 15. “ Domain Driven Design (DDD) Do you know DDD ? DDD is about placing our attention at the heart of the application, focusing on the complexity that is intrinsic to the business domain itself.
  16. 16. The reason to an Application Troubleshoot space and time problems What is the best way to do this? Encapsulating the business rules with layers
  17. 17. Work with layers “Hexagonal Architecture defines conceptual layers of code responsibility, and then points out ways to decouple code between those layers. It's helped clarify when, how and why we use interfaces (among other ideas)” Fideloper
  18. 18. Coupling between layers User Interface Application Domain Infrastructure
  19. 19. Coupling between layers User Interface Application Domain Infrastructure Dependency Inversion Principle
  20. 20. Decoupling between layers Core Domain Domain Application Infrastructure S Q L
  21. 21. Dependencies Core Domain Domain Application Infrastructure S Q L
  22. 22. Boundaries Core Domain Domain Application Infrastructure S Q L
  23. 23. Communication Between Layers: Boundaries Use Case Bus Handle
  24. 24. CommandBus Command Handler Command executes( ) handles( ) Fideloper. Hexagonal Architecture CommandBus Use Case Use Case
  25. 25. <?php namespace HexCommandBus; interface CommandInterface {} <?php namespace HexTicketsCommands; use HexCommandBusCommandInterface; class CreateTicketCommand implements CommandInterface { /** * @var array */ public $data; public function __construct(Array $data) { $this->data = $data; } public function __get($property) { if( isset($this->data[$property]) ) { return $this->data[$property]; } return null; } } https://github.com/fideloper/hexagonal-php
  26. 26. https://github.com/fideloper/hexagonal-php interface CommandBusInterface { public function execute(CommandInterface $command); } class CommandBus implements CommandBusInterface { private $container; private $inflector; public function __construct(Container $container, CommandNameInflector $inflector) { $this->container = $container; $this->inflector = $inflector; } public function execute(CommandInterface $command) { return $this->getHandler($command)->handle($command); } private function getHandler($command) { return $this->container->make( $this->inflector->getHandler($command) ); } }
  27. 27. <?php namespace HexTicketsHandlers; class CreateTicketHandler implements HandlerInterface { private $validator; private $repository; private $dispatcher; public function __construct(CreateTicketValidator $validator, TicketRepositoryInterface $repository, Dispatcher $dispatcher) { // set attributes } public function handle(CommandInterface $command) { $this->validator->validate($command); $this->save($command); } protected function save($command) { $message = new Message; $message->message = $command->message; $ticket = new Ticket; $ticket->subject = $command->subject; $ticket->name = $command->name; $ticket->email = $command->email; $ticket->setCategory( Category::find($command->category_id) ); // Need repo $ticket->setStaffer( Staffer::find($command->staffer_id) ); // Need repo $ticket->addMessage( $message ); $this->repository->save($ticket); $this->dispatcher->dispatch( $ticket->flushEvents() ); } } https://github.com/fideloper/hexagonal-php
  28. 28. Structure Core Domain Domain Application Infrastructure S Q L Biso Symfony 2 FriendsOfSym fony
  29. 29. { "name": "Symfony2Biso", "type": "project", "require": { "php": ">=5.3.9", "friendsofsymfony/rest-bundle": "1.7.*", "predis/predis": "1.0.*", "biso": "dev-master" }, "repositories": [ { "type": "package", "package": { "name": "biso", "version": "dev-master", "source": { "url": "https://github.com/pvgomes/biso", "type": "git", "reference": "origin/master" }, "autoload": { "psr-0": { "Domain": "src" } } } } ], }
  30. 30. <?php namespace Domain; interface Command { public function repositories(); public function eventName(); public function eventNameError(); } <?php namespace AppBundleApplicationCore; use AppBundleInfrastructureCoreConfiguration; use Domain; class CreateConfigurationCommand implements DomainCommand { public $data; private $configuration; private $eventName; public function __construct($marketKey, $key, $value) { $this->eventName = DomainCoreEvents::MARKET_CREATE_CONFIGURATION; $this->configuration = new Configuration(); $this->data = ['marketKey' => $marketKey, 'key' => $key, 'value' => $value]; } public function __get($property) { $value = null; if( isset($this->data[$property]) ) { $value = $this->data[$property]; } return $value; } ... Create Configuration Command
  31. 31. <?php namespace Domain; interface CommandBus { public function execute(Command $command); } <?php namespace AppBundleApplicationCommandBus; class CommandBus implements DomainCommandBus { private $container; private $eventDispatcher; private $inflector; private $applicationEvent; public function __construct(ContainerInterface $container, CommandNameInflector $inflector) { $this->container = $container; $this->inflector = $inflector; $this->eventDispatcher = $container->get('event_dispatcher'); $this->applicationEvent = new ApplicationEvent(); } public function execute(DomainCommand $command) { try { $this->applicationEvent->setCommand($command); $response = $this->getHandler($command)->handle($command); $this->eventDispatcher->dispatch($command->eventName(), $this->applicationEvent); } catch (Exception $exception) { $this->applicationEvent->setException($exception); $this->eventDispatcher->dispatch($command->eventNameError(), $this- >applicationEvent); throw $exception; } return $response; Command Bus
  32. 32. <?php namespace Domain; interface Handler { public function handle(Command $command); } <?php namespace DomainCore; class CreateConfigurationHandler implements Handler { private $configurationRepository; private $market; public function __construct(Market $market) { $this->market = $market; } public function handle(Command $command) { return $this->save($command); } protected function save(Command $command) { $configuration = $command->configurationEntity(); if (!$configuration instanceof Configuration) { throw new DomainException("Invalid configuration"); } $configuration->setMarket($this->market); $configuration->setKey($command->key); $configuration->setValue($command->value); $this->configurationRepository->add($configuration); return $configuration->getId(); } ... Command Handler
  33. 33. <?php namespace AppBundleApplicationControllerWeb; class SystemController extends Controller { /** * @Route("/system/configuration", name="configuration_list") * @param Request $request * @return SymfonyComponentHttpFoundationResponse */ public function configurationAction(Request $request) { $form = $this->createFormBuilder([])->add('key', 'text')->add('value', 'textarea')->getForm(); if ($request->isMethod('POST')) { $form->handleRequest($request); $data = $form->getData(); try { $createConfigurationCommand = new CreateConfigurationCommand($this->getUser()->getMarket()->getKeyName(), $data['key'], $data['value']); $this->get("command_bus")->execute($createConfigurationCommand); $flashMsg = "Chave gravada."; $flashMsgType = "success"; } catch (DomainException $e) { $flashMsg = $e->getMessage(); $flashMsgType = "warning"; } catch (Exception $e) { $flashMsg = "Erro ao inserir a chave de configuração."; $flashMsgType = "warning"; } $this->addFlash($flashMsgType , $flashMsg); } $viewVars['form'] = $form->createView(); return $this->render('web/system/configuration.html.twig', $viewVars); } Create Config Usage | Browser
  34. 34. <?php namespace AppBundleApplicationApiv1Controller; class SystemController extends ApiController implements TokenAuthentication { use JsonValidator; public function configurationCreate() { $request = $this->get('request'); $marketKey = $request->headers->get('key'); $requestContent = json_decode($$request->getContent()); $jsonResponse = new JsonResponse(); try { if (!$this->isValidJson($this->loadConfigurationCreateSchema(), $requestContent)) { throw new HttpException(400, $this->getJsonErrors()); } $createConfigurationCommand = new CreateConfigurationCommand($marketKey, $requestContent->key, $requestContent->value); $this->get("command_bus")>execute($createConfigurationCommand); $jsonResponse->setStatusCode(204); } catch (DomainException $exception) { $contentError['description'] = $exception->getMessage(); $jsonResponse->setStatusCode(400); $jsonResponse->setData($contentError); } catch (Exception $exception) { $contentError['description'] = $exception->getMessage(); $jsonResponse->setStatusCode(500); $jsonResponse->setData($contentError); } return $jsonResponse; } ... Create Config Usage | API
  35. 35. /pvgomes/symfony2biso
  36. 36. Thanks! Any questions? You can find me at: ◇ @pv_fusion ◇ pv.gomes89@gmail.com
  37. 37. Credits Special thanks to all the people who made and released these awesome resources for free: ◇ Contents of this presentation Paulo Victor Gomes ◇ Presentation template by SlidesCarnival ◇ Photographs by Unsplash References ❖ BROOKS, FREDERICK. The Design of Design: Essays from a Computer Scientist. ❖ BUENOSVINOS, CARLOS. SORONELLA, CHRISTIAN. AKBARY, KEYVAN. Domain Driven Design in PHP. ❖ COCKBURN, ALISTAIR. Hexagonal Architecture. ❖ VERNON, VAUGHN - Implementing Domain-Driven Design. ❖ EVANS, ERICK. Domain Driven Design: Tackling Complexity in the Heart of Software.

×