跳到导航

gRPC 服务

使用 RPC、消息和 Protocol Buffer 模式定义 gRPC 服务
以 Markdown 格式查看

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

服务定义

.proto 文件中定义 gRPC 服务:

user_service.proto
syntax = "proto3";
package userservice.v1;
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/field_mask.proto";
// User management service
service UserService {
// Create a new user account
rpc CreateUser(CreateUserRequest) returns (User) {
option deprecated = false;
}
// Get user by ID
rpc GetUser(GetUserRequest) returns (User);
// Update user information
rpc UpdateUser(UpdateUserRequest) returns (User);
// Delete a user account
rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);
// List users with pagination
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
// Search users by various criteria
rpc SearchUsers(SearchUsersRequest) returns (SearchUsersResponse);
}
// User message definition
message 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 enumeration
enum 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 preferences
message 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;
}

请求和响应消息

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

user_messages.proto
// Create user request
message 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 request
message GetUserRequest {
string id = 1 [(validate.rules).string.uuid = true];
}
// Update user request
message UpdateUserRequest {
string id = 1 [(validate.rules).string.uuid = true];
User user = 2;
google.protobuf.FieldMask update_mask = 3;
}
// Delete user request
message DeleteUserRequest {
string id = 1 [(validate.rules).string.uuid = true];
}
// List users request with pagination
message 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 response
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
int32 total_count = 3;
}
// Search users request
message 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 response
message 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;
}

服务实现

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

user_service.py
import grpc
from grpc import ServicerContext
import user_service_pb2
import user_service_pb2_grpc
from google.protobuf import empty_pb2
from typing import Iterator
class UserServiceServicer(user_service_pb2_grpc.UserServiceServicer):
def __init__(self, user_repository):
self.user_repository = user_repository
def CreateUser(
self,
request: user_service_pb2.CreateUserRequest,
context: ServicerContext
) -> user_service_pb2.User:
"""Create a new user account."""
try:
# Validate request
if 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 exists
if 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 user
user = self.user_repository.create_user(
email=request.email,
name=request.name,
age=request.age,
preferences=request.preferences
)
return user
except 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 user
except 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 exists
existing_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 updates
updated_user = self.user_repository.update_user(
user_id=request.id,
updates=request.user,
field_mask=request.update_mask
)
return updated_user
except 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 exists
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 empty_pb2.Empty()
# Soft delete user
self.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 pagination
page_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 最佳实践

字段编号

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

命名规范

  • 字段名使用 snake_case
  • 消息和服务名使用 PascalCase
  • 枚举值使用 UPPER_SNAKE_CASE
service UserManagementService { // PascalCase
rpc GetUser(GetUserRequest) returns (User);
}
message User { // PascalCase
string first_name = 1; // snake_case
UserStatus status = 2;
}
enum UserStatus {
USER_STATUS_UNSPECIFIED = 0; // UPPER_SNAKE_CASE
USER_STATUS_ACTIVE = 1;
}

版本管理

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

多个服务

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

services.proto
// 用户管理
service UserService {
rpc CreateUser(CreateUserRequest) returns (User);
rpc GetUser(GetUserRequest) returns (User);
}
// 身份验证
service AuthService {
rpc Login(LoginRequest) returns (LoginResponse);
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
}
// 通知服务
service NotificationService {
rpc SendNotification(SendNotificationRequest) returns (SendNotificationResponse);
rpc GetNotificationPreferences(GetNotificationPreferencesRequest) returns (NotificationPreferences);
}

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

自定义选项

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

API 导航名称

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

services.proto
// Import Fern custom options
import "fern/options.proto";
service CommentsService {
// Description of the endpoint
rpc CreateComment(CreateCommentRequest) returns (CreateCommentResponse) {
option (google.api.http) = {
post: "/comments/v1/comments"
response_body: "comment"
body: "*"
};
// Display name shown in navigation
option (fern.summary) = "Create your comment";
}
}