You may not need a query bus

Posted on by Matthias Noback

"Can you make a query bus with SimpleBus?" The question has been asked many times. I've always said no. Basically, because I didn't build in the option to return anything from a command handler. So a handler could never become a query handler, since a query will of course have to return something.

I've always thought that the demand for a query bus is just a sign of the need for symmetry. If you have command and query methods, then why not have command and query buses too? A desire for symmetry isn't a bad thing per se. Symmetry is attractive because it feels natural, and that feeling can serve as design feedback. For instance, you can use lack of symmetry to find out what aspect of a design is still missing, or to find alternative solutions.

Nonetheless, I think that we may actually not need a query bus at all.

The return type of a query bus is "mixed"

A command or query bus interface will look something like this:

interface Bus
{
    /**
     * @return mixed
     */
    public function handle(object $message);
}

A sample query and query handler would look like this:

final class GetExchangeRate
{
    // ...
}

final class GetExchangeRateHandler
{
    public function handle(GetExchangeRate $query): ExchangeRate
    {
        // ...
    }
}

When you pass an instance of GetExchangeRate to Bus::handle() it will eventually call GetExchangeRateHandler::handle() and return the value. But Bus::handle() has an unknown return type, which we would call "mixed". Now, you know that the return type is going to be ExchangeRate, but a compiler wouldn't know. Nor does your IDE.

// What type of value is `$result`?
$result = $bus->handle(new GetExchangeRate(/* ... */));

This situation reminds me of the problem of a service locator (or container, used as locator) that offers a generic method for retrieving services:

interface Container
{
    public function get(string $id): object;
}

You don't know what you're going to get until you get it. Still, you rely on it to return the thing you were expecting to get.

Implicit dependencies

This brings me to the next objection: if you know which service is going to answer your query, and what type the answer is going to be, why would you depend on another service?

If I see a service that needs an exchange rate, I would expect this service to have a dependency called ExchangeRateRepository, or ExchangeRateProvider, or anything else, but not a QueryBus, or even a Bus. I like to see what the actual dependencies of a service are.

final class CreateInvoice
{
    // What does this service need a `Bus` for?!

    public function __construct(Bus $bus)
    {
        // ...
    }
}

In fact, this argument is also valid for the command bus itself; we may not even need it, since there is one command handler for a given command. Why not call the handler directly? For the automatic database transaction wrapping the handler? I actually prefer dealing with the transaction in the repository implementation only. Automatic event dispatching? I do that manually in my application service.

Really, the main thing that I hope the command bus brought us, is a tendency to model use cases as application services, which are independent of an application's infrastructure. And I introduced the void return type for command handlers to prevent write model entities from ending up in the views. However, I've become much less dogmatic over the years: I happily return IDs of new entities from my application services these days.

No need for middleware

Actually, the idea of the command bus having middleware that could do things before or after executing the command handler, was pretty neat. Dealing with database transactions, dispatching events, logging, security checks, etc. However, middlewares also tend to hide important facts from the casual reader. One type of middleware is quite powerful nonetheless: one that serializes an incoming message and adds it to a queue for asynchronous processing. This works particularly well with commands, because they don't return anything anyway.

I'm not sure if any of these middleware solutions will be interesting for a query bus though. Queries shouldn't need to run within a database transaction. They won't dispatch events, they won't need logging, etc. In particular, they shouldn't need to be queued. That would not make a timely answer to your query likely.

A query handler that doesn't need middlewares, doesn't need a bus either. The only thing the bus can still do in that case is directly forward the query to the right handler. And, as I mentioned, if there's just one handler, and you wrote it, why not make it an explicit dependency and call it directly?

Suggested refactoring: Replace Query Bus with Service Dependency

It won't be a surprise that my advice is to replace usages of a query bus with a real service dependency. This gives you the following benefits:

  • Service dependencies will be explicit
  • Return types will be specific

The refactoring in case of the GetExchangeRate case looks as follows:

// Before:

final class GetExchangeRate
{
    public function __construct(Currency $from, Currency $to, Date $date)
    {
         // ...
    }
}

final class GetExchangeRateHandler
{
    public function handle(GetExchangeRate $query): ExchangeRate
    {
        // ...
    }
}

 // After:

final class ExchangeRateProvider
{
    public function getExchangeRateFor(Currency $from, Currency $to, Date $date): ExchangeRate
    {
        // ...
    }
}

Also, every service that used to depend on the bus for answering their GetExchangeRate query, would now depend on ExchangeRateProvider and it should get this dependency injected as a constructor argument.

final class CreateInvoice
{
    public function __construct(ExchangeRateProvider $exchangeRateProvider)
    {
        // ...
    }
}

Optional refactoring: Introduce Parameter Object

As you may have noticed, the constructor arguments of the query object are now the method arguments of getExchangeRateFor(). This means that we've applied the opposite of the Introduce Parameter Object refactoring. I find that in some cases it still pays to keep the query object. In particular if it's something that represents a complicated query, with multiple options for filtering, searching, limiting, etc. In that case, sometimes a builder can give quite elegant results:

final class Invoices
{
    public static function all(): self
    {
        // ...
    }

    public function createdAfter(Date $date): self
    {
        // ...
    }

    public function thatHaveBeenPaid(): self
    {
        // ...
    }
}

