Calculator - Multi-Method Service
This example demonstrates a gRPC service with multiple methods and proper error handling.
What You'll Learn
- Defining a service with multiple RPC methods
- Implementing multiple handlers
- Using gRPC status codes for error handling
- Working with numeric proto types
Proto Definition
// calculator.proto
syntax = "proto3";
package calculator;
service Calculator {
rpc Add (CalculatorRequest) returns (CalculatorResponse);
rpc Subtract (CalculatorRequest) returns (CalculatorResponse);
rpc Multiply (CalculatorRequest) returns (CalculatorResponse);
rpc Divide (CalculatorRequest) returns (CalculatorResponse);
}
message CalculatorRequest {
double first_number = 1;
double second_number = 2;
}
message CalculatorResponse {
double result = 1;
}Server Implementation
using gRPCServer# Include generated typesinclude("generated/calculator/calculator.jl")using .calculator# Handlersfunction add(ctx::ServerContext, request::CalculatorRequest)::CalculatorResponse @info "Add" a=request.first_number b=request.second_number request_id=ctx.request_id CalculatorResponse(request.first_number + request.second_number)endfunction subtract(ctx::ServerContext, request::CalculatorRequest)::CalculatorResponse @info "Subtract" a=request.first_number b=request.second_number request_id=ctx.request_id CalculatorResponse(request.first_number - request.second_number)endfunction multiply(ctx::ServerContext, request::CalculatorRequest)::CalculatorResponse @info "Multiply" a=request.first_number b=request.second_number request_id=ctx.request_id CalculatorResponse(request.first_number * request.second_number)endfunction divide(ctx::ServerContext, request::CalculatorRequest)::CalculatorResponse @info "Divide" a=request.first_number b=request.second_number request_id=ctx.request_id if request.second_number == 0.0 throw(GRPCError(StatusCode.INVALID_ARGUMENT, "Division by zero")) end CalculatorResponse(request.first_number / request.second_number)end# Register the service with the codegen registration functionfunction main() server = GRPCServer("127.0.0.1", 50052; enable_health_check = true, enable_reflection = true ) register_Calculator!(server; Add = add, Subtract = subtract, Multiply = multiply, Divide = divide ) @info "Calculator gRPC server starting" host="127.0.0.1" port=50052 run(server)endmain()Key Concepts
Error Handling with Status Codes
Throw GRPCError to return appropriate status codes:
throw(GRPCError(StatusCode.INVALID_ARGUMENT, "Division by zero"))Common status codes:
INVALID_ARGUMENT- Client provided invalid inputNOT_FOUND- Requested resource doesn't existPERMISSION_DENIED- Client lacks permissionINTERNAL- Server-side errorUNAUTHENTICATED- Missing or invalid authentication
Multiple Methods in One Service
Register all four handlers with the generated aggregate function:
register_Calculator!(server; Add = add, Subtract = subtract, Multiply = multiply, Divide = divide)Each keyword accepts a handler or a (handler, raw_request, raw_response) tuple; handler signatures are validated at registration time.
Numeric Types
Proto double maps to Julia Float64. The generated types handle serialization automatically.
Testing
Run the Server
cd examples/05_calculator
julia --project=../.. server.jlNote: This example uses port 50052 (different from hello world examples).
Call Add from Julia
In a second terminal, same directory, using the generated client stub (call gRPCClient.grpc_init() once before any call):
using gRPCServerinclude("generated/calculator/calculator.jl")using .calculatorimport gRPCClientgRPCClient.grpc_init()client = calculator.Calculator_Add_Client("127.0.0.1", 50052)resp = gRPCClient.grpc_sync_request(client, calculator.CalculatorRequest(first_number=5, second_number=3))@assert resp.result == 8println("Got: ", resp.result)Test Add
grpcurl -plaintext -d '{"first_number": 10, "second_number": 5}' \
localhost:50052 calculator.Calculator/AddExpected output:
{
"result": 15
}Test Subtract
grpcurl -plaintext -d '{"first_number": 10, "second_number": 3}' \
localhost:50052 calculator.Calculator/SubtractExpected output:
{
"result": 7
}Test Multiply
grpcurl -plaintext -d '{"first_number": 7, "second_number": 6}' \
localhost:50052 calculator.Calculator/MultiplyExpected output:
{
"result": 42
}Test Divide
grpcurl -plaintext -d '{"first_number": 20, "second_number": 4}' \
localhost:50052 calculator.Calculator/DivideExpected output:
{
"result": 5
}Test Error Handling (Division by Zero)
grpcurl -plaintext -d '{"first_number": 10, "second_number": 0}' \
localhost:50052 calculator.Calculator/DivideExpected output:
ERROR:
Code: InvalidArgument
Message: Division by zeroBest Practices
Input Validation
Always validate inputs before processing:
function handler(ctx::ServerContext, request::RequestType) if !is_valid(request) throw(GRPCError(StatusCode.INVALID_ARGUMENT, "Invalid input: ...")) end # Process valid requestendConsistent Error Messages
Provide clear, actionable error messages:
# Goodthrow(GRPCError(StatusCode.INVALID_ARGUMENT, "Division by zero"))# Betterthrow(GRPCError(StatusCode.INVALID_ARGUMENT, "Cannot divide $(request.first_number) by zero"))Logging
Log request details for debugging:
@info "Operation" a=request.first_number b=request.second_number request_id=ctx.request_idNext Steps
For production features, see Advanced Topics covering:
- Interceptors for logging, authentication, and rate limiting
- TLS configuration for secure connections
- Compression for large messages
- Health checking for load balancers