CQRS Implementation
Implement Command Query Responsibility Segregation with KubeMQ Commands and Queries.
Scenario
An e-commerce platform separates write and read operations: Commands create, update, and cancel orders in the write database, while Queries read order data from an optimized read projection. KubeMQ's built-in distinction between commands and queries maps directly to the CQRS pattern.
Architecture
Commands flow to the write side (orange) and mutate the write database; queries flow to the read side (amber) and serve from a synced projection.
Implementation
Command Handler (Writes)
The command handler subscribes to write operations, persists changes to the write database, and returns Executed: true.
_, err := client.SubscribeToCommands(ctx, "orders.write", "write-handlers",
kubemq.WithOnCommandReceive(func(cmd *kubemq.CommandReceive) {
var order map[string]interface{}
json.Unmarshal(cmd.Body, &order)
err := db.SaveOrder(order)
resp := kubemq.NewCommandReply().
SetRequestId(cmd.Id).
SetResponseTo(cmd.ResponseTo)
if err != nil {
resp.SetError(err.Error())
} else {
resp.SetExecutedAt(time.Now())
}
client.SendCommandResponse(ctx, resp)
}),
kubemq.WithOnError(func(err error) { log.Println("Error:", err) }),
)def on_command(request):
order = json.loads(request.body)
try:
db.save_order(order)
client.send_response_message(
CommandResponse(command_received=request, is_executed=True))
except Exception as e:
client.send_response_message(
CommandResponse(command_received=request, is_executed=False,
error=str(e)))
client.subscribe_to_commands(CommandsSubscription(
channel="orders.write", group="write-handlers",
on_receive_command_callback=on_command,
on_error_callback=lambda e: print(f"Error: {e}")),
cancel=cancel)client.subscribeToCommands({
channel: "orders.write",
group: "write-handlers",
onCommand: async (cmd) => {
const order = JSON.parse(new TextDecoder().decode(cmd.body));
try {
await db.saveOrder(order);
await client.sendCommandResponse({
id: cmd.id, replyChannel: cmd.replyChannel, executed: true,
});
} catch (err) {
await client.sendCommandResponse({
id: cmd.id, replyChannel: cmd.replyChannel,
executed: false, error: err.message,
});
}
},
onError: (err) => console.error("Error:", err.message),
});client.subscribeToCommands(CommandsSubscription.builder()
.channel("orders.write").group("write-handlers")
.onReceiveCommandCallback(cmd -> {
try {
var order = new Gson().fromJson(new String(cmd.getBody()), Order.class);
db.saveOrder(order);
return CommandResponseMessage.builder()
.requestId(cmd.getId()).isExecuted(true).build();
} catch (Exception e) {
return CommandResponseMessage.builder()
.requestId(cmd.getId()).isExecuted(false)
.error(e.getMessage()).build();
}
})
.onErrorCallback(err -> System.err.println("Error: " + err.getMessage()))
.build());await foreach (var cmd in client.SubscribeToCommandsAsync(
new CommandsSubscription { Channel = "orders.write", Group = "write-handlers" }))
{
try
{
var order = JsonSerializer.Deserialize<Order>(cmd.Body.Span);
await db.SaveOrderAsync(order!);
await client.SendCommandResponseAsync(new CommandResponse
{ RequestId = cmd.Id, IsExecuted = true });
}
catch (Exception ex)
{
await client.SendCommandResponseAsync(new CommandResponse
{ RequestId = cmd.Id, IsExecuted = false, Error = ex.Message });
}
}client.subscribeToCommands(
channel = "orders.write", group = "write-handlers",
onCommand = { cmd ->
try {
val order = Json.decodeFromString<Order>(String(cmd.body))
db.saveOrder(order)
client.sendCommandResponse(requestId = cmd.id, isExecuted = true)
} catch (e: Exception) {
client.sendCommandResponse(
requestId = cmd.id, isExecuted = false, error = e.message ?: "")
}
},
onError = { err -> System.err.println("Error: ${err.message}") }
)client.subscribeToCommands("orders.write", "write-handlers",
[&](const kubemq::CommandReceive& cmd) {
try {
auto order = nlohmann::json::parse(cmd.body);
db.saveOrder(order);
client.sendCommandResponse(cmd.id, true);
} catch (const std::exception& e) {
client.sendCommandResponse(cmd.id, false, e.what());
}
},
[](const std::string& err) { std::cerr << "Error: " << err << std::endl; }
);let rc = client.clone();
let sub = client
.subscribe_to_commands("orders.write", "write-handlers", move |cmd| {
let c = rc.clone();
Box::pin(async move {
let order: serde_json::Value =
serde_json::from_slice(&cmd.body).unwrap_or_default();
let reply = match db.save_order(&order) {
Ok(_) => CommandReplyBuilder::new()
.request_id(&cmd.id)
.response_to(&cmd.response_to)
.executed(true)
.build(),
Err(e) => CommandReplyBuilder::new()
.request_id(&cmd.id)
.response_to(&cmd.response_to)
.executed(false)
.error(&e.to_string())
.build(),
};
tokio::spawn(async move {
let _ = c.send_command_response(reply).await;
});
})
}, None)
.await?;sub = KubeMQ::CQ::CommandsSubscription.new(
channel: "orders.write", group: "write-handlers")
client.subscribe_to_commands(sub, cancellation_token: cancel,
on_error: ->(e) { puts "Error: #{e.message}" }) do |cmd|
order = JSON.parse(cmd.body)
begin
db.save_order(order)
response = KubeMQ::CQ::CommandResponseMessage.new(
request_id: cmd.id, reply_channel: cmd.reply_channel, executed: true)
rescue => e
response = KubeMQ::CQ::CommandResponseMessage.new(
request_id: cmd.id, reply_channel: cmd.reply_channel,
executed: false, error: e.message)
end
client.send_response(response)
end{:ok, sub} =
KubeMQ.Client.subscribe_to_commands(client, "orders.write",
group: "write-handlers",
on_command: fn cmd ->
order = Jason.decode!(cmd.body)
case Orders.save(order) do
:ok ->
KubeMQ.CommandReply.new(
request_id: cmd.id, response_to: cmd.reply_channel, executed: true)
{:error, reason} ->
KubeMQ.CommandReply.new(
request_id: cmd.id, response_to: cmd.reply_channel,
executed: false, error: reason)
end
end,
on_error: fn err -> IO.puts("Error: #{err.message}") end
)Query Handler (Reads)
The query handler reads from the optimized read projection and returns full data in the response body.
_, err := client.SubscribeToQueries(ctx, "orders.read", "read-handlers",
kubemq.WithOnQueryReceive(func(query *kubemq.QueryReceive) {
orderId := string(query.Body)
order, err := readDB.GetOrder(orderId)
resp := kubemq.NewQueryReply().
SetRequestId(query.Id).
SetResponseTo(query.ResponseTo)
if err != nil {
resp.SetError("order not found: " + orderId)
} else {
data, _ := json.Marshal(order)
resp.SetBody(data).SetExecutedAt(time.Now())
}
client.SendQueryResponse(ctx, resp)
}),
kubemq.WithOnError(func(err error) { log.Println("Error:", err) }),
)def on_query(request):
order_id = request.body.decode("utf-8")
order = read_db.get_order(order_id)
if order:
client.send_response_message(QueryResponse(
query_received=request, is_executed=True,
body=json.dumps(order).encode()))
else:
client.send_response_message(QueryResponse(
query_received=request, is_executed=False,
error=f"order not found: {order_id}"))
client.subscribe_to_queries(QueriesSubscription(
channel="orders.read", group="read-handlers",
on_receive_query_callback=on_query,
on_error_callback=lambda e: print(f"Error: {e}")),
cancel=cancel)client.subscribeToQueries({
channel: "orders.read",
group: "read-handlers",
onQuery: async (query) => {
const orderId = new TextDecoder().decode(query.body);
const order = await readDB.getOrder(orderId);
if (order) {
await client.sendQueryResponse({
id: query.id, replyChannel: query.replyChannel, executed: true,
body: new TextEncoder().encode(JSON.stringify(order)),
});
} else {
await client.sendQueryResponse({
id: query.id, replyChannel: query.replyChannel,
executed: false, error: `order not found: ${orderId}`,
});
}
},
onError: (err) => console.error("Error:", err.message),
});client.subscribeToQueries(QueriesSubscription.builder()
.channel("orders.read").group("read-handlers")
.onReceiveQueryCallback(query -> {
String orderId = new String(query.getBody());
var order = readDB.getOrder(orderId);
if (order != null) {
return QueryResponseMessage.builder()
.requestId(query.getId()).isExecuted(true)
.body(new Gson().toJson(order).getBytes()).build();
}
return QueryResponseMessage.builder()
.requestId(query.getId()).isExecuted(false)
.error("order not found: " + orderId).build();
})
.onErrorCallback(err -> System.err.println("Error: " + err.getMessage()))
.build());await foreach (var query in client.SubscribeToQueriesAsync(
new QueriesSubscription { Channel = "orders.read", Group = "read-handlers" }))
{
var orderId = Encoding.UTF8.GetString(query.Body.Span);
var order = await readDB.GetOrderAsync(orderId);
if (order is not null)
{
await client.SendQueryResponseAsync(new QueryResponse
{
RequestId = query.Id, IsExecuted = true,
Body = JsonSerializer.SerializeToUtf8Bytes(order)
});
}
else
{
await client.SendQueryResponseAsync(new QueryResponse
{
RequestId = query.Id, IsExecuted = false,
Error = $"order not found: {orderId}"
});
}
}client.subscribeToQueries(
channel = "orders.read", group = "read-handlers",
onQuery = { query ->
val orderId = String(query.body)
val order = readDB.getOrder(orderId)
if (order != null) {
client.sendQueryResponse(requestId = query.id, isExecuted = true,
body = Json.encodeToString(order).toByteArray())
} else {
client.sendQueryResponse(requestId = query.id, isExecuted = false,
error = "order not found: $orderId")
}
},
onError = { err -> System.err.println("Error: ${err.message}") }
)client.subscribeToQueries("orders.read", "read-handlers",
[&](const kubemq::QueryReceive& query) {
auto order = readDB.getOrder(query.body);
if (!order.empty()) {
client.sendQueryResponse(query.id, true, order);
} else {
client.sendQueryResponse(query.id, false, "",
"order not found: " + query.body);
}
},
[](const std::string& err) { std::cerr << "Error: " << err << std::endl; }
);let rc = client.clone();
let sub = client
.subscribe_to_queries("orders.read", "read-handlers", move |query| {
let c = rc.clone();
Box::pin(async move {
let order_id = String::from_utf8_lossy(&query.body).to_string();
let reply = match read_db.get_order(&order_id) {
Some(order) => QueryReplyBuilder::new()
.request_id(&query.id)
.response_to(&query.response_to)
.body(serde_json::to_vec(&order).unwrap())
.build(),
None => QueryReplyBuilder::new()
.request_id(&query.id)
.response_to(&query.response_to)
.executed(false)
.error(&format!("order not found: {}", order_id))
.build(),
};
tokio::spawn(async move {
let _ = c.send_query_response(reply).await;
});
})
}, None)
.await?;sub = KubeMQ::CQ::QueriesSubscription.new(
channel: "orders.read", group: "read-handlers")
client.subscribe_to_queries(sub, cancellation_token: cancel,
on_error: ->(e) { puts "Error: #{e.message}" }) do |query|
order_id = query.body
order = read_db.get_order(order_id)
response =
if order
KubeMQ::CQ::QueryResponseMessage.new(
request_id: query.id, reply_channel: query.reply_channel,
executed: true, body: order.to_json, metadata: "application/json")
else
KubeMQ::CQ::QueryResponseMessage.new(
request_id: query.id, reply_channel: query.reply_channel,
executed: false, error: "order not found: #{order_id}")
end
client.send_response(response)
end{:ok, sub} =
KubeMQ.Client.subscribe_to_queries(client, "orders.read",
group: "read-handlers",
on_query: fn query ->
order_id = query.body
case ReadStore.get_order(order_id) do
nil ->
KubeMQ.QueryReply.new(
request_id: query.id, response_to: query.reply_channel,
executed: false, error: "order not found: #{order_id}")
order ->
KubeMQ.QueryReply.new(
request_id: query.id, response_to: query.reply_channel,
executed: true, body: Jason.encode!(order),
metadata: "application/json")
end
end,
on_error: fn err -> IO.puts("Error: #{err.message}") end
)Client (Sends Both)
cmdResp, _ := client.SendCommand(ctx, kubemq.NewCommand().
SetChannel("orders.write").
SetBody([]byte(`{"orderId":"ORD-100","item":"Widget","qty":3}`)).
SetTimeout(10 * time.Second))
log.Printf("Create order — Executed: %v", cmdResp.Executed)
qryResp, _ := client.SendQuery(ctx, kubemq.NewQuery().
SetChannel("orders.read").
SetBody([]byte("ORD-100")).
SetTimeout(10 * time.Second))
log.Printf("Get order — Data: %s", qryResp.Body)cmd_resp = client.send_command(CommandMessage(
channel="orders.write",
body=b'{"orderId":"ORD-100","item":"Widget","qty":3}',
timeout_in_seconds=10))
print(f"Create order — Executed: {cmd_resp.is_executed}")
qry_resp = client.send_query(QueryMessage(
channel="orders.read", body=b"ORD-100", timeout_in_seconds=10))
print(f"Get order — Data: {qry_resp.body.decode('utf-8')}")const cmdResp = await client.sendCommand(createCommand({
channel: "orders.write",
body: JSON.stringify({ orderId: "ORD-100", item: "Widget", qty: 3 }),
timeoutInSeconds: 10,
}));
console.log("Create order — Executed:", cmdResp.executed);
const qryResp = await client.sendQuery(createQuery({
channel: "orders.read",
body: "ORD-100",
timeoutInSeconds: 10,
}));
console.log("Get order — Data:", new TextDecoder().decode(qryResp.body));var cmdResp = client.sendCommandRequest(CommandMessage.builder()
.channel("orders.write")
.body("{\"orderId\":\"ORD-100\",\"item\":\"Widget\",\"qty\":3}".getBytes())
.timeout(10000).build());
System.out.println("Create order — Executed: " + cmdResp.isExecuted());
var qryResp = client.sendQueryRequest(QueryMessage.builder()
.channel("orders.read").body("ORD-100".getBytes()).timeout(10000).build());
System.out.println("Get order — Data: " + new String(qryResp.getBody()));var cmdResp = await client.SendCommandAsync(new CommandMessage
{
Channel = "orders.write",
Body = Encoding.UTF8.GetBytes("{\"orderId\":\"ORD-100\",\"item\":\"Widget\",\"qty\":3}"),
Timeout = TimeSpan.FromSeconds(10)
});
Console.WriteLine($"Create order — Executed: {cmdResp.IsExecuted}");
var qryResp = await client.SendQueryAsync(new QueryMessage
{
Channel = "orders.read", Body = Encoding.UTF8.GetBytes("ORD-100"),
Timeout = TimeSpan.FromSeconds(10)
});
Console.WriteLine($"Get order — Data: {Encoding.UTF8.GetString(qryResp.Body.Span)}");val cmdResp = client.sendCommand(CommandMessage(
channel = "orders.write",
body = """{"orderId":"ORD-100","item":"Widget","qty":3}""".toByteArray(),
timeout = 10000))
println("Create order — Executed: ${cmdResp.isExecuted}")
val qryResp = client.sendQuery(QueryMessage(
channel = "orders.read", body = "ORD-100".toByteArray(), timeout = 10000))
println("Get order — Data: ${String(qryResp.body)}")kubemq::CommandMessage cmd;
cmd.channel = "orders.write";
cmd.body = R"({"orderId":"ORD-100","item":"Widget","qty":3})";
cmd.timeout = 10000;
auto cmdResp = client.sendCommand(cmd);
std::cout << "Create order — Executed: " << cmdResp.isExecuted << std::endl;
kubemq::QueryMessage query;
query.channel = "orders.read";
query.body = "ORD-100";
query.timeout = 10000;
auto qryResp = client.sendQuery(query);
std::cout << "Get order — Data: " << qryResp.body << std::endl;let command = CommandBuilder::new()
.channel("orders.write")
.body(br#"{"orderId":"ORD-100","item":"Widget","qty":3}"#.to_vec())
.timeout(Duration::from_secs(10))
.build();
let cmd_resp = client.send_command(command).await?;
println!("Create order — Executed: {}", cmd_resp.executed);
let query = QueryBuilder::new()
.channel("orders.read")
.body(b"ORD-100".to_vec())
.timeout(Duration::from_secs(10))
.build();
let qry_resp = client.send_query(query).await?;
println!("Get order — Data: {}", String::from_utf8_lossy(&qry_resp.body));cmd = KubeMQ::CQ::CommandMessage.new(
channel: "orders.write", timeout: 10_000,
body: '{"orderId":"ORD-100","item":"Widget","qty":3}')
cmd_resp = client.send_command(cmd)
puts "Create order — Executed: #{cmd_resp.executed}"
query = KubeMQ::CQ::QueryMessage.new(
channel: "orders.read", timeout: 10_000, body: "ORD-100")
qry_resp = client.send_query(query)
puts "Get order — Data: #{qry_resp.body}"command =
KubeMQ.Command.new(
channel: "orders.write",
body: ~s({"orderId":"ORD-100","item":"Widget","qty":3}),
timeout: 10_000)
{:ok, cmd_resp} = KubeMQ.Client.send_command(client, command)
IO.puts("Create order — Executed: #{cmd_resp.executed}")
query = KubeMQ.Query.new(channel: "orders.read", body: "ORD-100", timeout: 10_000)
{:ok, qry_resp} = KubeMQ.Client.send_query(client, query)
IO.puts("Get order — Data: #{qry_resp.body}")Production Considerations
Was this page helpful?