Help us improve
Share bugs, ideas, or general feedback.
From grimoire
Defines proto contracts for gRPC services: field numbering, naming conventions, error modeling with google.rpc.Status, streaming patterns, and backward compatibility.
npx claudepluginhub jeffreytse/grimoire --plugin grimoireHow this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:design-grpc-serviceThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Write the `.proto` file as the authoritative contract before any implementation — apply consistent naming, safe field numbering, typed error codes, and versioning rules that preserve backward compatibility.
Generates gRPC service definitions, stubs, and implementations from Protocol Buffers. Supports streaming RPCs, interceptors, health checks, TLS, tests, and REST gateways for high-performance APIs.
Designs proto3 Protocol Buffers schemas for gRPC services: messages, enums, RPC patterns, streaming, naming conventions, C# ASP.NET Core implementation.
Protocol buffers, service definitions, streaming RPC, and performance-oriented API design.
Share bugs, ideas, or general feedback.
Write the .proto file as the authoritative contract before any implementation — apply consistent naming, safe field numbering, typed error codes, and versioning rules that preserve backward compatibility.
Adopted by: Google uses Protocol Buffers and gRPC for virtually all internal service communication. Lyft, Netflix, Dropbox, and Cloudflare use gRPC for internal microservices. The Buf toolchain (buf.build) brings CI-enforced linting and breaking-change detection, adopted by thousands of teams. Google AIP defines naming, pagination, and error conventions for all Google APIs over gRPC.
Impact: Protobuf's binary encoding and HTTP/2 multiplexing produce 5–10× smaller payloads than equivalent JSON/REST. One .proto generates correct stubs in Go, Java, Python, C++, Rust, and 10+ languages simultaneously. A well-designed proto is reused for years; a poorly designed one accumulates workarounds in every consumer.
Why best: Writing the proto first forces explicit decisions about field types, enums, error codes, and streaming before any implementation begins. Protobuf's compatibility rules make safe evolution possible — but only if the initial design follows them.
Sources: Google AIP (aip.dev); Protocol Buffers Language Guide; Buf best practices; gRPC docs
syntax = "proto3";
package example.inventory.v1;
option go_package = "github.com/example/api/inventory/v1;inventoryv1";
option java_multiple_files = true;
option java_package = "com.example.inventory.v1";
import "google/protobuf/timestamp.proto";
import "google/protobuf/field_mask.proto";
import "google/rpc/error_details.proto";
service InventoryService {
rpc GetProduct(GetProductRequest) returns (Product);
rpc ListProducts(ListProductsRequest) returns (ListProductsResponse);
rpc CreateProduct(CreateProductRequest) returns (Product);
rpc UpdateProduct(UpdateProductRequest) returns (Product);
rpc DeleteProduct(DeleteProductRequest) returns (google.protobuf.Empty);
rpc ArchiveProduct(ArchiveProductRequest) returns (ArchiveProductResponse);
}
Naming rules: RPC names PascalCase verb-first; field names snake_case; enum values UPPER_SNAKE_CASE prefixed with enum name to avoid collision.
message Product {
string id = 1; // field 1–15: 1 byte on wire — use for frequent fields
string name = 2;
int64 price_cents = 3; // money as integer — no float imprecision
string currency_code = 4;
ProductStatus status = 5;
google.protobuf.Timestamp created_at = 6; // always Timestamp, never string/int for time
google.protobuf.Timestamp updated_at = 7;
}
enum ProductStatus {
PRODUCT_STATUS_UNSPECIFIED = 0; // proto3 default is 0 — never assign semantic meaning to 0
PRODUCT_STATUS_ACTIVE = 1;
PRODUCT_STATUS_DRAFT = 2;
PRODUCT_STATUS_ARCHIVED = 3;
}
message UpdateProductRequest {
Product product = 1;
google.protobuf.FieldMask update_mask = 2; // distinguishes absent from zero — required for PATCH
}
message ListProductsRequest {
int32 page_size = 2;
string page_token = 3;
}
message ListProductsResponse {
repeated Product products = 1;
string next_page_token = 2; // empty when no more pages
}
google.rpc.Status for rich error details// Validation error with field-level details
st, _ := status.New(codes.InvalidArgument, "invalid product").
WithDetails(&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{Field: "price_cents", Description: "must be a positive integer"},
},
})
return nil, st.Err()
gRPC → HTTP status mapping: INVALID_ARGUMENT→400, UNAUTHENTICATED→401, PERMISSION_DENIED→403, NOT_FOUND→404, ALREADY_EXISTS→409, RESOURCE_EXHAUSTED→429, INTERNAL→500, UNAVAILABLE→503.
service DataService {
rpc WatchProducts(WatchProductsRequest) returns (stream ProductEvent); // server streaming: live updates
rpc ImportProducts(stream ImportProductRequest) returns (ImportProductsResponse); // client streaming: bulk import
rpc SyncInventory(stream SyncRequest) returns (stream SyncResponse); // bidi: real-time collaboration
}
Add string resume_token to streaming request/response so clients reconnect without missing events.
message Product {
string id = 1;
string name = 2;
int64 price_cents = 4;
reserved 3; // never reuse — causes silent data corruption
reserved "legacy_price";
}
Increment v1 → v2 for breaking changes. Serve both versions from the same binary during migration; consumers migrate at their own pace.
Breaking changes: removing/renaming a field, changing field type or number, removing an enum value, singular↔repeated.
buf lint # naming/style violations
buf breaking --against '.git#branch=main' # breaking change detection
buf generate # regenerate stubs
{ENUM_NAME}_UNSPECIFIED — proto3 treats absent fields as 0.reserved removed numbers and names.google.protobuf.Timestamp for all timestamps.google.protobuf.FieldMask for update operations to distinguish absent from zero.buf lint and buf breaking in CI before merge.Using string for money: Introduces parsing ambiguity. Use int64 price_cents or google.type.Money.
Reusing a removed field number: Silent data corruption when old and new code coexist. Always reserved it.
Semantic meaning on enum 0: STATUS_ACTIVE = 0 silently activates every message with an unset status. Use UNSPECIFIED = 0.
Not using FieldMask for updates: Without it, a partial update zeros all omitted fields — proto3 cannot distinguish absent from zero.
# Generate stubs, implement, test with grpcurl
buf generate
grpcurl -plaintext localhost:9090 list
grpcurl -plaintext -d '{"id": "prod_01HXYZ"}' \
localhost:9090 example.inventory.v1.InventoryService/GetProduct