Ramatou Adamou Issa

Ramatou Adamou Issa

  • Experiences
  • Formation
  • Musics
  • Blog

›Recent Posts

Recent Posts

  • Watchtower, container for updating docker images
  • PHP8 (Migrating existing PHP7 project to PHP8)
  • What news in Symfony 5
  • Command Query Segregation Responsibility (CQRS)
  • AFUP conference feedbacks

Command Query Segregation Responsibility (CQRS)

December 17, 2019

Ramatou Adamou Issa

Introduction

CQRS, stand for Command query responsibility segregation, is a design pattern that involve a complete separation between reading and writing data in your application. The command is responsible of writing data and the query stand for reading. The first thing that come in mind when you read this little definition is:

Why do I need to implement this design pattern? Still, there is already CRUD pattern which stand for creating, reading, updating and deleting data.

As Martin Fowler explain it very well in his tutorial here, CQRS become very useful when your are dealing with a very sophisticated behavior. beside, the CRUD pattern is not enough when we need to store data that's different from the data we provided.

CQRS and Domain Driven Development implementation.

As, I'm passionate of using design pattern as soon as it is possible during my application development process, I join CQRS with Hexagonal Architecture. pattern.

The little example is a template application. it's only goal is to be used for creating, deleting and updating template. The application architecture is like this.

  1. Application

    • CreateTemplateController
    • UpdateTemplateController
  2. Domain

    • Command
      • CreateTemplate
      • CreateTemplateHandler
      • UpdateTemplate
      • UpdateTemplateHandler
    • Query
      • GetTemplateDataProvider
      • GetTemplateHandler
      • GetTemplateQuery
  3. Infrastructure(nothing in this folder for this example)

Command part Code

All controllers are implemented following the one action, one controller pattern. This mean that in each controller, there is only one magic method (__invoke), that implement all the logic.

Create Template Controller

<?php
    namespace AppBundle\Application;
    
    use Symfony\Component\Validator\ConstraintViolationListInterface;
    use AppBundle\Domain\Command\CreateTemplate;
    use AppBundle\Domain\DTO\TemplateDTO;
    use AppBundle\InputException;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

    class CreateTemplateController extends AbstractController
    {
        /**
         * @ParamConverter("template", converter="fos_rest.request_body")
         */
        public function __invoke(string $id, TemplateDTO $template, ?ConstraintViolationListInterface $validationErrors)
        {
            if ($validationErrors && \count($validationErrors) > 0) {
                throw InputException::create(InputException::TEMPLATE_NOT_FOUND, (array) $validationErrors);
            }
    
            $template = $this->getCreateTemplate()->execute($template, $id);
    
            return $this->getApiSuccessResponse(['template' => $template]);
        }
    
        private function getCreateTemplateCommand(): CreateTemplate
        {
            return  $this->get('app.domain.cqrs.create.template.command');
        }
    }

Create Template Command

<?php

namespace AppBundle\Domain\Command\Template;

use AppBundle\Domain\Command\CommandInterface;
use AppBundle\Domain\DTO\TemplateDTO;
use AppBundle\Domain\Factory\TemplateDTOFactory;
use AppBundle\Exception\InputException;

class CreateTemplate implements CommandInterface
{
    /**@var TemplateDTOFactory*/
    private $templateDTOFactory;
    /** @var CreateTemplateHandler */
    private $createTemplateHandler;

    public function __construct(
        TemplateDTOFactory $templateDTOFactory,
        CreateTemplateHandler $createTemplateHandler
    ) {
        $this->templateDTOFactory = $templateDTOFactory;
        $this->createTemplateHandler = $templateDTOFactory;
    }

    public function execute(object $templateDTO, ?string $objectIdentifier): object
    {
        if (!$templateDTO instanceof TemplateDTO) {
            throw InputException::create(InputException::TEMPLATE_NOT_FOUND, [
                \sprintf("Invalid template body parameter")
            ]);
        }

        $template = $this->createTemplateHandler->handle($templateDTO);

        return $this->templateDTOFactory->create($template);
    }
}

CreateTemplateHandler

<?php

namespace AppBundle\Domain\Command\Template;

