CQRS and Application Pipelines in Kotlin

James
The Startup
Published in
6 min readDec 23, 2020

There are plenty of stories out there telling us of the advantages of CQRS — The Command and Query Responsibility Segregation pattern. The simple version is that we should separate Commands (user requests that change the data) from Queries (user requests that request data or some calculation on it), and that the data model we use often shouldn’t be the same for the two cases. Queries should never cause a change in the state of the system — no database writes, no file access — while Commands should only do these things and should not return data.

For example, in a REST endpoint, a GET request is a query, while a POST would be a command. However, the two use the same data model. For many apps this is fine, but if you have an app that deals with a high volume of transactions (e.g. every time a user clicks it makes a data entry) and all queries are aggregates (how many users are based in Europe), using the same data model is not appropriate. You could have separated endpoints — one GET only and one that is POST only, at which point you have achieved CQRS without even thinking about it. But what about the internals of your application? How do you deal with these?

I’m a fan of application pipelines. An application pipeline (as I use the term) separates the incoming request from the code that handles that request. It also leaves the system in a good design with respect to SOLID principles, gives us easy to test methods, and, possibly most importantly, it’s fairly easy to read and understand.

We’re going to start with two interfaces, Command and Query. In a pure world a Command doesn’t need a return value, but in the real world it is useful to return small amounts of information such as success/error information, ID of newly created entities, etc. In a pure world queries also do not affect the state of the system, but in the real world logs will be written, caches may be updated. This means that functionally the two interfaces are identical, and the only difference is in intent. We’ll inherit a base interface called Request for ease of use in the pipeline code, but we should always implement Command and Query to make it clear which is intended.

The generic type tells us what the return type of the Request is, for example, a command that creates an entity and returns the entity’s id might have the type Command<Int> . Notice the use of out on the generic type, this allows us to use the type more flexibly, while restricting it to only being used as an output (see https://kotlinlang.org/docs/reference/generics.html)

When we get a request, we need to do something with it, so the next interface we’ll define is a Handler . This gets a little tricky with the generics, but we essentially want a handler to take a specific request (command or query), do some processing, and return a value of the request’s generic type.

We can now create a concrete example. For example, if we have an entity type which has an id and a name (the much overused Person type will do), we could query for a specific user with code like this:

Notice that I’ve used a data class for the actual request, and its fields are the request parameters. In fact, you can think of it as a method/function signature, with parameters and a return type. However, we can implement multiple handlers if we want, and test each independently.

How do we deal with getting the person from the database? Dependency injection. Suppose we have an interface that covers accessing the database (a repository interface), we can provide that to new instances of the Handler , if we change database in the future we need only change the implementation of the interface to the new version. You might be tempted to build the database access into the handler, but with the repository multiple handlers can reuse the database access code, as different queries might want to fetch the same data but perform different calculations on it. We can also provide a Mocked repository in order to test our handler.

Our updated example looks like this:

Obviously we should check that the id exists and handle other errors but that would obscure the example here.

We’ve now separated our requests from our handlers and we can show the difference in intent between a Command and a Query. We don’t yet have a pipeline, so that’s what we’ll build next.

We’re going to use Kotlin’s object keyword to create a singleton to manage our pipeline. We only want one of them, and we want easy access to it from throughout our code. There are other ways to do this, mostly using IoC containers but Kotlin’s singletons are easy for this case.

Our pipeline wants to be able to accept a command or query, and execute the relevant handler. Now we have a decision to make, do we want one handler to execute or have the option of many? This may depend on the application you’re writing, we’re going to go with many handlers for commands — so more behaviour can be added later without affecting existing handlers — and a single handler for queries — as we only want a single result and multiple handlers could get confusing.

Queries are easier, so our pipeline starts off looking like this:

Don’t panic. That mess looks confusing, but it’s fairly simple. There’s just a lot of generic types going on. inline and reified together like that tell the compiler to essentially copy and paste that function in wherever it’s used, rather than call it like a normal function (this is why queryHandlers cannot be private, it’s copied in so its access must be public). We need to use this trick as the JVM erases generic types, and we need the type information to register the handler. The dispatch method looks up the relevant handler and calls it.

Notice also we’ve used the @Suppress annotation. This gives two benefits:

  1. The compiler warning about this unchecked cast is removed — we know more than the compiler does, and we know the cast will succeed
  2. It tells all future developers that we’ve seen the warning, considered it, and disregarded it. If we ignored the warning without an annotation it would be left to future developers to work out why we ignored it

Thankfully, this is “write once” code. to use it looks like this:

You register the handler once and can call it from anywhere in your code (You shouldn’t, but you can). This does add a dependency on your pipeline to your code, but decouples any dependency on the handler implementation. (The pipeline dependency could be worked around with various DI techniques, but I find they remove readability and don’t overall add much benefit)

The Command can have a similar handler, only one that deals with a list. Once it’s finished the entire object looks like this (note I’ve moved the supress annotation to the object to avoid cluttering the code with it):

That’s it, our commands and queries are separated into a pipeline. Our dependencies have been broken down and handlers can each work with their own independent models. This structure stands well for extension in future too, for example, if you move to microservices, the pipeline implementation can be swapped for one that publishes Commands/Queries to a pub/sub system, and the subscriptions set up on the topics of that system, and there is no need to change any of the handler logic. We’ve also managed to gain the benefits of dependency injection without introducing a lot of complexity/IoC container/annotations.

Some final thoughts to bear in mind:

  • Think carefully before dispatching requests from inside a handler. It can be done, but can very quickly lead to an unmanageable knot of spaghetti code.¹
  • We made our request instances into data classes — which means their toString() method is provided for us. We should log every request that gets dispatched onto the pipeline, which will make debugging much easier when things go wrong.
  • In a threaded environment, we may wish to update our handler registration so that we register a function that returns a new instance, rather than returning an instance. This saves us from many threading woes, and can be defined as an anonymous function at the point of registration so we keep all of our dependency injection benefits.
  • We currently don’t have a good way of dealing with errors in our handlers. I would take a leaf from functional programming languages and make a sealed Result type, which could be OK with a value or Err with error information — and then use a Kotlin switch/match on the returned value from a command/query
  1. It works in an event-sourcing style system (e.g. kafka), where the event’s handler places the event into a stream, and also dispatches it so that the views can be updated. There can also be a handler to replay all events in such a system.

--

--