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 input
  • NOT_FOUND - Requested resource doesn't exist
  • PERMISSION_DENIED - Client lacks permission
  • INTERNAL - Server-side error
  • UNAUTHENTICATED - 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.jl

Note: 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/Add

Expected output:

{
  "result": 15
}

Test Subtract

grpcurl -plaintext -d '{"first_number": 10, "second_number": 3}' \
  localhost:50052 calculator.Calculator/Subtract

Expected output:

{
  "result": 7
}

Test Multiply

grpcurl -plaintext -d '{"first_number": 7, "second_number": 6}' \
  localhost:50052 calculator.Calculator/Multiply

Expected output:

{
  "result": 42
}

Test Divide

grpcurl -plaintext -d '{"first_number": 20, "second_number": 4}' \
  localhost:50052 calculator.Calculator/Divide

Expected output:

{
  "result": 5
}

Test Error Handling (Division by Zero)

grpcurl -plaintext -d '{"first_number": 10, "second_number": 0}' \
  localhost:50052 calculator.Calculator/Divide

Expected output:

ERROR:
  Code: InvalidArgument
  Message: Division by zero

Best 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 requestend

Consistent 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_id

Next 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