Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DDD, CQRS, ES, Hexagonal... and Symfony

DDD, CQRS, ES, Hexagonal... and Symfony

We talk a lot about various architectural approaches and how to make our projects better when using them to achieve business goals and technical quality. Unfortunately, we don't talk as much about the environment in which we apply those techniques - the framework - and since they represent the crucial part which determines the amount of work, I think we should. In this talk I'd like to show you how Symfony can be used with virtually any kind of approach - DDD, CQRS, Event Sourcing, Hexagonal Architecture, and many more - and how well it fits in each of those use cases. There are no silver bullets, but some tools do a really good job of letting you take control, and Symfony is definitely one of them.

Tomasz Kowalczyk

June 13, 2019
Tweet

More Decks by Tomasz Kowalczyk

Other Decks in Programming

Transcript

  1. !==

  2. ├── bin │ └── console ├── composer.json ├── composer.lock ├──

    config │ ├── bootstrap.php │ ├── bundles.php │ ├── packages │ │ ├── cache.yaml │ │ ├── dev │ │ │ └── routing.yaml │ │ ├── framework.yaml │ │ ├── routing.yaml │ │ └── test │ │ ├── framework.yaml │ │ └── routing.yaml │ ├── routes.yaml │ └── services.yaml ├── public │ └── index.php ├── src │ ├── Controller │ └── Kernel.php ├── symfony.lock ├── var └── vendor
  3. ├── bin │ └── console ├── composer.json ├── composer.lock ├──

    config │ ├── bootstrap.php │ ├── bundles.php │ ├── packages │ │ ├── cache.yaml │ │ ├── dev │ │ │ └── routing.yaml │ │ ├── framework.yaml │ │ ├── routing.yaml │ │ └── test │ │ ├── framework.yaml │ │ └── routing.yaml │ ├── routes.yaml │ └── services.yaml ├── public │ └── index.php ├── src │ ├── Controller │ └── Kernel.php ├── symfony.lock ├── var └── vendor
  4. └── src ├── Command │ └── GenerateCommand.php ├── Controller │

    ├── UserController.php │ └── CarController.php ├── Entity │ ├── User.php │ └── Car.php ├── Form │ ├── UserType.php │ └── CarType.php ├── Manager │ ├── UserManager.php │ └── CarManager.php ├── Repository │ ├── UserRepository.php │ └── CarRepository.php ├── Security │ └── Voter │ ├── UserVoter.php │ └── CarVoter.php ├── Service │ ├── UserService.php │ └── CarService.php └── Kernel.php
  5. └── src ├── Command │ └── GenerateCommand.php ├── Controller │

    ├── UserController.php │ └── CarController.php ├── Entity │ ├── User.php │ └── Car.php ├── Form │ ├── UserType.php │ └── CarType.php ├── Manager │ ├── UserManager.php │ └── CarManager.php ├── Repository │ ├── UserRepository.php │ └── CarRepository.php ├── Security │ └── Voter │ ├── UserVoter.php │ └── CarVoter.php ├── Service │ ├── UserService.php │ └── CarService.php └── Kernel.php
  6. src ├── Account │ ├── Application │ ├── Domain │

    └── Infrastructure └── Shared ├── Application ├── Domain └── Infrastructure └── SymfonyKernel.php
  7. src ├── Account │ ├── Application │ ├── Domain │

    └── Infrastructure └── Shared ├── Application │ └── CommandInterface.php ├── Domain └── Infrastructure ├── SynchronousCommandBus.php ├── CommandBusInterface.php └── SymfonyKernel.php
  8. interface CommandBusInterface { public function handle(CommandInterface $command): void; } final

    class SynchronousCommandBus implements CommandBusInterface { private $handlers; public function map(string $command, callable $handler): void { $this->handlers[$command] = $handler; } public function handle(CommandInterface $command): void { $fqcn = \get_class($command); $handlerNotFound = false === isset($this->handlers[$fqcn]); HandlerNotFoundException::throwWhen($handlerNotFound, $fqcn); call_user_func($this->handlers[$fqcn], $command); } }
  9. src ├── Account │ ├── Application │ ├── Domain │

    │ ├── Account.php │ │ └── AccountRepositoryInterface.php │ └── Infrastructure └── Shared ├── Application │ └── CommandInterface.php ├── Domain └── Infrastructure ├── SynchronousCommandBus.php ├── CommandBusInterface.php └── SymfonyKernel.php
  10. interface AccountRepositoryInterface { public function findById(int $id): Account; } final

    class Account { /** @var int */ private $id; /** @var string */ private $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } }
  11. src ├── Account │ ├── Application │ │ ├── AccountQueryInterface.php

    │ │ ├── SetupAccountCommand.php │ │ └── SetupAccountHandler.php │ ├── Domain │ │ ├── Account.php │ │ └── AccountRepositoryInterface.php │ └── Infrastructure └── Shared ├── Application │ └── CommandInterface.php ├── Domain └── Infrastructure ├── SynchronousCommandBus.php ├── CommandBusInterface.php └── SymfonyKernel.php
  12. final class SetupAccountCommand implements CommandInterface { /** @var int */

    private $id; /** @var string */ private $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } public function getId(): int { return $this->id; } public function getEmail(): string { return $this->email; } }
  13. final class SetupAccountHandler { /** @var AccountRepositoryInterface */ private $accounts;

    public function __construct(AccountRepositoryInterface $accounts) { $this->accounts = $accounts; } public function __invoke(SetupAccountCommand $cmd) { $account = new Account($cmd->getId(), $cmd->getEmail()); $this->accounts->add($account); } }
  14. interface AccountQueryInterface { public function findByEmail(EmailValue $email): AccountView; } final

    class AccountView { /** @var int */ public $id; /** @var string */ public $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } }
  15. src ├── Account │ ├── Application │ │ ├── AccountQueryInterface.php

    │ │ ├── SetupAccountCommand.php │ │ └── SetupAccountHandler.php │ ├── Domain │ │ ├── Account.php │ │ └── AccountRepositoryInterface.php │ └── Infrastructure │ ├── AccountController.php │ ├── DoctrineDbalAccountQuery.php │ └── DoctrineOrmAccountRepository.php └── Shared ├── Application │ └── CommandInterface.php ├── Domain └── Infrastructure ├── SynchronousCommandBus.php ├── CommandBusInterface.php └── SymfonyKernel.php
  16. final class DoctrineOrmAccountRepository implements AccountRepositoryInterface { /** @var EntityManagerInterface */

    private $em; public function __construct(EntityManagerInterface $em) { $this->em = $em; } public function findById(int $id): Account { return $this->em->find(Account::class, $id); } }
  17. final class DoctrineDbalAccountQuery implements AccountQueryInterface { /** @var Connection */

    private $connection; public function __construct(Connection $connection) { $this->connection = $connection; } public function findByEmail(EmailValue $email): AccountView { $sql = 'SELECT id, email FROM user WHERE email = :email'; $data = $this->connection->exec($sql, ['email' => $email]); return new AccountView($data['id'], $data['email']); } }
  18. final class AccountController { // ... public function __construct(CommandBusInterface $bus,

    RouterInterface $router) { // ... } public function setupAction(Request $request): Response { $id = generateId(); $payload = $request->request->all(); $command = new SetupAccountCommand($id, $payload['email']); $this->bus->handle($command); return new Response(null, Response::HTTP_CREATED, [ 'Location' => $this->router->generate('account.view', [$id]), ]); } }
  19. parameters: services: _defaults: autowire: true autoconfigure: true App\: resource: '../src/*'

    exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}' App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments']
  20. E S

  21. src ├── Account │ ├── Application │ │ ├── AccountQueryInterface.php

    │ │ ├── SetupAccountCommand.php │ │ └── SetupAccountHandler.php │ ├── Domain │ │ ├── Account.php │ │ ├── AccountWasSetupEvent.php │ │ └── AccountRepositoryInterface.php │ └── Infrastructure │ ├── AccountController.php │ ├── DoctrineDbalAccountQuery.php │ └── DoctrineOrmAccountRepository.php └── Shared ├── Application │ └── CommandInterface.php ├── Domain └── Infrastructure ├── SynchronousCommandBus.php ├── CommandBusInterface.php └── SymfonyKernel.php
  22. final class AccountWasSetupEvent { /** @var int */ private $id;

    /** @var string */ private $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } public function getId(): int { return $this->id; } public function getEmail(): string { return $this->email; } }