gRPC Services
gRPC services are the core building blocks of your API. Each service defines a collection of remote procedure calls (RPCs) that clients can invoke, along with the message types used for requests and responses.
Service definition
Define a gRPC service in a .proto file:
syntax = "proto3";package userservice.v1;import "google/protobuf/empty.proto";import "google/protobuf/timestamp.proto";import "google/protobuf/field_mask.proto";// User management serviceservice UserService {// Create a new user accountrpc CreateUser(CreateUserRequest) returns (User) {option deprecated = false;}// Get user by IDrpc GetUser(GetUserRequest) returns (User);// Update user informationrpc UpdateUser(UpdateUserRequest) returns (User);// Delete a user accountrpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);// List users with paginationrpc ListUsers(ListUsersRequest) returns (ListUsersResponse);// Search users by various criteriarpc SearchUsers(SearchUsersRequest) returns (SearchUsersResponse);}// User message definitionmessage User {string id = 1;string email = 2;string name = 3;int32 age = 4;UserStatus status = 5;google.protobuf.Timestamp created_at = 6;google.protobuf.Timestamp updated_at = 7;repeated string roles = 8;UserPreferences preferences = 9;}// User status enumerationenum UserStatus {USER_STATUS_UNSPECIFIED = 0;USER_STATUS_ACTIVE = 1;USER_STATUS_INACTIVE = 2;USER_STATUS_SUSPENDED = 3;USER_STATUS_DELETED = 4;}// Nested message for user preferencesmessage UserPreferences {bool email_notifications = 1;string timezone = 2;string language = 3;ThemeMode theme = 4;}enum ThemeMode {THEME_MODE_UNSPECIFIED = 0;THEME_MODE_LIGHT = 1;THEME_MODE_DARK = 2;THEME_MODE_AUTO = 3;}
Request and response messages
Define clear request and response message types:
// Create user requestmessage CreateUserRequest {string email = 1 [(validate.rules).string.email = true];string name = 2 [(validate.rules).string.min_len = 1];int32 age = 3 [(validate.rules).int32.gte = 0];UserPreferences preferences = 4;}// Get user requestmessage GetUserRequest {string id = 1 [(validate.rules).string.uuid = true];}// Update user requestmessage UpdateUserRequest {string id = 1 [(validate.rules).string.uuid = true];User user = 2;google.protobuf.FieldMask update_mask = 3;}// Delete user requestmessage DeleteUserRequest {string id = 1 [(validate.rules).string.uuid = true];}// List users request with paginationmessage ListUsersRequest {int32 page_size = 1 [(validate.rules).int32 = {gte: 1, lte: 100}];string page_token = 2;string filter = 3;string order_by = 4;}// List users responsemessage ListUsersResponse {repeated User users = 1;string next_page_token = 2;int32 total_count = 3;}// Search users requestmessage SearchUsersRequest {string query = 1 [(validate.rules).string.min_len = 1];repeated UserStatus status_filter = 2;repeated string role_filter = 3;int32 page_size = 4 [(validate.rules).int32 = {gte: 1, lte: 100}];string page_token = 5;}// Search users responsemessage SearchUsersResponse {repeated User users = 1;string next_page_token = 2;int32 total_count = 3;SearchMetadata metadata = 4;}message SearchMetadata {int32 search_time_ms = 1;repeated string suggested_corrections = 2;}
Service implementation
Implement the service in your preferred language:
import grpcfrom grpc import ServicerContextimport user_service_pb2import user_service_pb2_grpcfrom google.protobuf import empty_pb2from typing import Iteratorclass UserServiceServicer(user_service_pb2_grpc.UserServiceServicer):def __init__(self, user_repository):self.user_repository = user_repositorydef CreateUser(self,request: user_service_pb2.CreateUserRequest,context: ServicerContext) -> user_service_pb2.User:"""Create a new user account."""try:# Validate requestif not request.email or not request.name:context.set_code(grpc.StatusCode.INVALID_ARGUMENT)context.set_details('Email and name are required')return user_service_pb2.User()# Check if user already existsif self.user_repository.get_by_email(request.email):context.set_code(grpc.StatusCode.ALREADY_EXISTS)context.set_details(f'User with email {request.email} already exists')return user_service_pb2.User()# Create useruser = self.user_repository.create_user(email=request.email,name=request.name,age=request.age,preferences=request.preferences)return userexcept Exception as e:context.set_code(grpc.StatusCode.INTERNAL)context.set_details(f'Failed to create user: {str(e)}')return user_service_pb2.User()def GetUser(self,request: user_service_pb2.GetUserRequest,context: ServicerContext) -> user_service_pb2.User:"""Get user by ID."""try:user = self.user_repository.get_by_id(request.id)if not user:context.set_code(grpc.StatusCode.NOT_FOUND)context.set_details(f'User with ID {request.id} not found')return user_service_pb2.User()return userexcept Exception as e:context.set_code(grpc.StatusCode.INTERNAL)context.set_details(f'Failed to get user: {str(e)}')return user_service_pb2.User()def UpdateUser(self,request: user_service_pb2.UpdateUserRequest,context: ServicerContext) -> user_service_pb2.User:"""Update user information."""try:# Check if user existsexisting_user = self.user_repository.get_by_id(request.id)if not existing_user:context.set_code(grpc.StatusCode.NOT_FOUND)context.set_details(f'User with ID {request.id} not found')return user_service_pb2.User()# Apply field mask for partial updatesupdated_user = self.user_repository.update_user(user_id=request.id,updates=request.user,field_mask=request.update_mask)return updated_userexcept Exception as e:context.set_code(grpc.StatusCode.INTERNAL)context.set_details(f'Failed to update user: {str(e)}')return user_service_pb2.User()def DeleteUser(self,request: user_service_pb2.DeleteUserRequest,context: ServicerContext) -> empty_pb2.Empty:"""Delete a user account."""try:# Check if user existsuser = self.user_repository.get_by_id(request.id)if not user:context.set_code(grpc.StatusCode.NOT_FOUND)context.set_details(f'User with ID {request.id} not found')return empty_pb2.Empty()# Soft delete userself.user_repository.delete_user(request.id)return empty_pb2.Empty()except Exception as e:context.set_code(grpc.StatusCode.INTERNAL)context.set_details(f'Failed to delete user: {str(e)}')return empty_pb2.Empty()def ListUsers(self,request: user_service_pb2.ListUsersRequest,context: ServicerContext) -> user_service_pb2.ListUsersResponse:"""List users with pagination."""try:# Apply paginationpage_size = min(request.page_size or 20, 100)users, next_page_token, total_count = self.user_repository.list_users(page_size=page_size,page_token=request.page_token,filter_expr=request.filter,order_by=request.order_by)return user_service_pb2.ListUsersResponse(users=users,next_page_token=next_page_token,total_count=total_count)except Exception as e:context.set_code(grpc.StatusCode.INTERNAL)context.set_details(f'Failed to list users: {str(e)}')return user_service_pb2.ListUsersResponse()def SearchUsers(self,request: user_service_pb2.SearchUsersRequest,context: ServicerContext) -> user_service_pb2.SearchUsersResponse:"""Search users by various criteria."""try:start_time = time.time()users, next_page_token, total_count = self.user_repository.search_users(query=request.query,status_filter=request.status_filter,role_filter=request.role_filter,page_size=request.page_size or 20,page_token=request.page_token)search_time_ms = int((time.time() - start_time) * 1000)metadata = user_service_pb2.SearchMetadata(search_time_ms=search_time_ms,suggested_corrections=[] # Add spell check suggestions if needed)return user_service_pb2.SearchUsersResponse(users=users,next_page_token=next_page_token,total_count=total_count,metadata=metadata)except Exception as e:context.set_code(grpc.StatusCode.INTERNAL)context.set_details(f'Failed to search users: {str(e)}')return user_service_pb2.SearchUsersResponse()
Protocol buffer best practices
Field numbers
- Use field numbers 1-15 for frequently used fields (more efficient encoding)
- Reserve field numbers for removed fields to maintain compatibility
- Never reuse field numbers
message User {// Frequently used fields (1-15)string id = 1;string email = 2;string name = 3;// Less frequently used fieldsUserPreferences preferences = 16;repeated string tags = 17;// Reserved fieldsreserved 4, 5, 6;reserved "old_field_name", "deprecated_field";}
Naming conventions
- Use
snake_casefor field names - Use
PascalCasefor message and service names - Use
UPPER_SNAKE_CASEfor enum values
service UserManagementService { // PascalCaserpc GetUser(GetUserRequest) returns (User);}message User { // PascalCasestring first_name = 1; // snake_caseUserStatus status = 2;}enum UserStatus {USER_STATUS_UNSPECIFIED = 0; // UPPER_SNAKE_CASEUSER_STATUS_ACTIVE = 1;}
Versioning
- Include version in package names
- Use semantic versioning for breaking changes
syntax = "proto3";package userservice.v1; // Version in package nameoption go_package = "example.com/userservice/v1";
Multiple services
Organize related functionality into separate services:
// User managementservice UserService {rpc CreateUser(CreateUserRequest) returns (User);rpc GetUser(GetUserRequest) returns (User);}// Authenticationservice AuthService {rpc Login(LoginRequest) returns (LoginResponse);rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);}// Notification serviceservice NotificationService {rpc SendNotification(SendNotificationRequest) returns (SendNotificationResponse);rpc GetNotificationPreferences(GetNotificationPreferencesRequest) returns (NotificationPreferences);}
gRPC services provide a strongly-typed, high-performance foundation for building distributed systems with clear contracts between clients and servers.
Custom options
Fern provides custom Protocol Buffer options to enhance your API Reference documentation and generated code.
API navigation name
Use the fern.summary option to set an explicit display name for endpoints that’s more user-friendly than the default RPC method name.
// Import Fern custom optionsimport "fern/options.proto";service CommentsService {// Description of the endpointrpc CreateComment(CreateCommentRequest) returns (CreateCommentResponse) {option (google.api.http) = {post: "/comments/v1/comments"response_body: "comment"body: "*"};// Display name shown in navigationoption (fern.summary) = "Create your comment";}}