Albertobeiz

Albertobeiz

Symfony Tips #20 - CQRS - Query Bus

Symfony Tips #20 - CQRS - Query Bus

Subscribe to my newsletter and never miss my upcoming articles

🖥 Symfony Tips: Quick and practical tricks to develop solid backend systems.

Quick one! Queries should also have their own bus.

This is OK

    #[Route('/events', name: 'Get_DomainEventsList', methods: ['GET'])]
    public function __invoke(): array
    {
        return $this->eventStore->createQueryBuilder('e')
            ->select('e')
            ->getQuery()
            ->getArrayResult();
    }

This is better

Add a query bus to messenger.yaml config

        default_bus: command.bus
        buses:
            query.bus:

            command.bus:
                middleware:
                    - doctrine_ping_connection
                    - doctrine_close_connection
                    - doctrine_transaction

            event.bus:
                default_middleware: allow_no_handlers
                middleware:
                    - doctrine_ping_connection
                    - doctrine_close_connection
                    - doctrine_transaction

Add Interfaces


interface QueryHandler
{
}

interface Query
{
}

Link each handler with its bus

services.yaml

    _instanceof:
        App\Modules\Shared\Infrastructure\QueryHandler:
            tags:
                - { name: messenger.message_handler, bus: query.bus }

We need a class for the QueryBus to be able to get the answer (We should create one also for the CommandBus).

class QueryBus
{
    use HandleTrait {
        handle as private handleQuery;
    }

    public function __construct(
        MessageBusInterface $queryBus
    )
    {
        $this->messageBus = $queryBus;
    }

    public function query(Query $query)
    {
        return $this->handleQuery($query);
    }
}

To move the domain logic inside our Modules we'll use a Query and a Query Handler

class QueryDomainEvents implements Query
{
}
class QueryDomainEventsHandler extends QueryHandler
{
    public function __construct(
        private EventStore $eventStore
    ){}

    public function __invoke(QueryDomainEvents $query): array
    {
        return $this->eventStore->createQueryBuilder('e')
            ->select('e')
            ->getQuery()
            ->getArrayResult();
    }
}

And we'll dispatch queries from our Api

class Get_DomainEventsList
{
    public function __construct(
        private QueryBus $queryBus
    ){}

    #[Route('/events', name: 'Get_DomainEventsList', methods: ['GET'])]
    public function __invoke(): array
    {
        return $this->queryBus->query(new QueryDomainEvents());
    }
}

Why?

As with Commands, we now have a single point to interact with our Domain.

Run test.sh and check that everything stays the same.

Symfony tip completed 👍! Check the final code and leave a ⭐️!

Next Tip -> Symfony Tips #21 - CQRS - Event Bus

Previous Tip -> Symfony Tips #19 - CQRS - Command Bus

HEY! Follow me at @albertobeiz if you found this tip useful or have any question.

Interested in reading more such articles from Alberto Beiz?

Support the author by donating an amount of your choice.

 
Share this