Skip to content

Command, Query or Event. What's the difference?

Published:

Understanding Message Types in Event-Driven Architecture: Beyond Just Events

If you’ve been following our series on event-driven architecture, you’ve probably noticed that we talk about events… a lot. And for good reason! Events are fundamental to building modern, distributed systems. But here’s something interesting that my AWS colleague Rajie Banery recently pointed out to me: while events are crucial, they’re just one piece of a bigger puzzle called messaging.

The Big Picture: What is Messaging?

Think of messaging as the language your services use to talk to each other. Just like human communication doesn’t consist of only sharing news (“events”), your services need different ways to communicate depending on what they’re trying to achieve.

Messaging can happen in various ways within your system:

Now, you might be thinking, “That’s great, but I’m building an event-driven system. Why should I care about other types of messages?” It’s a fair question! While building a purely event-driven system is possible, it’s also challenging. At some point, you’ll likely need different types of communication – and that’s perfectly okay.

Breaking Down Message Types

Let’s explore the three main types of messages you’ll encounter: queries, commands, and events. Each serves a specific purpose, and understanding when to use each one will make your system more robust and maintainable.

1. Queries: Getting the Data You Need

Queries are probably the most familiar type of message for most developers. They’re all about retrieving information. Imagine your frontend needs to display a customer’s order history. It needs to ask your backend, “Hey, can you give me all of James’s orders?”

Traditionally, queries are synchronous – your frontend makes an HTTP GET request and waits for the response. This makes sense because the UI can’t really do anything until it has the data to display.

But here’s something interesting: queries don’t always have to be synchronous. You can implement asynchronous queries using callbacks. For example, your order service might need customer information from the customer service. Instead of making a direct call, it could:

  1. Send a query message to a queue
  2. Include a return URL or callback queue
  3. Let the customer service process the request at its own pace
  4. Receive the response through the callback mechanism

While this pattern isn’t always the best choice (sometimes a simple HTTP request is just fine), it’s a powerful tool to have in your toolkit.

2. Commands: Making Things Happen

Commands are about telling services to do something. Think of them as the “please do this” messages in your system. They’re often seen at system boundaries, like when a user submits an order through your frontend.

What makes commands interesting is their relationship with events. Often, a command will trigger one or more events. For example:

  1. Frontend sends a “Submit Order” command to the backend
  2. Backend processes the command
  3. Backend publishes an “Order Created” event
  4. Other services (like shipping or notifications) react to this event

While commands at system boundaries (like API endpoints) are often synchronous, internal commands between services can be asynchronous. Your order service might send an “Add Loyalty Points” command to a queue that the loyalty service monitors, rather than calling it directly.

3. Events: Sharing What Happened

We’ve covered events extensively in previous posts, but let’s put them in context. Events are notifications about something that has happened. They’re the “FYI” messages of your system.

What’s fascinating about events is how they often follow commands. A command comes in, something happens, and an event (or several) goes out. This pattern creates a nice separation of concerns:

Understanding System Boundaries

One of the most important concepts to grasp is the role of system boundaries in choosing message types. You’ll often find that:

When designing your APIs, consider what type of message makes the most sense. Should it be:

The answer depends on your specific needs, but understanding these patterns gives you more options.

Wrapping Up

While events are fantastic and should often be your go-to choice in an event-driven system, don’t feel bad about using commands and queries when they make sense. The goal isn’t to make everything event-driven – it’s to build a system that’s maintainable, scalable, and does what it needs to do.

Remember:

As you continue building your event-driven systems, keep these patterns in mind. Sometimes an event is exactly what you need. Other times, a good old-fashioned command or query might be the better choice. And that’s perfectly fine!

What patterns have you found most useful in your systems? How do you decide when to use each type of message? Share your thoughts in the comments below!


Stay tuned for more posts in our event-driven architecture series. Next time, we’ll dive deeper into implementing asynchronous queries using callbacks. Don’t forget to subscribe to get notified when new articles are published!