On this page

gRPC Protocol Reference

Interface declaration:

orders = service("orders",
    interface("grpc", "grpc", 50051),
    image = "myapp-orders:latest",
    healthcheck = tcp("localhost:50051"),
)

Methods

call(method="", body="{}")

Invoke a gRPC method with a JSON-encoded request body.

resp = orders.grpc.call(
    method="/orders.OrderService/CreateOrder",
    body='{"item":"widget","qty":1}',
)
# resp.data = {"method": "/orders.OrderService/CreateOrder", "raw": "..."}

resp = orders.grpc.call(
    method="/health.HealthService/Check",
    body="{}",
)
ParameterTypeDefaultDescription
methodstringrequiredFull gRPC method path (/package.Service/Method)
bodystring"{}"JSON-encoded request message

Response:

FieldTypeDescription
.data["method"]stringgRPC method called
.data["raw"]stringRaw response bytes as string
.okboolTrue if gRPC status is OK
.statusintgRPC status code (0=OK, see table below)
.duration_msintExecution time

gRPC Status Codes

CodeNameDescription
0OKSuccess
1CANCELLEDOperation cancelled
2UNKNOWNUnknown error
3INVALID_ARGUMENTClient sent invalid argument
4DEADLINE_EXCEEDEDTimeout
5NOT_FOUNDResource not found
13INTERNALInternal server error
14UNAVAILABLEService unavailable
16UNAUTHENTICATEDAuthentication required

Fault Rules

error(method=, status=, message=)

Return a gRPC error for matching methods.

unavailable = fault_assumption("orders_unavailable",
    target = orders.grpc,
    rules = [error(method="/orders.OrderService/*", status=14,
                   message="service unavailable")],
)

not_found = fault_assumption("order_not_found",
    target = orders.grpc,
    rules = [error(method="/orders.OrderService/GetOrder", status=5,
                   message="order not found")],
)

deadline = fault_assumption("deadline_exceeded",
    target = orders.grpc,
    rules = [error(method="*", status=4, message="deadline exceeded")],
)
ParameterTypeDescription
methodstringgRPC method glob ("/orders.OrderService/*")
statusintgRPC status code (see table above)
messagestringError message

delay(method=, delay=)

slow_orders = fault_assumption("slow_orders",
    target = orders.grpc,
    rules = [delay(method="/orders.OrderService/CreateOrder", delay="5s")],
)

drop(method=)

Returns UNAVAILABLE with “connection dropped” message.

drop_creates = fault_assumption("drop_creates",
    target = orders.grpc,
    rules = [drop(method="/orders.OrderService/CreateOrder")],
)

Seed / Reset Patterns

gRPC services are typically backed by a database — seed the database directly rather than the gRPC service:

orders = service("orders",
    interface("grpc", "grpc", 50051),
    image = "myapp-orders:latest",
    depends_on = [db],
    reuse = True,
    # No seed on the gRPC service — seed the DB instead
)

db = service("postgres", ...,
    reuse = True,
    seed = lambda: db.main.exec(sql=open("./seed.sql").read()),
    reset = lambda: db.main.exec(sql="TRUNCATE orders RESTART IDENTITY CASCADE"),
)