Handle Commands
Build a command responder that processes incoming requests and sends execution status.
What You Will Build
An order processing handler that receives commands, executes business logic, and sends back success or failure responses.
How It Works
A responder subscribes to a command channel, processes each incoming command, and replies with an execution result. KubeMQ correlates the reply back to the blocked sender.
Responder flow: KubeMQ routes each command to the handler and returns its execution result to the waiting sender.
Steps
Subscribe to Commands
Subscribe to the orders.process channel to receive incoming commands. Optionally specify a group name for load balancing across multiple responders.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/kubemq-io/kubemq-go/v2"
)
type OrderCommand struct {
Action string `json:"action"`
OrderID string `json:"orderId"`
}
func main() {
ctx := context.Background()
client, err := kubemq.NewClient(ctx,
kubemq.WithAddress("localhost", 50000),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
_, err = client.SubscribeToCommands(ctx, "orders.process", "",
kubemq.WithOnCommandReceive(func(cmd *kubemq.CommandReceive) {
handleCommand(ctx, client, cmd)
}),
kubemq.WithOnError(func(err error) {
log.Println("Subscription error:", err)
}),
)
if err != nil {
log.Fatal(err)
}
fmt.Println("Command handler ready...")
<-ctx.Done()
}import json
import time
from kubemq.cq import (
Client as CQClient, CommandsSubscription,
CommandReceived, CommandResponse, CancellationToken,
)
client = CQClient(address="localhost:50000")
cancel = CancellationToken()
def handle_command(request: CommandReceived) -> None:
order = json.loads(request.body)
print(f"Handling {order['action']} for {order['orderId']}")
# Process the command (see next step)
client.send_response_message(
CommandResponse(command_received=request, is_executed=True)
)
client.subscribe_to_commands(
subscription=CommandsSubscription(
channel="orders.process",
on_receive_command_callback=handle_command,
on_error_callback=lambda e: print(f"Subscription error: {e}"),
),
cancel=cancel,
)
print("Command handler ready...")
time.sleep(3600)const { KubeMQClient } = require("kubemq-js");
const client = new KubeMQClient({ address: "localhost:50000" });
client.subscribeToCommands({
channel: "orders.process",
onCommand: (cmd) => handleCommand(client, cmd),
onError: (err) => console.error("Subscription error:", err.message),
});
console.log("Command handler ready...");CQClient client = CQClient.builder()
.address("localhost:50000")
.clientId("order-handler")
.build();
client.subscribeToCommands(CommandsSubscription.builder()
.channel("orders.process")
.onReceiveCommandCallback(cmd -> handleCommand(client, cmd))
.onErrorCallback(err ->
System.err.println("Subscription error: " + err.getMessage()))
.build());
System.out.println("Command handler ready...");
Thread.sleep(3600000);
client.close();await using var client = new KubeMQClient(new KubeMQClientOptions());
await client.ConnectAsync();
Console.WriteLine("Command handler ready...");
await foreach (var cmd in client.SubscribeToCommandsAsync(
new CommandsSubscription { Channel = "orders.process" }))
{
await HandleCommand(client, cmd);
}val client = CQClient("localhost:50000")
client.subscribeToCommands(
channel = "orders.process",
onCommand = { cmd -> handleCommand(client, cmd) },
onError = { err -> System.err.println("Subscription error: ${err.message}") }
)
println("Command handler ready...")
Thread.sleep(3600000)
client.close()#include <kubemq/client.h>
#include <iostream>
#include <thread>
auto client = kubemq::CQClient("localhost:50000");
client.subscribeToCommands("orders.process", "",
[&client](const kubemq::CommandReceive& cmd) {
handleCommand(client, cmd);
},
[](const std::string& err) {
std::cerr << "Subscription error: " << err << std::endl;
}
);
std::cout << "Command handler ready..." << std::endl;
std::this_thread::sleep_for(std::chrono::hours(1));use kubemq::prelude::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct OrderCommand {
action: String,
#[serde(rename = "orderId")]
order_id: String,
}
#[tokio::main]
async fn main() -> kubemq::Result<()> {
let client = KubemqClient::builder()
.host("localhost")
.port(50000)
.build()
.await?;
let rc = client.clone();
let _sub = client
.subscribe_to_commands(
"orders.process",
"", // group: "" for no load balancing
move |cmd| {
let c = rc.clone();
Box::pin(async move { handle_command(c, cmd).await })
},
None,
)
.await?;
println!("Command handler ready...");
tokio::signal::ctrl_c().await.ok();
client.close().await?;
Ok(())
}require 'kubemq'
client = KubeMQ::CQClient.new(address: 'localhost:50000', client_id: 'order-handler')
cancel = KubeMQ::CancellationToken.new
sub = KubeMQ::CQ::CommandsSubscription.new(channel: 'orders.process')
client.subscribe_to_commands(
sub,
cancellation_token: cancel,
on_error: ->(e) { puts "Subscription error: #{e.message}" }
) do |cmd|
handle_command(client, cmd)
end
puts 'Command handler ready...'
cancel.wait{:ok, client} =
KubeMQ.Client.start_link(address: "localhost:50000", client_id: "order-handler")
# The on_command callback returns a CommandReply — the SDK sends it automatically
{:ok, _sub} =
KubeMQ.Client.subscribe_to_commands(client, "orders.process",
on_command: fn cmd -> handle_command(cmd) end,
on_error: fn err -> IO.puts("Subscription error: #{err.message}") end
)
IO.puts("Command handler ready...")
Process.sleep(:infinity)Process the Command
Parse the request body, execute business logic, and determine the result.
func handleCommand(ctx context.Context, client *kubemq.Client,
cmd *kubemq.CommandReceive) {
var order OrderCommand
if err := json.Unmarshal(cmd.Body, &order); err != nil {
sendError(ctx, client, cmd, "invalid request body")
return
}
switch order.Action {
case "create":
fmt.Printf("Creating order %s\n", order.OrderID)
case "cancel":
fmt.Printf("Cancelling order %s\n", order.OrderID)
default:
sendError(ctx, client, cmd, "unknown action: "+order.Action)
return
}
sendSuccess(ctx, client, cmd)
}def handle_command(request: CommandReceived) -> None:
try:
order = json.loads(request.body)
except json.JSONDecodeError:
send_error(request, "invalid request body")
return
action = order.get("action")
if action == "create":
print(f"Creating order {order['orderId']}")
elif action == "cancel":
print(f"Cancelling order {order['orderId']}")
else:
send_error(request, f"unknown action: {action}")
return
send_success(request)function handleCommand(client, cmd) {
let order;
try {
order = JSON.parse(Buffer.from(cmd.body).toString());
} catch {
sendError(client, cmd, "invalid request body");
return;
}
switch (order.action) {
case "create":
console.log(`Creating order ${order.orderId}`);
break;
case "cancel":
console.log(`Cancelling order ${order.orderId}`);
break;
default:
sendError(client, cmd, `unknown action: ${order.action}`);
return;
}
sendSuccess(client, cmd);
}private CommandResponseMessage handleCommand(CQClient client,
CommandReceive cmd) {
try {
var order = new Gson().fromJson(
new String(cmd.getBody()), OrderCommand.class);
switch (order.action()) {
case "create" -> System.out.println("Creating order " + order.orderId());
case "cancel" -> System.out.println("Cancelling order " + order.orderId());
default -> {
return errorResponse(cmd, "unknown action: " + order.action());
}
}
return successResponse(cmd);
} catch (Exception e) {
return errorResponse(cmd, "invalid request body");
}
}async Task HandleCommand(KubeMQClient client, CommandReceive cmd)
{
try
{
var order = JsonSerializer.Deserialize<OrderCommand>(cmd.Body.Span);
switch (order?.Action)
{
case "create": Console.WriteLine($"Creating order {order.OrderId}"); break;
case "cancel": Console.WriteLine($"Cancelling order {order.OrderId}"); break;
default: await SendError(client, cmd, $"unknown action: {order?.Action}"); return;
}
await SendSuccess(client, cmd);
}
catch
{
await SendError(client, cmd, "invalid request body");
}
}fun handleCommand(client: CQClient, cmd: CommandReceive) {
val order = try {
Json.decodeFromString<OrderCommand>(String(cmd.body))
} catch (e: Exception) {
sendError(client, cmd, "invalid request body")
return
}
when (order.action) {
"create" -> println("Creating order ${order.orderId}")
"cancel" -> println("Cancelling order ${order.orderId}")
else -> { sendError(client, cmd, "unknown action: ${order.action}"); return }
}
sendSuccess(client, cmd)
}void handleCommand(kubemq::CQClient& client,
const kubemq::CommandReceive& cmd) {
auto order = nlohmann::json::parse(cmd.body, nullptr, false);
if (order.is_discarded()) {
sendError(client, cmd, "invalid request body");
return;
}
auto action = order["action"].get<std::string>();
if (action == "create") {
std::cout << "Creating order " << order["orderId"] << std::endl;
} else if (action == "cancel") {
std::cout << "Cancelling order " << order["orderId"] << std::endl;
} else {
sendError(client, cmd, "unknown action: " + action);
return;
}
sendSuccess(client, cmd);
}async fn handle_command(client: KubemqClient, cmd: CommandReceive) {
let order: OrderCommand = match serde_json::from_slice(&cmd.body) {
Ok(o) => o,
Err(_) => return send_error(client, &cmd, "invalid request body").await,
};
match order.action.as_str() {
"create" => println!("Creating order {}", order.order_id),
"cancel" => println!("Cancelling order {}", order.order_id),
other => return send_error(client, &cmd, &format!("unknown action: {other}")).await,
}
send_success(client, &cmd).await;
}def handle_command(client, cmd)
order = begin
JSON.parse(cmd.body)
rescue JSON::ParserError
return send_error(client, cmd, 'invalid request body')
end
case order['action']
when 'create' then puts "Creating order #{order['orderId']}"
when 'cancel' then puts "Cancelling order #{order['orderId']}"
else return send_error(client, cmd, "unknown action: #{order['action']}")
end
send_success(client, cmd)
enddef handle_command(cmd) do
case Jason.decode(cmd.body) do
{:ok, %{"action" => "create", "orderId" => id}} ->
IO.puts("Creating order #{id}")
send_success(cmd)
{:ok, %{"action" => "cancel", "orderId" => id}} ->
IO.puts("Cancelling order #{id}")
send_success(cmd)
{:ok, %{"action" => action}} ->
send_error(cmd, "unknown action: #{action}")
{:error, _} ->
send_error(cmd, "invalid request body")
end
endSend Success Response
Return Executed: true to indicate the command was processed successfully.
func sendSuccess(ctx context.Context, client *kubemq.Client,
cmd *kubemq.CommandReceive) {
resp := kubemq.NewCommandReply().
SetRequestId(cmd.Id).
SetResponseTo(cmd.ResponseTo).
SetExecutedAt(time.Now())
_ = client.SendCommandResponse(ctx, resp)
}def send_success(request: CommandReceived) -> None:
client.send_response_message(
CommandResponse(command_received=request, is_executed=True)
)function sendSuccess(client, cmd) {
client.sendCommandResponse({ requestId: cmd.id, isExecuted: true });
}private CommandResponseMessage successResponse(CommandReceive cmd) {
return CommandResponseMessage.builder()
.requestId(cmd.getId())
.isExecuted(true)
.build();
}async Task SendSuccess(KubeMQClient client, CommandReceive cmd) =>
await client.SendCommandResponseAsync(new CommandResponse
{
RequestId = cmd.Id, IsExecuted = true
});fun sendSuccess(client: CQClient, cmd: CommandReceive) {
client.sendCommandResponse(requestId = cmd.id, isExecuted = true)
}void sendSuccess(kubemq::CQClient& client,
const kubemq::CommandReceive& cmd) {
client.sendCommandResponse(cmd.id, true);
}async fn send_success(client: KubemqClient, cmd: &CommandReceive) {
// A reply with no error means Executed: true
let reply = CommandReplyBuilder::new()
.request_id(&cmd.id)
.response_to(&cmd.response_to)
.build();
let _ = client.send_command_response(reply).await;
}def send_success(client, cmd)
response = KubeMQ::CQ::CommandResponseMessage.new(
request_id: cmd.id,
reply_channel: cmd.reply_channel,
executed: true
)
client.send_response(response)
end# The callback returns a CommandReply — the SDK sends it automatically
def send_success(cmd) do
KubeMQ.CommandReply.new(
request_id: cmd.id,
response_to: cmd.reply_channel,
executed: true
)
endSend Error Response
Return Executed: false with an error message when processing fails.
func sendError(ctx context.Context, client *kubemq.Client,
cmd *kubemq.CommandReceive, errMsg string) {
resp := kubemq.NewCommandReply().
SetRequestId(cmd.Id).
SetResponseTo(cmd.ResponseTo).
SetError(errMsg)
_ = client.SendCommandResponse(ctx, resp)
}def send_error(request: CommandReceived, error_msg: str) -> None:
client.send_response_message(
CommandResponse(
command_received=request,
is_executed=False,
error=error_msg,
)
)function sendError(client, cmd, errorMsg) {
client.sendCommandResponse({
requestId: cmd.id,
isExecuted: false,
error: errorMsg,
});
}private CommandResponseMessage errorResponse(CommandReceive cmd, String error) {
return CommandResponseMessage.builder()
.requestId(cmd.getId())
.isExecuted(false)
.error(error)
.build();
}async Task SendError(KubeMQClient client, CommandReceive cmd, string error) =>
await client.SendCommandResponseAsync(new CommandResponse
{
RequestId = cmd.Id, IsExecuted = false, Error = error
});fun sendError(client: CQClient, cmd: CommandReceive, errorMsg: String) {
client.sendCommandResponse(
requestId = cmd.id, isExecuted = false, error = errorMsg
)
}void sendError(kubemq::CQClient& client,
const kubemq::CommandReceive& cmd, const std::string& errorMsg) {
client.sendCommandResponse(cmd.id, false, errorMsg);
}async fn send_error(client: KubemqClient, cmd: &CommandReceive, err_msg: &str) {
// Setting an error marks the reply as not executed
let reply = CommandReplyBuilder::new()
.request_id(&cmd.id)
.response_to(&cmd.response_to)
.error(err_msg)
.build();
let _ = client.send_command_response(reply).await;
}def send_error(client, cmd, error_msg)
response = KubeMQ::CQ::CommandResponseMessage.new(
request_id: cmd.id,
reply_channel: cmd.reply_channel,
executed: false,
error: error_msg
)
client.send_response(response)
end# Return a CommandReply with executed: false — the SDK sends it automatically
def send_error(cmd, error_msg) do
KubeMQ.CommandReply.new(
request_id: cmd.id,
response_to: cmd.reply_channel,
executed: false,
error: error_msg
)
endResponder Best Practices
- Keep processing fast — the sender is blocking and waiting for your response
- Always send a response — if you don't respond, the sender will timeout (code 301)
- Use groups for scaling — multiple responders with the same group name share load via round-robin
- Handle unknown commands gracefully — return
Executed: falsewith a descriptive error
Next Steps
Was this page helpful?