$this->invoiceRepository->find(
    Invoices::all()
        ->createdAfter(Date::fromString('2019-06-30'),
        ->thatHaveBeenPaid()
);
PHP command bus query bus SimpleBus
Comments
This website uses MailComments: you can send your comments to this post by email. Read more about MailComments, including suggestions for writing your comments (in HTML or Markdown).
Alexandre Hocquard

I actually prefer dealing with the transaction in the repository implementation only

Actually, validation and persistency of your aggregate should be wrapped in the same transaction.
Transactions are useful for:
- the atomiticity of the operations (and so, rollback)
- isolation, which mean consistency view of your data

Otherwise, you don't guarantee the consistency view of your data when you are doing validation in one/several queries that are in different transactions.

It's the same when fetching data for a read model with multiple queries by the way: you should wrapped it in a transaction. Otherwise, you could have an object that represents a state that never exists.

If you respect it, the bus pattern is useful because it avoids to start transaction everywhere.

Of course, you can sacrify consistency here. But I suppose it's barely something you want when dealing with the persistence of an aggregate. It's maybe more acceptable when dealing with read model.

Bruce Weirdan
Now, you know that the return type is going to be ExchangeRate, but a compiler wouldn't know. Nor does your IDE.

Of course IDE can figure it out, given it has proper plugins installed (like, Psalm plugin) and a little bit of code and docblock adjustments are added: https://psalm.dev/r/20a97c294e .

Dmitri Lakachauskis

What bugs me most is not an inability to typehint a return type from a bus (I can kind of "cast" the return type with @var statement), but the fact that I can't really know what kind of exceptions the application layer can throw.


public function view(string $id): CinemaDto
{
$query = new GetCinema($id);
try {
/** @var CinemaDto $result */
$result = $this->queryBus->handle($query);

return $result;

// No way to tell why we try to catch this exception.
} catch (CinemaNotFound $e) {
throw new NotFoundHttpException();
}
}

I mostly use direct service injection for queries, but I can understand the appeal of query buses for some people.

Roman O Dukuy

This is problem is not only query bus, you can throw error for command bus handler too and you still do not know why you need catch this exception. For myself, I resolve this situation next: I add @throw notification in my command class.

Olivier Laviale

I use dispatchers instead of buses, they don't leave the boundaries of the application. I have both a command dispatcher and a query dispatcher. The "command" and the "query" are both simple objects, internal representation of an input. The query object is used as a cache key and the command object is used to invalidate cache keys. The result of the query dispatcher goes straight to serialization, my action doesn't have to know what's being returned, it has only one job: create the proper "command" or "query" object. So yes, the query dispatcher is pretty important to me :)

I wrote an article about this over here: https://olvlvl.com/2018-01-...

Benr77

I know that you've written about this in an earlier blog post - injecting a repository into a controller to generate an ID before dispatching a command to e.g. CreateBooking (to be able to use the ID in a redirect without using a return value from the command bus).

This feels slightly wrong to me, injecting the whole repository just to get the ID before an entity is created, so instead I've sometimes used return values from commands (with the Symfony Messenger).

However, this also feels wrong because it suffers from exactly the same issues as you've described here with query buses having a mixed/unknown return type.

See Symfony Docs which describe best practice how to get a correctly typed return value https://symfony.com/doc/cur... but this involves creating a whole private method and depending on the envelope class etc. Not ideal at all!

You will have thought about this a lot more than I have been able to, but is there a better way? For commands I do use the bus middleware a lot but this bugs me so I've variously used a few options, all of which are partially unpalatable:

- don't bother needing the ID in the controller and redirect the the list view instead of the detail view for the new entity (i.e. compromise the ideal in the UI)

- in the handler, add the new ID to the command object itself, which then becomes available in the controller after the command has executed. Works but feels dirty. But you can typehint it :)

- generate the ID in the controller directly, instead of injecting the repository just for it's nextIdentity() method

As Luís Cobucci has mentioned, I'm also using the command bus pattern to provide good separation between my UI controllers and the application layer, which does enforce a good style I think.

Matthias Noback

Thanks for your comment. Yes, different options, depending on your needs.

In my current project, we're using application services which are pretty much handlers. When they create a new entity, they just return the ID. I still let the repository generate the ID, and because the service already has the repository: no problem. This keeps the controllers simple indeed. The added benefit of creating the ID upfront is maybe only useful if the bus might handle the command asynchronously, of if you allow external systems to generate identity as well. That would basically only work if the ID is a UUID, in which case that other system could provide the ID they want, and you just accept it.

Денис Черносов

"final class Invoices" looks like query builder

I agree that final quiery is not the same as initial query. And i need a chain of handlers/middlewares before DataProvider/Repository.

UserQueryObject -> QueryBuilder -> chain/bus of qb handlers/transformers -> FinalQueryObject -> DataProvider/Repository -> ResultObject

And after that ResultObject should be transformed/serialized with some metadata to some output format. This requires also chain of handlers.

Luís Cobucci

Humm... I've got mixed feelings here 😊

I can agree that one definitely doesn't need a query bus to fetch things from your command handlers or services. However, the sentence below is completely applicable for the read side of your software:

Really, the main thing that I hope the command bus brought us, is a tendency to model use cases as application services

If we see the message buses as entrance point from UI layer (controllers, CLI commands, etc) to Application layer, it makes total sense to use a query bus. Those exposed queries are also use cases.

And, yes, one may have middleware attached to a query bus: logging, authorisation, i18n, etc. It really depends on your needs.

Matthias Noback

Very good point. Agreed. I realize that I've written this with the assumption that you'll be using read models anyway, and these models will be the use cases you talk about as well (each should also be modeled to serve a specific use case). However, I still don't think you need a bus for that.

Also, if you prefer to use middleware, the bus may still be something for you. I'm actually hesitant to do anything else than logging in middlewares, because, as pointed out in this article, I feel like "important business things" end up being mixed with infrastructural things.