Alberto Beiz
Albertobeiz

Albertobeiz

Symfony Tips #21 - CQRS - Event Bus

Symfony Tips #21 - CQRS - Event Bus

Subscribe to my newsletter and never miss my upcoming articles

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

Having a static EventBus is a very convenient and easy way of dispatching events but we are mixing different application layers and maybe having a global object that you can access anywhere in your code is not the best idea.

This is OK

    class User
{
    public function __construct(
        Uuid $uuid,
        string $username,
        string $email
    )
    {
        $this->setUuid($uuid);
        $this->setUsername($username);
        $this->setEmail($email);

        EventBus::dispatch(new UserCreated($uuid, $username, $email));
    }

This is better

Make your user extend a new AggregateRoot class

class User extends AggregateRoot
{
    public function __construct(
        Uuid $uuid,
        string $username,
        string $email
    )
    {
        $this->setUuid($uuid);
        $this->setUsername($username);
        $this->setEmail($email);

        $this->record(new UserCreated($uuid, $username, $email));
    }

Aggregate roots can record DomainEvents

abstract class AggregateRoot
{
    protected array $domainEvents = [];

    final public function getDomainEvents(): array
    {
        return $this->domainEvents;
    }

    final protected function record(DomainEvent $domainEvent): void
    {
        $this->domainEvents[] = $domainEvent;
    }
}

And now in your command handler you can make the dispatch

class CreateUserCommandHandler implements CommandHandler
{
    public function __construct(
        private UserRepository $userRepository,
        private MessageBusInterface $eventBus
    ){}

    public function __invoke(CreateUserCommand $command)
    {
        if ($this->userRepository->findOneBy(['email' => $command->email])) {
            throw new InvalidArgumentException('[Error] Email Already Exists');
        }

        $user = new User(
            $command->uuid,
            $command->username,
            $command->email
        );
        $this->userRepository->persist($user);

        foreach ($user->getDomainEvents() as $event) {
            $this->eventBus->dispatch($event);
        }
    }
}

Why?

To keep each responsibility at their layer. Entities shouldn't know there's an Event Bus, the same way they don't know there's a Repository.

That said I usually stick with the static Bus (it works and it's testable even if it's not "clean code") except if it's a greenfield project with an experienced Team.

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

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

Next Tip -> Symfony Tips #22 - CQRS - Projections

Previous Tip -> Symfony Tips #20 - CQRS - Query Bus

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

Did you find this article valuable?

Support Alberto Beiz by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
 
Share this