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:
1 syntax = "proto3"; 2 3 package userservice.v1; 4 5 import "google/protobuf/empty.proto"; 6 import "google/protobuf/timestamp.proto"; 7 import "google/protobuf/field_mask.proto"; 8 9 // User management service 10 service UserService { 11 // Create a new user account 12 rpc CreateUser(CreateUserRequest) returns (User) { 13 option deprecated = false; 14 } 15 16 // Get user by ID 17 rpc GetUser(GetUserRequest) returns (User); 18 19 // Update user information 20 rpc UpdateUser(UpdateUserRequest) returns (User); 21 22 // Delete a user account 23 rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty); 24 25 // List users with pagination 26 rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); 27 28 // Search users by various criteria 29 rpc SearchUsers(SearchUsersRequest) returns (SearchUsersResponse); 30 } 31 32 // User message definition 33 message User { 34 string id = 1; 35 string email = 2; 36 string name = 3; 37 int32 age = 4; 38 UserStatus status = 5; 39 google.protobuf.Timestamp created_at = 6; 40 google.protobuf.Timestamp updated_at = 7; 41 repeated string roles = 8; 42 UserPreferences preferences = 9; 43 } 44 45 // User status enumeration 46 enum UserStatus { 47 USER_STATUS_UNSPECIFIED = 0; 48 USER_STATUS_ACTIVE = 1; 49 USER_STATUS_INACTIVE = 2; 50 USER_STATUS_SUSPENDED = 3; 51 USER_STATUS_DELETED = 4; 52 } 53 54 // Nested message for user preferences 55 message UserPreferences { 56 bool email_notifications = 1; 57 string timezone = 2; 58 string language = 3; 59 ThemeMode theme = 4; 60 } 61 62 enum ThemeMode { 63 THEME_MODE_UNSPECIFIED = 0; 64 THEME_MODE_LIGHT = 1; 65 THEME_MODE_DARK = 2; 66 THEME_MODE_AUTO = 3; 67 }
Request and response messages
Define clear request and response message types:
1 // Create user request 2 message CreateUserRequest { 3 string email = 1 [(validate.rules).string.email = true]; 4 string name = 2 [(validate.rules).string.min_len = 1]; 5 int32 age = 3 [(validate.rules).int32.gte = 0]; 6 UserPreferences preferences = 4; 7 } 8 9 // Get user request 10 message GetUserRequest { 11 string id = 1 [(validate.rules).string.uuid = true]; 12 } 13 14 // Update user request 15 message UpdateUserRequest { 16 string id = 1 [(validate.rules).string.uuid = true]; 17 User user = 2; 18 google.protobuf.FieldMask update_mask = 3; 19 } 20 21 // Delete user request 22 message DeleteUserRequest { 23 string id = 1 [(validate.rules).string.uuid = true]; 24 } 25 26 // List users request with pagination 27 message ListUsersRequest { 28 int32 page_size = 1 [(validate.rules).int32 = {gte: 1, lte: 100}]; 29 string page_token = 2; 30 string filter = 3; 31 string order_by = 4; 32 } 33 34 // List users response 35 message ListUsersResponse { 36 repeated User users = 1; 37 string next_page_token = 2; 38 int32 total_count = 3; 39 } 40 41 // Search users request 42 message SearchUsersRequest { 43 string query = 1 [(validate.rules).string.min_len = 1]; 44 repeated UserStatus status_filter = 2; 45 repeated string role_filter = 3; 46 int32 page_size = 4 [(validate.rules).int32 = {gte: 1, lte: 100}]; 47 string page_token = 5; 48 } 49 50 // Search users response 51 message SearchUsersResponse { 52 repeated User users = 1; 53 string next_page_token = 2; 54 int32 total_count = 3; 55 SearchMetadata metadata = 4; 56 } 57 58 message SearchMetadata { 59 int32 search_time_ms = 1; 60 repeated string suggested_corrections = 2; 61 }
Service implementation
Implement the service in your preferred language:
1 import grpc 2 from grpc import ServicerContext 3 import user_service_pb2 4 import user_service_pb2_grpc 5 from google.protobuf import empty_pb2 6 from typing import Iterator 7 8 class UserServiceServicer(user_service_pb2_grpc.UserServiceServicer): 9 10 def __init__(self, user_repository): 11 self.user_repository = user_repository 12 13 def CreateUser( 14 self, 15 request: user_service_pb2.CreateUserRequest, 16 context: ServicerContext 17 ) -> user_service_pb2.User: 18 """Create a new user account.""" 19 try: 20 # Validate request 21 if not request.email or not request.name: 22 context.set_code(grpc.StatusCode.INVALID_ARGUMENT) 23 context.set_details('Email and name are required') 24 return user_service_pb2.User() 25 26 # Check if user already exists 27 if self.user_repository.get_by_email(request.email): 28 context.set_code(grpc.StatusCode.ALREADY_EXISTS) 29 context.set_details(f'User with email {request.email} already exists') 30 return user_service_pb2.User() 31 32 # Create user 33 user = self.user_repository.create_user( 34 email=request.email, 35 name=request.name, 36 age=request.age, 37 preferences=request.preferences 38 ) 39 40 return user 41 42 except Exception as e: 43 context.set_code(grpc.StatusCode.INTERNAL) 44 context.set_details(f'Failed to create user: {str(e)}') 45 return user_service_pb2.User() 46 47 def GetUser( 48 self, 49 request: user_service_pb2.GetUserRequest, 50 context: ServicerContext 51 ) -> user_service_pb2.User: 52 """Get user by ID.""" 53 try: 54 user = self.user_repository.get_by_id(request.id) 55 if not user: 56 context.set_code(grpc.StatusCode.NOT_FOUND) 57 context.set_details(f'User with ID {request.id} not found') 58 return user_service_pb2.User() 59 60 return user 61 62 except Exception as e: 63 context.set_code(grpc.StatusCode.INTERNAL) 64 context.set_details(f'Failed to get user: {str(e)}') 65 return user_service_pb2.User() 66 67 def UpdateUser( 68 self, 69 request: user_service_pb2.UpdateUserRequest, 70 context: ServicerContext 71 ) -> user_service_pb2.User: 72 """Update user information.""" 73 try: 74 # Check if user exists 75 existing_user = self.user_repository.get_by_id(request.id) 76 if not existing_user: 77 context.set_code(grpc.StatusCode.NOT_FOUND) 78 context.set_details(f'User with ID {request.id} not found') 79 return user_service_pb2.User() 80 81 # Apply field mask for partial updates 82 updated_user = self.user_repository.update_user( 83 user_id=request.id, 84 updates=request.user, 85 field_mask=request.update_mask 86 ) 87 88 return updated_user 89 90 except Exception as e: 91 context.set_code(grpc.StatusCode.INTERNAL) 92 context.set_details(f'Failed to update user: {str(e)}') 93 return user_service_pb2.User() 94 95 def DeleteUser( 96 self, 97 request: user_service_pb2.DeleteUserRequest, 98 context: ServicerContext 99 ) -> empty_pb2.Empty: 100 """Delete a user account.""" 101 try: 102 # Check if user exists 103 user = self.user_repository.get_by_id(request.id) 104 if not user: 105 context.set_code(grpc.StatusCode.NOT_FOUND) 106 context.set_details(f'User with ID {request.id} not found') 107 return empty_pb2.Empty() 108 109 # Soft delete user 110 self.user_repository.delete_user(request.id) 111 112 return empty_pb2.Empty() 113 114 except Exception as e: 115 context.set_code(grpc.StatusCode.INTERNAL) 116 context.set_details(f'Failed to delete user: {str(e)}') 117 return empty_pb2.Empty() 118 119 def ListUsers( 120 self, 121 request: user_service_pb2.ListUsersRequest, 122 context: ServicerContext 123 ) -> user_service_pb2.ListUsersResponse: 124 """List users with pagination.""" 125 try: 126 # Apply pagination 127 page_size = min(request.page_size or 20, 100) 128 129 users, next_page_token, total_count = self.user_repository.list_users( 130 page_size=page_size, 131 page_token=request.page_token, 132 filter_expr=request.filter, 133 order_by=request.order_by 134 ) 135 136 return user_service_pb2.ListUsersResponse( 137 users=users, 138 next_page_token=next_page_token, 139 total_count=total_count 140 ) 141 142 except Exception as e: 143 context.set_code(grpc.StatusCode.INTERNAL) 144 context.set_details(f'Failed to list users: {str(e)}') 145 return user_service_pb2.ListUsersResponse() 146 147 def SearchUsers( 148 self, 149 request: user_service_pb2.SearchUsersRequest, 150 context: ServicerContext 151 ) -> user_service_pb2.SearchUsersResponse: 152 """Search users by various criteria.""" 153 try: 154 start_time = time.time() 155 156 users, next_page_token, total_count = self.user_repository.search_users( 157 query=request.query, 158 status_filter=request.status_filter, 159 role_filter=request.role_filter, 160 page_size=request.page_size or 20, 161 page_token=request.page_token 162 ) 163 164 search_time_ms = int((time.time() - start_time) * 1000) 165 166 metadata = user_service_pb2.SearchMetadata( 167 search_time_ms=search_time_ms, 168 suggested_corrections=[] # Add spell check suggestions if needed 169 ) 170 171 return user_service_pb2.SearchUsersResponse( 172 users=users, 173 next_page_token=next_page_token, 174 total_count=total_count, 175 metadata=metadata 176 ) 177 178 except Exception as e: 179 context.set_code(grpc.StatusCode.INTERNAL) 180 context.set_details(f'Failed to search users: {str(e)}') 181 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
1 message User { 2 // Frequently used fields (1-15) 3 string id = 1; 4 string email = 2; 5 string name = 3; 6 7 // Less frequently used fields 8 UserPreferences preferences = 16; 9 repeated string tags = 17; 10 11 // Reserved fields 12 reserved 4, 5, 6; 13 reserved "old_field_name", "deprecated_field"; 14 }
Naming conventions
- Use
snake_casefor field names - Use
PascalCasefor message and service names - Use
UPPER_SNAKE_CASEfor enum values
1 service UserManagementService { // PascalCase 2 rpc GetUser(GetUserRequest) returns (User); 3 } 4 5 message User { // PascalCase 6 string first_name = 1; // snake_case 7 UserStatus status = 2; 8 } 9 10 enum UserStatus { 11 USER_STATUS_UNSPECIFIED = 0; // UPPER_SNAKE_CASE 12 USER_STATUS_ACTIVE = 1; 13 }
Versioning
- Include version in package names
- Use semantic versioning for breaking changes
1 syntax = "proto3"; 2 3 package userservice.v1; // Version in package name 4 5 option go_package = "example.com/userservice/v1";
Multiple services
Organize related functionality into separate services:
1 // User management 2 service UserService { 3 rpc CreateUser(CreateUserRequest) returns (User); 4 rpc GetUser(GetUserRequest) returns (User); 5 } 6 7 // Authentication 8 service AuthService { 9 rpc Login(LoginRequest) returns (LoginResponse); 10 rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse); 11 } 12 13 // Notification service 14 service NotificationService { 15 rpc SendNotification(SendNotificationRequest) returns (SendNotificationResponse); 16 rpc GetNotificationPreferences(GetNotificationPreferencesRequest) returns (NotificationPreferences); 17 }
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.
1 // Import Fern custom options 2 import "fern/options.proto"; 3 4 service CommentsService { 5 // Description of the endpoint 6 rpc CreateComment(CreateCommentRequest) returns (CreateCommentResponse) { 7 option (google.api.http) = { 8 post: "/comments/v1/comments" 9 response_body: "comment" 10 body: "*" 11 }; 12 13 // Display name shown in navigation 14 option (fern.summary) = "Create your comment"; 15 } 16 }