gRPC 服务

使用 RPC、消息和 Protocol Buffer 模式定义 gRPC 服务

以 Markdown 格式查看

gRPC 服务是 API 的核心构建块。每个服务定义了客户端可以调用的远程过程调用(RPC)集合,以及用于请求和响应的消息类型。

服务定义

.proto 文件中定义 gRPC 服务:

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}

请求和响应消息

定义清晰的请求和响应消息类型:

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}

服务实现

使用您首选的语言实现服务:

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 最佳实践

字段编号

  • 对于频繁使用的字段使用字段编号 1-15(编码更高效)
  • 为已删除的字段保留字段编号以维持兼容性
  • 永远不要重复使用字段编号
1message User {
2 // 频繁使用的字段 (1-15)
3 string id = 1;
4 string email = 2;
5 string name = 3;
6
7 // 不太频繁使用的字段
8 UserPreferences preferences = 16;
9 repeated string tags = 17;
10
11 // 保留字段
12 reserved 4, 5, 6;
13 reserved "old_field_name", "deprecated_field";
14}

命名规范

  • 字段名使用 snake_case
  • 消息和服务名使用 PascalCase
  • 枚举值使用 UPPER_SNAKE_CASE
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}

版本管理

  • 在包名中包含版本
  • 对于破坏性更改使用语义版本控制
1syntax = "proto3";
2
3package userservice.v1; // 包名中的版本
4
5option go_package = "example.com/userservice/v1";

多个服务

将相关功能组织到单独的服务中:

services.proto
1// 用户管理
2service UserService {
3 rpc CreateUser(CreateUserRequest) returns (User);
4 rpc GetUser(GetUserRequest) returns (User);
5}
6
7// 身份验证
8service AuthService {
9 rpc Login(LoginRequest) returns (LoginResponse);
10 rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
11}
12
13// 通知服务
14service NotificationService {
15 rpc SendNotification(SendNotificationRequest) returns (SendNotificationResponse);
16 rpc GetNotificationPreferences(GetNotificationPreferencesRequest) returns (NotificationPreferences);
17}

gRPC 服务为构建分布式系统提供了强类型、高性能的基础,在客户端和服务器之间建立了清晰的合约。

自定义选项

Fern 提供自定义 Protocol Buffer 选项来增强您的 API 参考文档和生成的代码。

API 导航名称

使用 fern.summary 选项为端点设置明确的显示名称,比默认的 RPC 方法名称更用户友好。

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}