Getting Started with RPC
Send your first KubeMQ command and query in 5 minutes.
Prerequisites: KubeMQ server running on localhost:50000 and your SDK installed. See Getting Started for setup.
What You Will Build
A command sender that creates an order and a query sender that retrieves order status — each paired with a responder that handles the request and returns a response.
Steps
Install the SDK
go get github.com/kubemq-io/kubemq-go/v2pip install kubemqnpm install kubemq-js<dependency>
<groupId>io.kubemq.sdk</groupId>
<artifactId>kubemq-sdk-Java</artifactId>
<version>2.1.1</version>
</dependency>dotnet add package KubeMQ.SDK.CSharpimplementation("io.kubemq.sdk:kubemq-sdk-kotlin:2.1.0")vcpkg install kubemq[dependencies]
kubemq = "1.0"
tokio = { version = "1", features = ["full"] }gem install kubemqdef deps do
[{:kubemq, "~> 1.0"}]
endCreate a Command Responder
Start the responder first. It subscribes to incoming commands on orders.process, processes them, and sends back an execution status.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/kubemq-io/kubemq-go/v2"
)
func main() {
ctx := context.Background()
client, err := kubemq.NewClient(ctx,
kubemq.WithAddress("localhost", 50000),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
sub, err := client.SubscribeToCommands(ctx, "orders.process", "",
kubemq.WithOnCommandReceive(func(cmd *kubemq.CommandReceive) {
fmt.Printf("Received command: %s\n", cmd.Body)
resp := kubemq.NewCommandReply().
SetRequestId(cmd.Id).
SetResponseTo(cmd.ResponseTo).
SetExecutedAt(time.Now())
_ = client.SendCommandResponse(ctx, resp)
}),
kubemq.WithOnError(func(err error) {
log.Println("Error:", err)
}),
)
if err != nil {
log.Fatal(err)
}
defer sub.Unsubscribe()
fmt.Println("Command responder listening on 'orders.process'...")
<-ctx.Done()
}import time
from kubemq.cq import Client as CQClient
from kubemq.cq import CommandsSubscription, CommandReceived, CommandResponse, CancellationToken
def on_command(request: CommandReceived) -> None:
print(f"Received command: {request.body.decode('utf-8')}")
client.send_response_message(
CommandResponse(command_received=request, is_executed=True)
)
def on_error(err):
print(f"Error: {err}")
client = CQClient(address="localhost:50000")
cancel = CancellationToken()
client.subscribe_to_commands(
subscription=CommandsSubscription(
channel="orders.process",
on_receive_command_callback=on_command,
on_error_callback=on_error,
),
cancel=cancel,
)
print("Command responder listening on 'orders.process'...")
time.sleep(3600)const { KubeMQClient } = require("kubemq-js");
const client = new KubeMQClient({ address: "localhost:50000" });
client.subscribeToCommands({
channel: "orders.process",
onCommand: (cmd) => {
console.log("Received command:", Buffer.from(cmd.body).toString());
client.sendCommandResponse({ requestId: cmd.id, isExecuted: true });
},
onError: (err) => console.error("Error:", err.message),
});
console.log("Command responder listening on 'orders.process'...");CQClient client = CQClient.builder()
.address("localhost:50000")
.clientId("order-responder")
.build();
client.subscribeToCommands(CommandsSubscription.builder()
.channel("orders.process")
.onReceiveCommandCallback(cmd -> {
System.out.println("Received: " + new String(cmd.getBody()));
return CommandResponseMessage.builder()
.requestId(cmd.getId())
.isExecuted(true)
.build();
})
.onErrorCallback(err -> System.err.println("Error: " + err.getMessage()))
.build());
System.out.println("Command responder listening on 'orders.process'...");
Thread.sleep(3600000);
client.close();await using var client = new KubeMQClient(new KubeMQClientOptions());
await client.ConnectAsync();
Console.WriteLine("Command responder listening on 'orders.process'...");
await foreach (var cmd in client.SubscribeToCommandsAsync(
new CommandsSubscription { Channel = "orders.process" }))
{
Console.WriteLine($"Received: {Encoding.UTF8.GetString(cmd.Body.Span)}");
await client.SendCommandResponseAsync(new CommandResponse
{
RequestId = cmd.Id,
IsExecuted = true
});
}val client = CQClient("localhost:50000")
client.subscribeToCommands(
channel = "orders.process",
onCommand = { cmd ->
println("Received command: ${String(cmd.body)}")
client.sendCommandResponse(
requestId = cmd.id, isExecuted = true
)
},
onError = { err -> System.err.println("Error: ${err.message}") }
)
println("Command responder listening on 'orders.process'...")
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) {
std::cout << "Received command: " << cmd.body << std::endl;
client.sendCommandResponse(cmd.id, true);
},
[](const std::string& err) {
std::cerr << "Error: " << err << std::endl;
}
);
std::cout << "Command responder listening on 'orders.process'..." << std::endl;
std::this_thread::sleep_for(std::chrono::hours(1));use kubemq::prelude::*;
use kubemq::CommandReplyBuilder;
use std::time::Duration;
#[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",
"",
move |cmd| {
let c = rc.clone();
Box::pin(async move {
println!("Received command: {}", String::from_utf8_lossy(&cmd.body));
let reply = CommandReplyBuilder::new()
.request_id(&cmd.id)
.response_to(&cmd.response_to)
.build();
tokio::spawn(async move {
let _ = c.send_command_response(reply).await;
});
})
},
None,
)
.await?;
println!("Command responder listening on 'orders.process'...");
tokio::signal::ctrl_c().await.ok();
sub.unsubscribe().await;
client.close().await?;
Ok(())
}require 'kubemq'
client = KubeMQ::CQClient.new(address: 'localhost:50000', client_id: 'order-responder')
cancel = KubeMQ::CancellationToken.new
sub = KubeMQ::CQ::CommandsSubscription.new(channel: 'orders.process')
client.subscribe_to_commands(sub, cancellation_token: cancel,
on_error: ->(e) { puts "Error: #{e.message}" }) do |cmd|
puts "Received command: #{cmd.body}"
response = KubeMQ::CQ::CommandResponseMessage.new(
request_id: cmd.id,
reply_channel: cmd.reply_channel,
executed: true
)
client.send_response(response)
end
puts "Command responder listening on 'orders.process'..."
cancel.wait{:ok, client} =
KubeMQ.Client.start_link(address: "localhost:50000", client_id: "order-responder")
{:ok, _sub} =
KubeMQ.Client.subscribe_to_commands(client, "orders.process",
on_command: fn cmd ->
IO.puts("Received command: #{cmd.body}")
KubeMQ.CommandReply.new(
request_id: cmd.id,
response_to: cmd.reply_channel,
executed: true
)
end,
on_error: fn err -> IO.puts("Error: #{err.message}") end
)
IO.puts("Command responder listening on 'orders.process'...")
Process.sleep(:infinity)Send a Command
In a separate terminal, send a command. The sender blocks until the responder replies or the timeout expires.
ctx := context.Background()
client, err := kubemq.NewClient(ctx,
kubemq.WithAddress("localhost", 50000),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
resp, err := client.SendCommand(ctx, kubemq.NewCommand().
SetChannel("orders.process").
SetBody([]byte(`{"action":"create","orderId":"ORD-1234"}`)).
SetTimeout(10 * time.Second))
if err != nil {
log.Fatal(err)
}
log.Printf("Executed: %v", resp.Executed)from kubemq.cq import Client as CQClient
from kubemq.cq import CommandMessage
with CQClient(address="localhost:50000") as client:
response = client.send_command(
CommandMessage(
channel="orders.process",
body=b'{"action":"create","orderId":"ORD-1234"}',
timeout_in_seconds=10,
)
)
print(f"Executed: {response.is_executed}")const { KubeMQClient } = require("kubemq-js");
const client = new KubeMQClient({ address: "localhost:50000" });
const response = await client.sendCommand({
channel: "orders.process",
body: Buffer.from(JSON.stringify({ action: "create", orderId: "ORD-1234" })),
timeoutInSeconds: 10,
});
console.log("Executed:", response.isExecuted);CQClient client = CQClient.builder()
.address("localhost:50000")
.clientId("order-sender")
.build();
CommandResponseMessage response = client.sendCommandRequest(
CommandMessage.builder()
.channel("orders.process")
.body("{\"action\":\"create\",\"orderId\":\"ORD-1234\"}".getBytes())
.timeout(10000)
.build());
System.out.println("Executed: " + response.isExecuted());
client.close();await using var client = new KubeMQClient(new KubeMQClientOptions());
await client.ConnectAsync();
var response = await client.SendCommandAsync(new CommandMessage
{
Channel = "orders.process",
Body = Encoding.UTF8.GetBytes("{\"action\":\"create\",\"orderId\":\"ORD-1234\"}"),
Timeout = TimeSpan.FromSeconds(10)
});
Console.WriteLine($"Executed: {response.IsExecuted}");val client = CQClient("localhost:50000")
val response = client.sendCommand(CommandMessage(
channel = "orders.process",
body = """{"action":"create","orderId":"ORD-1234"}""".toByteArray(),
timeout = 10000
))
println("Executed: ${response.isExecuted}")
client.close()#include <kubemq/client.h>
#include <iostream>
auto client = kubemq::CQClient("localhost:50000");
kubemq::CommandMessage cmd;
cmd.channel = "orders.process";
cmd.body = R"({"action":"create","orderId":"ORD-1234"})";
cmd.timeout = 10000;
auto response = client.sendCommand(cmd);
std::cout << "Executed: " << response.isExecuted << std::endl;use kubemq::prelude::*;
use kubemq::CommandBuilder;
use std::time::Duration;
let client = KubemqClient::builder()
.host("localhost")
.port(50000)
.build()
.await?;
let command = CommandBuilder::new()
.channel("orders.process")
.body(br#"{"action":"create","orderId":"ORD-1234"}"#.to_vec())
.timeout(Duration::from_secs(10))
.build();
let response = client.send_command(command).await?;
println!("Executed: {}", response.executed);
client.close().await?;require 'kubemq'
client = KubeMQ::CQClient.new(address: 'localhost:50000', client_id: 'order-sender')
msg = KubeMQ::CQ::CommandMessage.new(
channel: 'orders.process',
timeout: 10,
body: '{"action":"create","orderId":"ORD-1234"}'
)
result = client.send_command(msg)
puts "Executed: #{result.executed}"
client.close{:ok, client} =
KubeMQ.Client.start_link(address: "localhost:50000", client_id: "order-sender")
command =
KubeMQ.Command.new(
channel: "orders.process",
body: ~s({"action":"create","orderId":"ORD-1234"}),
timeout: 10_000
)
{:ok, response} = KubeMQ.Client.send_command(client, command)
IO.puts("Executed: #{response.executed}")
KubeMQ.Client.close(client)Expected output (sender): Executed: true
Expected output (responder): Received command: {"action":"create","orderId":"ORD-1234"}
Create a Query Responder
Queries work like commands but the responder can include data in the response body. Set up a query responder on orders.lookup.
sub, err := client.SubscribeToQueries(ctx, "orders.lookup", "",
kubemq.WithOnQueryReceive(func(query *kubemq.QueryReceive) {
fmt.Printf("Query for: %s\n", query.Body)
resp := kubemq.NewQueryReply().
SetRequestId(query.Id).
SetResponseTo(query.ResponseTo).
SetBody([]byte(`{"orderId":"ORD-1234","status":"shipped","total":99.99}`)).
SetExecutedAt(time.Now())
_ = client.SendQueryResponse(ctx, resp)
}),
kubemq.WithOnError(func(err error) {
log.Println("Error:", err)
}),
)from kubemq.cq import QueriesSubscription, QueryReceived, QueryResponse
def on_query(request: QueryReceived) -> None:
print(f"Query for: {request.body.decode('utf-8')}")
client.send_response_message(
QueryResponse(
query_received=request,
is_executed=True,
body=b'{"orderId":"ORD-1234","status":"shipped","total":99.99}',
)
)
client.subscribe_to_queries(
subscription=QueriesSubscription(
channel="orders.lookup",
on_receive_query_callback=on_query,
on_error_callback=on_error,
),
cancel=cancel,
)client.subscribeToQueries({
channel: "orders.lookup",
onQuery: (query) => {
console.log("Query for:", Buffer.from(query.body).toString());
client.sendQueryResponse({
requestId: query.id,
isExecuted: true,
body: Buffer.from(
JSON.stringify({ orderId: "ORD-1234", status: "shipped", total: 99.99 })
),
});
},
onError: (err) => console.error("Error:", err.message),
});client.subscribeToQueries(QueriesSubscription.builder()
.channel("orders.lookup")
.onReceiveQueryCallback(query -> {
System.out.println("Query for: " + new String(query.getBody()));
return QueryResponseMessage.builder()
.requestId(query.getId())
.isExecuted(true)
.body("{\"orderId\":\"ORD-1234\",\"status\":\"shipped\",\"total\":99.99}".getBytes())
.build();
})
.onErrorCallback(err -> System.err.println("Error: " + err.getMessage()))
.build());await foreach (var query in client.SubscribeToQueriesAsync(
new QueriesSubscription { Channel = "orders.lookup" }))
{
Console.WriteLine($"Query for: {Encoding.UTF8.GetString(query.Body.Span)}");
await client.SendQueryResponseAsync(new QueryResponse
{
RequestId = query.Id,
IsExecuted = true,
Body = Encoding.UTF8.GetBytes(
"{\"orderId\":\"ORD-1234\",\"status\":\"shipped\",\"total\":99.99}")
});
}client.subscribeToQueries(
channel = "orders.lookup",
onQuery = { query ->
println("Query for: ${String(query.body)}")
client.sendQueryResponse(
requestId = query.id,
isExecuted = true,
body = """{"orderId":"ORD-1234","status":"shipped","total":99.99}""".toByteArray()
)
},
onError = { err -> System.err.println("Error: ${err.message}") }
)client.subscribeToQueries("orders.lookup", "",
[&client](const kubemq::QueryReceive& query) {
std::cout << "Query for: " << query.body << std::endl;
client.sendQueryResponse(query.id, true,
R"({"orderId":"ORD-1234","status":"shipped","total":99.99})");
},
[](const std::string& err) {
std::cerr << "Error: " << err << std::endl;
}
);use kubemq::QueryReplyBuilder;
let rc = client.clone();
let sub = client
.subscribe_to_queries(
"orders.lookup",
"",
move |query| {
let c = rc.clone();
Box::pin(async move {
println!("Query for: {}", String::from_utf8_lossy(&query.body));
let reply = QueryReplyBuilder::new()
.request_id(&query.id)
.response_to(&query.response_to)
.body(br#"{"orderId":"ORD-1234","status":"shipped","total":99.99}"#.to_vec())
.build();
tokio::spawn(async move {
let _ = c.send_query_response(reply).await;
});
})
},
None,
)
.await?;sub = KubeMQ::CQ::QueriesSubscription.new(channel: 'orders.lookup')
client.subscribe_to_queries(sub, cancellation_token: cancel,
on_error: ->(e) { puts "Error: #{e.message}" }) do |query|
puts "Query for: #{query.body}"
response = KubeMQ::CQ::QueryResponseMessage.new(
request_id: query.id,
reply_channel: query.reply_channel,
executed: true,
body: '{"orderId":"ORD-1234","status":"shipped","total":99.99}'
)
client.send_response(response)
end{:ok, _sub} =
KubeMQ.Client.subscribe_to_queries(client, "orders.lookup",
on_query: fn query ->
IO.puts("Query for: #{query.body}")
KubeMQ.QueryReply.new(
request_id: query.id,
response_to: query.reply_channel,
executed: true,
body: ~s({"orderId":"ORD-1234","status":"shipped","total":99.99})
)
end,
on_error: fn err -> IO.puts("Error: #{err.message}") end
)Send a Query
Send a query to retrieve order data. Unlike commands, the response body is preserved.
resp, err := client.SendQuery(ctx, kubemq.NewQuery().
SetChannel("orders.lookup").
SetBody([]byte("ORD-1234")).
SetTimeout(10 * time.Second))
if err != nil {
log.Fatal(err)
}
log.Printf("Order data: %s", resp.Body)from kubemq.cq import Client as CQClient
from kubemq.cq import QueryMessage
with CQClient(address="localhost:50000") as client:
response = client.send_query(
QueryMessage(
channel="orders.lookup",
body=b"ORD-1234",
timeout_in_seconds=10,
)
)
print(f"Order data: {response.body.decode('utf-8')}")const response = await client.sendQuery({
channel: "orders.lookup",
body: Buffer.from("ORD-1234"),
timeoutInSeconds: 10,
});
console.log("Order data:", Buffer.from(response.body).toString());QueryResponseMessage response = client.sendQueryRequest(
QueryMessage.builder()
.channel("orders.lookup")
.body("ORD-1234".getBytes())
.timeout(10000)
.build());
System.out.println("Order data: " + new String(response.getBody()));var response = await client.SendQueryAsync(new QueryMessage
{
Channel = "orders.lookup",
Body = Encoding.UTF8.GetBytes("ORD-1234"),
Timeout = TimeSpan.FromSeconds(10)
});
Console.WriteLine($"Order data: {Encoding.UTF8.GetString(response.Body.Span)}");val response = client.sendQuery(QueryMessage(
channel = "orders.lookup",
body = "ORD-1234".toByteArray(),
timeout = 10000
))
println("Order data: ${String(response.body)}")kubemq::QueryMessage query;
query.channel = "orders.lookup";
query.body = "ORD-1234";
query.timeout = 10000;
auto response = client.sendQuery(query);
std::cout << "Order data: " << response.body << std::endl;use kubemq::QueryBuilder;
use std::time::Duration;
let query = QueryBuilder::new()
.channel("orders.lookup")
.body(b"ORD-1234".to_vec())
.timeout(Duration::from_secs(10))
.build();
let response = client.send_query(query).await?;
println!("Order data: {}", String::from_utf8_lossy(&response.body));msg = KubeMQ::CQ::QueryMessage.new(
channel: 'orders.lookup',
timeout: 10,
body: 'ORD-1234'
)
result = client.send_query(msg)
puts "Order data: #{result.body}"query =
KubeMQ.Query.new(
channel: "orders.lookup",
body: "ORD-1234",
timeout: 10_000
)
{:ok, response} = KubeMQ.Client.send_query(client, query)
IO.puts("Order data: #{response.body}")Unlike commands, query responses preserve the full response body and metadata. This makes queries ideal for data retrieval operations.
Run the Example
- Start the responder in one terminal (handles both commands and queries)
- Run the command sender in a separate terminal
- Run the query sender in a separate terminal
- Observe the responses in each terminal
What Just Happened
- The responder subscribed to both command and query channels
- The sender sent a command — the responder processed it and returned
Executed: true(response body stripped) - The sender sent a query — the responder returned the full order data in the response body (preserved)
Commands vs Queries Summary
| Aspect | Command | Query |
|---|---|---|
| Response body | Stripped (always nil) | Preserved |
| Response metadata | Stripped | Preserved |
| CacheHit field | Stripped | Preserved |
| Use for | Writes, mutations, actions | Reads, lookups, data retrieval |
Next Steps
Was this page helpful?