Get Started Free
‹ Back to courses
course: Designing Event-Driven Microservices

Commands, Queries, and Events

7 min
Wade Waldron

Wade Waldron

Staff Software Practice Lead

Commands, Queries, and Events

Overview

Messages between microservices come in three different flavors: Commands, Queries, and Events. Commands change the state, Queries request the state, and Events share the state. When designing microservice communication patterns, it is important to understand the role of these messages. Following a clear set of rules can help ensure consistency and clarity within your system of microservices.

Topics:

  • Commands
  • Queries
  • Events
  • Delta Events
  • Fact Events
  • Data Coupling

Resources

Use the promo codes MICRO101 & CONFLUENTDEV1 to get $25 of free Confluent Cloud usage and skip credit card entry.

Be the first to get updates and new content

We will only share developer content and updates, including notifications when new content is added. We will never send you sales emails. 🙂 By subscribing, you understand we will process your personal information in accordance with our Privacy Statement.

Commands, Queries, and Events

Hi, I'm Wade from Confluent.

When communicating between microservices, we divide the messages they send into three categories: Commands, Events, and Queries.

Each has a unique role to play that we'll discuss in detail.

In interface design, there is something known as the "Principle of Least Surprise".

It states that "a user should not be surprised by an interaction with the interface".

For example, if I choose to open a file in an application,

I would expect it to load the file.

If it also modified, or deleted the file, that would be surprising.

Following this principle leads to software that is more predictable, cleaner, and easier to use.

So how does this apply to microservices?

/slide

Communication with a microservice can be generalized as a set of messages.

Messages are the basic unit of communication, essentially a way to transfer data from one system to the other.

These messages break down into 3 categories, commands, queries, and events.

slide/

Commands are a request to change the state of a microservice.

Following the principle of least surprise, the change should reflect the intent of the command.

If we issue an "AddUser" command to the Users service,

it should add that user.

It shouldn't do anything unexpected, such as deleting the user.

However, commands can fail, or be rejected.

The sender will often wait for a response to see if the command succeeded.

These responses might contain details about the result, such as the User for an "AddUser" command.

But they can also be simple acknowledgments, or acks, basically just a message saying "I heard you."

There is some debate about the right amount of data to include.

Some developers prefer to return a detailed message.

Others prefer to send an ack.

One advantage to an ack is it hides the implementation details.

It allows the microservice choice in how it handles the command.

Rather than processing synchronously, it might just record the command,

reply with the ack,

and do the processing later.

This helps to decouple the two services so they aren't waiting on each other.

Once the state of the system has changed, we need a way to access it, especially if the command was processed asynchronously.

Queries are a request to obtain the current state of the system.

They are usually synchronous and always expect a detailed response.

For example, we could issue a "GetUser" query to obtain the user we created with the "AddUser" command.

Keeping with the principle of least surprise, queries never change the state.

When I issue a "GetUser" query, it would be surprising if the user was altered in some way.

Like with commands, the query should clearly reflect its intent.

Unfortunately, the synchronous nature of a query means we may be stuck waiting.

But, what if we don't want to wait?

Events are the result of a command and communicate changes to the system.

They are a reflection of something that happened in the past.

Events are often sent through tools such as Apache Kafka.

Following the principle of least surprise, the names of the events should reveal their intent.

They are often written as the past tense of the action that triggered them.

For example, for an "AddUser" command, the resulting event might be "UserAdded".

It's also important to recognize that events are not commands.

Downstream applications can listen for the events and make changes in response, but they don't have to.

They can choose to ignore any event they aren't interested in.

They still handle the event, they just don't do anything with it.

However, the event has already happened so listening applications can't reject it.

They have to acknowledge it, even if they choose to ignore it.

We may choose to ignore the lessons of history, but we can't deny that it happened.

Events should be handled asynchronously.

We go into this in more detail in our Asynchronous Events video which you can find linked in the comments.

The key point is that waiting for events creates coupling and we generally want to avoid that.

Events can also make commands and queries more asynchronous.

When issuing a command, rather than waiting for a response, we can listen for the resulting event.

This allows commands to work in a fully asynchronous fashion.

Similarly, rather than doing synchronous queries, we can listen to the events that record changes to the state.

We can use those events to create a local copy of the data, without having to perform an external query.

There are two types of events.

Delta events record only the details that changed in the system.

For example, a "ChangeUserAddress" command might result in a "UserAddressChanged" event.

It contains an identifier for the user, as well as details of the address change.

However, it doesn't contain other information such as the name, email address, or phone number.

Delta events are lightweight.

The small size means they use less network bandwidth and require less storage.

This makes them efficient to send through a platform such as Apache Kafka.

However, they might lack the information to be useful.

If we wanted the names of the people who changed their address, that might not be in the events.

We'd have to issue a query to the user service to obtain the missing details.

This creates additional traffic and can increase coupling.

Fact events, on the other hand, contain rich details about the object being modified.

The "UserAddressChanged" event could be augmented to contain all of the details of the user, including the name, email address, and phone number.

The rich data contained in a Fact event means we may not need separate queries.

The larger size requires more bandwidth, disk space, and processing time,

but in return, it reduces coupling.

When using fact events, we need to be careful to avoid data coupling.

Imagine that the "UserAddressChanged" event contains a field for the user's phone number.

If a downstream consumer deserializes the phone number,

then we have introduced coupling.

Changes to the data, such as converting the phone number to an array of phone numbers, would mean updating the deserializer.

A good practice is to ensure that you only deserialize the data you need, to avoid accidental coupling.

When we design a system, the key is to pay attention to the types of messages we are using and understand the role each one plays.

As a best practice, it is a good idea to favor asynchronous events for the bulk of the communication in order to reduce coupling.

That doesn't mean we can't use commands and queries, but we should limit them to where we really need them, and try to keep them asynchronous if possible.

There's a cost to making things synchronous and we should avoid paying it as long as we can.

Do you favor asynchronous events or do you prefer synchronous commands and queries?

Let me know in the comments below.

Meanwhile, you might want to check out some of our courses on Confluent Developer for more information on building event-driven microservices.

Please, like, share, and subscribe so we can keep the conversation going.

And thanks for watching.