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

Simplify your application with a command bus

Simplify your application with a command bus

Introduction to the command bus pattern, and how to set it up on your Symfony application.
Talk given at SymfonyCon Cluj 2017

Romaric Drigon

November 17, 2017
Tweet

More Decks by Romaric Drigon

Other Decks in Technology

Transcript

  1. Simplify your
    application with a
    command bus
    SymfonyCon Cluj 2017

    View Slide

  2. Romaric Drigon
    Software engineer @ netinfluence,
    Switzerland

    View Slide

  3. What we want to avoid

    View Slide

  4. How our code
    should feel
    like

    View Slide

  5. What is a command bus?
    A command...
    "...an object is used to encapsulate all information
    needed to perform an action..."
    ...and a bus
    "...a communication system that transfers data
    between components..."

    View Slide

  6. To wrap up...

    View Slide

  7. A use case
    A Finder-like application in javascript
    On each file, you can operate commands: create a folder, rename a file,
    remove...
    Requests are sent from the JS application to a Symfony backend

    View Slide

  8. Setting up a command bus
    We will use Tactician (https://tactician.thephpleague.com/)
    Developed by Ross Tuck
    A Symfony bundle is available
    Notable alternatives include SimpleBus, by Matthias Noback

    View Slide

  9. A Command
    namespace AppBundle\Model\Command;
    use AppBundle\Entity\Item;
    class RemoveCommand
    {
    private $item;
    public function __construct(Item $item)
    {
    $this->item = $item;
    }
    public function getItem(): Item
    {
    return $this->item;
    }
    }

    View Slide

  10. The corresponding handler
    namespace AppBundle\Handler;
    use AppBundle\Entity\Item;
    use AppBundle\Model\Command\RemoveCommand;
    class RemoveHandler
    {
    private $entityManager; // To inject
    public function handle(RemoveCommand $removeCommand): string
    {
    $item = $removeCommand->getItem();
    $this->entityManager->remove($item);
    $this->entityManager->flush(); // We can find a better way
    return $item->getUuid(); // It can return a result!
    }
    }

    View Slide

  11. Declaring the handler
    Declared as a Symfony service:
    Note: handlers now support autowiring, check out Tactician bundle documentation
    app.remove_handler:
    class: AppBundle\Handler\RemoveHandler
    tags:
    - { name: tactician.handler, command: AppBundle\Model\Command\RemoveComman
    arguments:
    - '@doctrine.orm.default_entity_manager'

    View Slide

  12. Sending the command to the bus
    From the Symfony API controller:
    Great, Romaric... but what is the interest?
    /**
    * @ParamConverter("item")
    */
    public function apiAction(Item $item)
    {
    $command = new RemoveCommand($item);
    $uuid = $this->get('tactician.commandbus')->handle($command);
    return new JsonResponse(['uuid' => $uuid]);
    }

    View Slide

  13. It scales well!

    View Slide

  14. The application is getting complex...
    After one month, we had 25 commands and handlers, totaling 3500 lines
    of code.

    View Slide

  15. Adding middlewares
    Huge interest: middlewares help to simplify common tasks.

    View Slide

  16. Middleware examples
    Doctrine (https://tactician.thephpleague.com/plugins/doctrine/) : wraps handlers
    in a DB transaction
    Logger (https://tactician.thephpleague.com/plugins/logger/) : can log everything
    to Monolog (audit!)
    Validation (shipped with Symfony Tactician bundle)
    Security, authorization
    ...

    View Slide

  17. Going further
    Testing: handlers and middlewares are easier to unit test
    Commands could be sent to a message queue (RabbitMQ...) instead of
    staying in memory, or scheduled to be executed later (delayed task...).

    View Slide

  18. [email protected]fluence.ch
    http://blog.netinfluence.ch

    View Slide