Skip to content

Types of Messages in Message-Driven Systems

Differently shaped mailboxes
Photo by Mathyas Kurmann on Unsplash.

If you googled "event-driven architecture", I bet you'd find hundreds of detailed articles. Most of these articles would tell you how sending event messages between services enables asynchronous communication, makes components loosely coupled, allows them to be scaled easier, and even kisses you goodnight. If it sounds too good to be true, that's most likely because I've made up the last one. Sorry, where was I? Oh yes, as good as it sounds, having only event messages is not always enough. Using them too much (or abusing them) may result in bloated messages, unclear intent, or impossible-to-understand data flows. If it sounds too familiar or you'd rather avoid all of this, chances are you'd benefit from introducing different types of messages in your message-driven systems.

Different message types: event, document, command, query, and reply

Events

Event messages (later called events) provide data about facts that have already happened. As bizarre as it sounds, data is often less important than facts themselves.

Events should be named in the past tense. Event names should reflect what occurred.

{
    "UserId" : 149,
    "Username" : "Neo"
}
{
    "OrderId" : 7,
    "Comment" : "Should be delivered in two weeks"
}

Events can have 0 to n consumers. Event publishers are not aware of event consumers. Therefore event publishers are considered to be decoupled from event consumers. Moreover, event consumers are isolated from each other. Thus one consumer failing to process events doesn't impact other consumers.

Publishing events

Event schemas and definitions should usually be defined by event publishers.

Since events represent historical facts that already happened, consumers can't reject events. At least not without time travel.

Enstein rejecting event

Commands

Command messages (later called commands) provide data required to invoke specific actions.

Commands should be named according to the actions they intend to invoke.

{
    "RequestId" : "53e3cf85-9549-4022-bbdd-196bde9f6fef",
    "UserId" : 149,
    "Username" : "Neo"
}
{
    "RequestId" : "d2c5ad0a-825a-4529-9097-7c90423f451b",
    "OrderId" : 7,
    "Comment" : "Should be delivered in two weeks"
}

Each command can have many senders but only 1 consumer. Command senders are aware of command consumers. Therefore command senders are considered to be coupled to command consumers.

Sending commands

Command schemas and definitions should usually be defined by command consumers.

Since, unlike events, commands represent intents for invoking actions, consumers can reject commands. Both command rejections and acceptances are sent back to command senders as reply messages. However, in many cases, command consumers can skip sending acceptance replies if other events are published, implicitly informing command senders about successful command executions.

Queries

Query messages (later called queries) provide data required to retrieve information.

Queries should be named according to the information they intend to retrieve.

{
    "RequestId" : "0b19fe9e-46c7-4876-b56b-5c414fecf6f6",
    "UserId" : 149
}
{
    "RequestId" : "37f84037-4e48-44a0-9a7d-41f822a7bbbd",
    "SearchText" : "monitor 27 inch",
    "MinPrice" : 200,
    "MaxPrice" : 500
}

Each query can have many senders but only 1 consumer. Query senders are aware of query consumers. Therefore query senders are considered to be coupled to query consumers.

Sending queries

Query schemas and definitions should usually be defined by query consumers.

Consumers can reject queries. Both rejections and retrieved information are sent back to query producers as reply messages.

Replies

Reply messages (later called replies) respond to previous queries or commands (later called requests).

Successful query replies should be named according to queries. It's OK to have generic command acceptance and request rejection replies unless you need them explicitly linked to their requests.

{
    "RequestId" : "0b19fe9e-46c7-4876-b56b-5c414fecf6f6",
    "UserId" : 149,
    "Username" : "Neo",
    "Email" : [email protected],
    "ProfilePictureUrl" : "https://tenor.com/view/burn-in100-gif-25389111",
    "Address" : "Simulated reality"
}
{
    "RequestId" : "53e3cf85-9549-4022-bbdd-196bde9f6fef"
}
{
    "RequestId" : "53e3cf85-9549-4022-bbdd-196bde9f6fef",
    "Reason" : "Username is already taken",
    "ErrorCode" : "DUPLICATE_USERNAME"
}

Each reply has only 1 sender and 1 consumer. Reply addresses are usually sent together with original requests, which makes reply senders unaware of reply consumers. Therefore reply senders can be considered to be decoupled from reply consumers.

Reply schemas and definitions should usually be defined by reply senders.

Replies are responses to requests. While they can represent request rejections, replies themselves can't be rejected by consumers.

Documents

Document messages (later called documents) provide data and let receivers decide what to do with it.

Documents should be named according to the data they provide.

{
    "Id" : 1567,
    "Name" : "Shampoo 3-in-1",
    "Description" : "This 3-in-1 shampoo is a perfect fit for your hair, your dog, and washing dishes.",
    "Price" : 6.67,
    "Stock" : 29
}
{
    "Id" : 98,
    "MakeId" : 32,
    "ModelId" : 7,
    "TrimId" : 2,
    "Price" : 80000,
    "FeatureIds" : [ 7, 15, 27, 82]
}

Documents can have 0 to n consumers. Document publishers are not aware of document consumers. Therefore document publishers are considered to be decoupled from document consumers. Moreover, document consumers are isolated from each other. Thus one consumer failing to process documents doesn't impact other consumers.

Publishing documents

Document schemas and definitions should usually be defined by document publishers.

Since documents represent data or data changes, consumers can't reject documents.

Final Words

There are multiple distinct message types you could pick from when building message-driven systems. However, the most significant focus should be on building message flows that fit the core business domain and your system's design.

Thank you for reading. Is there something else that this post is missing? I'd love to hear about it in the comments.