Subscribe to PHP Freaks RSS

You may not need a query bus

syndicated from on June 27, 2019

"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 nee

Truncated by Planet PHP, read more at the original (another 2987 bytes)