gRPC Services

Define gRPC services with RPCs, messages, and Protocol Buffer schemas
View as Markdown

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:

user_service.proto
1syntax = "proto3";
2
3package userservice.v1;
4
5import "google/protobuf/empty.proto";
6import "google/protobuf/timestamp.proto";
7import "google/protobuf/field_mask.proto";
8
9// User management service
10service 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
33message 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
46enum 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
55message UserPreferences {
56 bool email_notifications = 1;
57 string timezone = 2;
58 string language = 3;
59 ThemeMode theme = 4;
60}
61
62enum 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:

user_messages.proto
1// Create user request
2message 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
10message GetUserRequest {
11 string id = 1 [(validate.rules).string.uuid = true];
12}
13
14// Update user request
15message 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
22message DeleteUserRequest {
23 string id = 1 [(validate.rules).string.uuid = true];
24}
25
26// List users request with pagination
27message 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
35message ListUsersResponse {
36 repeated User users = 1;
37 string next_page_token = 2;
38 int32 total_count = 3;
39}
40
41// Search users request
42message 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
51message SearchUsersResponse {
52 repeated User users = 1;
53 string next_page_token = 2;
54 int32 total_count = 3;
55 SearchMetadata metadata = 4;
56}
57
58message SearchMetadata {
59 int32 search_time_ms = 1;
60 repeated string suggested_corrections = 2;
61}

Service implementation

Implement the service in your preferred language:

user_service.py
1import grpc
2from grpc import ServicerContext
3import user_service_pb2
4import user_service_pb2_grpc
5from google.protobuf import empty_pb2
6from typing import Iterator
7
8class 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
1message 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_case for field names
  • Use PascalCase for message and service names
  • Use UPPER_SNAKE_CASE for enum values
1service UserManagementService { // PascalCase
2 rpc GetUser(GetUserRequest) returns (User);
3}
4
5message User { // PascalCase
6 string first_name = 1; // snake_case
7 UserStatus status = 2;
8}
9
10enum 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
1syntax = "proto3";
2
3package userservice.v1; // Version in package name
4
5option go_package = "example.com/userservice/v1";

Multiple services

Organize related functionality into separate services:

services.proto
1// User management
2service UserService {
3 rpc CreateUser(CreateUserRequest) returns (User);
4 rpc GetUser(GetUserRequest) returns (User);
5}
6
7// Authentication
8service AuthService {
9 rpc Login(LoginRequest) returns (LoginResponse);
10 rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
11}
12
13// Notification service
14service 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.

services.proto
1// Import Fern custom options
2import "fern/options.proto";
3
4service 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}