use Doctrine\ORM\EntityManagerInterface;
use AppBundle\Domain\DTO\TemplateDTO;
use AppBundle\Domain\Factory\TemplateFactory;
use AppBundle\Domain\Query\TemplateType\TemplateTypeDataProvider;
use AppBundle\Entity\Template;
use AppBundle\Entity\TemplateType;
use AppBundle\Exception\InputException;

class CreateTemplateHandler
{
    /** @var EntityManagerInterface */
    private $entityManager;
    /** @var TemplateFactory */
    private $templateFactory;
    /** @var TemplateTypeDataProvider */
    private $templateTypeDataProvider;

    public function __construct(
        EntityManagerInterface $entityManager,
        TemplateFactory $templateFactory,
        TemplateTypeDataProvider $templateTypeDataProvider
    ) {
        $this->entityManager = $entityManager;
        $this->templateFactory = $templateFactory;
        $this->templateTypeDataProvider = $templateTypeDataProvider;
    }

    public function handle(TemplateDTO $templateDTO): Template
    {
        try {
            /** @var Template $template */
            $template = $this->TemplateFactory->create($templateDTO);
            $templateTypeIdentifier = $templateDTO->getTemplateType()->getIdentifier();

            $templateType = $this->TemplateTypeDataProvider->getItem(
                TemplateType::class, $templateTypeIdentifier
            );

            if(!$templateType) {
                throw InputException::create(InputException::_NOT_FOUND, [
                    \sprintf(
                        "There is no type for this  template corresponding to %s",
                        $templateTypeIdentifier
                    )
                ]);
            }

            $template->setTemplateType($templateType);
            $this->entityManager->persist($template);
            $this->entityManager->flush();

            return $template;
        } catch (\Exception $exception) {
            throw InputException::create(InputException::ING_DOCTRINE_ERROR, [$exception->getMessage()]);
        }
    }
}

Query Part

GetTemplateDataProvider

<?php

namespace AppBundle\Domain\Query\Template;

use AppBundle\Entity\Template;

class TemplateDataProvider
{
    private $getTemplateHandler;

    public function __construct(GetTemplateHandler $getTemplateHandler)
    {
        $this->getTemplateHandler = $getTemplateHandler;
    }

    public function supports(string $resourceClass): bool
    {
        return Template::class === $resourceClass;
    }

    public function getItem(string $resourceClass, string $identifier)
    {
        if (!$this->supports($resourceClass)) {
            throw new \Exception("ResourceClassNotSupportedException");
        }

        $template  = $this->getTemplateTypeHandler->handle((new GetTemplateQuery())->setId($identifier));

        return $template;
    }
}

TemplateHandler

<?php

namespace AppBundle\Domain\Query\Template;

use Doctrine\ORM\EntityManagerInterface;
use AppBundle\Entity\Template;
use AppBundle\Exception\InputException;

class GetTemplateHandler
{
    /** @var EntityManagerInterface */
    private $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function handle(GetTemplateQuery $templateQuery): Template
    {
        $templateRepository = $this->entityManager->getRepository(Template::class);
        $template = $templateRepository->findOneBy(['id' => $templateQuery->getId()]);

        if(!$template instanceof Template) {
            throw InputException::create(
                InputException::EMAIL_NOT_FOUND,
                [
                    \sprintf(
                        "Unable to find template with id %s",
                        $templateQuery->getId()
                    )
                ]
            );
        }

        return $template;
    }
}

GetTemplateQuery

<?php

namespace AppBundle\ing\Domain\Query\Template;

class GetTemplateQuery
{
    private $id;

    public function getId(): string
    {
        return $this->id;
    }

    public function setId(string $id): self
    {
        $this->id = $id;

        return $this;
    }
}

Et voila :). have Fun

Tweet
Recent Posts
  • Introduction
  • CQRS and Domain Driven Development implementation.
  • Command part Code
    • Create Template Controller
    • Create Template Command
    • CreateTemplateHandler
  • Query Part
    • GetTemplateDataProvider
    • TemplateHandler
    • GetTemplateQuery
Ramatou Adamou Issa
Ramatou Adamou Issa
HomeExperiencesFormation
Social Networks
InstagramTwitterChat
Accounts
Gallery photoBlogGitHubStar
Copyright © 2025 Ramazaki