📝 Quiz: Part I — Foundations (Chapters 1-5)
1. What transport protocol does gRPC use?
2. Why does gRPC require HTTP/2 (not HTTP/1.1)?
3. In Protobuf, what happens when a field has its default value (e.g., int32 = 0)?
4. What was Google's internal RPC system (predecessor to gRPC) called?
5. Why does Protobuf use field numbers instead of field names?
Chapter 6: Service Definition with .proto Files
The .proto file is the heart of gRPC. It's the contract between client and server — the single source of truth that both sides agree on. This chapter teaches you how to write service definitions like a pro.
Anatomy of a gRPC Service Definition
syntax = "proto3";
package telecom.amf.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
option go_package = "telecom/amf/v1;amfv1";
// Service: a collection of RPC methods
service SessionService {
// Unary RPC: one request, one response
rpc CreateSession(CreateSessionRequest) returns (Session);
// Unary RPC
rpc GetSession(GetSessionRequest) returns (Session);
// Server streaming: one request, stream of responses
rpc WatchSession(WatchSessionRequest) returns (stream SessionEvent);
// Client streaming: stream of requests, one response
rpc ReportMetrics(stream MetricReport) returns (MetricSummary);
// Bidirectional streaming: stream both ways
rpc StreamUpdates(stream SessionUpdate) returns (stream SessionEvent);
// Returns nothing meaningful
rpc DeleteSession(DeleteSessionRequest) returns (google.protobuf.Empty);
}
Request/Response Message Design
A critical gRPC convention: every RPC gets its own request and response message types, even if they seem redundant.
// ✗ BAD: Reusing messages across RPCs
rpc GetUser(UserId) returns (User);
rpc DeleteUser(UserId) returns (Empty);
// ✓ GOOD: Dedicated request/response per RPC
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
message GetUserRequest {
string user_id = 1;
}
message GetUserResponse {
User user = 1;
}
message DeleteUserRequest {
string user_id = 1;
}
message DeleteUserResponse {
// Empty for now, but can add fields later without breaking the API
}
Because APIs evolve. Today GetUser only needs an ID. Tomorrow you might need to add a field_mask (which fields to return) or a view parameter. If you used a generic UserId message, you'd have to change every RPC that uses it. With GetUserRequest, you only change one message.
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Package | lowercase dot-separated | mycompany.myservice.v1 |
| Service | PascalCase + "Service" suffix | UserService |
| RPC method | PascalCase verb+noun | CreateUser, ListOrders |
| Message | PascalCase | CreateUserRequest |
| Field | snake_case | user_id, created_at |
| Enum | PascalCase | SessionState |
| Enum value | UPPER_SNAKE_CASE | STATE_ACTIVE |
Common Patterns
CRUD Operations
service UserService {
rpc CreateUser(CreateUserRequest) returns (User);
rpc GetUser(GetUserRequest) returns (User);
rpc UpdateUser(UpdateUserRequest) returns (User);
rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}
Resource Hierarchy
// Parent-child relationships via resource names
message GetSessionRequest {
// Format: "networks/{network}/sessions/{session}"
string name = 1;
}
Well-Known Types (Google's Standard Library)
import "google/protobuf/timestamp.proto"; // point in time
import "google/protobuf/duration.proto"; // time span
import "google/protobuf/empty.proto"; // void return
import "google/protobuf/field_mask.proto"; // partial updates
import "google/protobuf/wrappers.proto"; // nullable scalars
import "google/protobuf/any.proto"; // dynamic typing
import "google/protobuf/struct.proto"; // JSON-like dynamic
message Session {
string id = 1;
google.protobuf.Timestamp created_at = 2;
google.protobuf.Duration ttl = 3;
}
File Organization
For a real service, organize your .proto files like this:
Import and Package System
// File: mycompany/billing/v1/invoice.proto
syntax = "proto3";
package mycompany.billing.v1;
// Import from another package
import "mycompany/users/v1/user.proto";
message Invoice {
string id = 1;
// Reference type from another package using fully-qualified name
mycompany.users.v1.User customer = 2;
}
Comments and Documentation
// Single-line comments use //
/* Multi-line comments
use C-style blocks */
// Comments directly above a message/field/rpc become documentation
// in generated code (as doc comments in Go, Javadoc in Java, etc.)
/// Some tools recognize triple-slash as doc comments
// Create a new user account.
// The email must be unique across the system.
// Returns ALREADY_EXISTS if the email is taken.
rpc CreateUser(CreateUserRequest) returns (User);
- The .proto file is the API contract — treat it like a public interface
- Every RPC gets its own Request/Response message (for future extensibility)
- Follow naming conventions: PascalCase for types, snake_case for fields
- Use well-known types (Timestamp, Duration, FieldMask) instead of reinventing
- Organize protos by package with versioning (v1, v2)