More Related Content

Hexagonal architecture - message-oriented software design (PHP Benelux 2016)

  1. HEXAGONAL ARCHITECTURE Message oriented software design By Matthias Noback
  2. ARCHITECTURE What's the problem?
  3. Nice app
  4. Sad app
  5. Your brain can't handle it M V C ?
  6. Coupling to frameworks and libraries
  7. How do you start a new project? Pick a framework Install a skeleton project Remove demo stuff Auto-generate entities Auto-generate CRUD controllers Done "It's a Symfony project!"
  8. That's actually outside in The boring stuff The interesting stuff Symfony Doctrine RabbitMQ Redis Angular
  9. Slow tests DB Browser Message queue Key- value Filesystem
  10. Why do frameworks not solve this for us? Because they can't ;)
  11. Frameworks are about encapsulation
  12. Low-level API $requestContent = file_get_contents('php://input'); $contentType = $_SERVER['CONTENT_TYPE']; if ($contentType === 'application/json') { $data = json_decode($requestContent, true); } elseif ($contentType === 'application/xml') { $xml = simplexml_load_string($requestContent); ... }
  13. Nicely hides the details $data = $serializer->deserialize( $request->getContent(), $request->getContentType() );
  14. Low-level API $stmt = $db->prepare( 'SELECT * FROM Patient p WHERE p.anonymous = ?' ); $stmt->bindValue(1, true); $stmt->execute(); $result = $stmt->fetch(PDO::FETCH_ASSOC); $patient = Patient::reconstituteFromArray($result);
  15. Hides a lot of details $patient = $repository->createQueryBuilder('p') ->where('p.anonymous = true') ->getQuery() ->getResult();
  16. What about abstraction?
  17. $patient = $repository->createQueryBuilder('p') ->where('p.anonymous = true') ->getQuery() ->getResult(); Concrete Concrete Concrete
  18. $patients = $repository->anonymousPatients(); Abstract Nice DIY
  19. Coupling to the delivery mechanism
  20. public function registerPatientAction(Request $request) { $patient = new Patient(); $form = $this->createForm(new RegisterPatientForm(), $patient); $form->handleRequest($request); if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($patient); $em->flush(); return $this->redirect($this->generateUrl('patient_list')); } return array( 'form' => $form->createView() ); } Request and Form are web-specific EntityManager is ORM, i.e. relational DB-specific
  21. Reusability: impossible
  22. Some functionality The web The CLI
  23. Some functionalityRun it
  24. Lack of intention-revealing code
  25. data data data
  26. public function updateAction(Request $request) { $patient = new Patient(); $form = $this->createForm(new PatientType(), $patient); $form->handleRequest($request); if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($patient); $em->flush(); return $this->redirect($this->generateUrl('patient_list')); } return array( 'form' => $form->createView() ); } from the HTTP request copied into an entity then stored in the database What exactly changed?! And... why?
  27. R.A.D. Rapid Application Development B.A.D. B.A.D. Application Development
  28. In summary Coupling to a framework Coupling to a delivery mechanism (e.g. the web) Slow tests Lack of intention in the code
  29. THE ESSENCE of your application
  30. The essence Other things
  31. The "heart"?
  32. "The heart of software is its ability to solve domain-related problems for its users. –Eric Evans, Domain Driven Design All other features, vital though they may be, support this basic purpose."
  33. What's essential? Domain model Interaction with it Use cases
  34. What's not essential?
  35. “The database is an implementation detail” –Cool software architect
  36. The core doesn't need to know about it
  37. What about interaction?
  38. The core doesn't need to know about it
  39. Infrastructure The world outside Web browser Terminal Database Messaging Filesystem (E)mail
  40. Mmm... layers Layers allow you to separate Layers allow you to allocate Layers have boundaries Rules for crossing
  41. Rules about communication Actually: rules about dependencies
  42. The dependency rule –Robert Martin, Screaming Architecture
  43. What crosses layer boundaries? Message
  44. Messages someFunctionCall( $arguments, $prepared, $for, $the, $receiver ); $message = new TypeOfMessage( $some, $relevant, $arguments ); handle($message);
  45. What about the application boundary? The app Message The world outside
  46. How does an app allow incoming messages at all? By exposing input ports Routes Console commands A WSDL file for a SOAP API
  47. Ports use protocols for communication Each port has a language of its own
  48. Web (HTTP)
  49. Messaging (AMQP)
  50. HTTP Request Form Request Controller Entity Value object W eb port Translate the request Repository
  51. Adapters The translators are called: adapters
  52. "Ports and adapters" Ports: allow for communication to happen Adapters: translate messages from the world outside == Hexagonal architecture Alistair Cockburn
  53. An example Plain HTTP message $_POST, $_GET, $_SERVER, Request POST /patients/ HTTP/1.1 Host: name=Matthias&email=matthiasn Command $command = new RegisterPatient( $request->get('name'), $request->get('email') );
  54. Command $command = new RegisterPatient( $request->get('name'), $request->get('email') ); Expresses intention Implies change Independent of delivery mechanism Only the message
  55. class RegisterPatientHandler { public function handle(RegisterPatient $command) { $patient = Patient::register( $command->name(), $command->email() ); $this->patientRepository->add($patient); } } Command Command handler
  56. Command Command handler A Command bus Command handler B Command handler C
  57. HTTP Request Form Request Controller Patient (entity) W eb port PatientRepository RegisterPatient- Handler RegisterPatient (command) Infrastructure Application Dom ain
  58. Change New entity (Patient) Entity- Manager UnitOf- Work $patient = Patient::register( $command->name(), $command->email() ); $this->patientRepository ->add($patient); Insert query (SQL) INSERT INTO patients SET name='Matthias', email='matthiasnoback@gmai';
  59. SQL query EntityManager UnitOfWork QueryBuilder Persistence port Prepare for persistence PatientRepository C ore Infrastructure
  60. Messaging (AMQP) Persistence (MySQL)
  61. What often goes wrong: we violate boundary rules...
  62. EntityManager UnitOfWork QueryBuilder PatientRepository C ore Infrastructure
  63. RegisterPatient- Handler Domain Infrastructure Application Domain PatientRepository (uses MySQL) EntityManager UnitOfWork QueryBuilder
  64. PatientRepository (uses MySQL) Domain Infrastructure Application Domain RegisterPatient- Handler
  65. Domain PatientRepository (interface) Dependency inversion PatientRepository (uses MySQL) RegisterPatient- Handler
  66. Domain InMemory- PatientRepository Speedy alternative RegisterPatient- Handler PatientRepository (uses MySQL) PatientRepository (interface)
  67. "A good software architecture allows decisions [...] to be deferred and delayed." –Robert Martin, Screaming Architecture
  68. IN CONCLUSION what did we get from all of this?
  69. Separation of concerns Core Infrastructure
  70. Command Command Command handler Command handler Stand-alone use cases Command Command handler Intention- revealing Reusable
  71. Infrastructure stand-ins Regular implementation Interface Stand-in, fast implementation
  72. This is all very much supportive of... See also: Modelling by Example DDD TDD BDD CQRS