Go中为多Proto文件实现通用GET/LIST RPC方法:最佳实践与实现方案
问题解答
1. 这在Go中是否属于最佳实践?
视业务场景而定,但在符合条件的情况下,这是推荐的最佳实践之一:
- 适用场景:当多个RPC服务的Get/List方法核心逻辑高度复用(比如都是从数据库按ID查询、按条件分页查询,仅操作的模型和对应的Proto请求/响应类型不同),通用化实现能大幅减少重复代码,符合DRY(Don't Repeat Yourself)原则,降低长期维护成本。
- 注意事项:如果后续某个服务需要特殊逻辑(比如Get方法需额外权限校验、自定义数据转换),要确保通用实现留有扩展入口(比如允许注入自定义钩子函数),避免过度通用导致后续修改陷入僵局。
2. 如何实现通用的GET/LIST方法?
Go 1.18及以上版本支持的泛型特性,刚好适配「方法签名一致但请求/响应对象不同」的场景。核心思路是:通过泛型抽象通用逻辑,让各Proto服务的实现结构体调用通用函数,并传入具体的请求/响应类型和业务依赖(如DAO层实例)。
示例代码
第一步:定义通用泛型工具函数
先抽象通用DAO接口和泛型Get/List函数,封装通用逻辑:
package service import "context" // 通用DAO接口,定义Get和List的基础操作契约 type GenericDAO[Req any, Resp any] interface { Get(ctx context.Context, req Req) (*Resp, error) List(ctx context.Context, req Req) (*Resp, error) } // 通用Get方法:包含日志、基础参数校验等通用逻辑 func GenericGet[Req any, Resp any](ctx context.Context, req Req, dao GenericDAO[Req, Resp]) (*Resp, error) { // 示例通用逻辑:打印请求日志 // log.Printf("handling get request: %v", req) // 调用DAO层的具体查询逻辑 return dao.Get(ctx, req) } // 通用List方法:包含分页参数默认值设置等通用逻辑 func GenericList[Req any, Resp any](ctx context.Context, req Req, dao GenericDAO[Req, Resp]) (*Resp, error) { // 示例通用逻辑:统一处理分页参数默认值 if listReq, ok := any(req).(interface{ GetPage() int32; SetPage(int32); GetSize() int32; SetSize(int32) }); ok { if listReq.GetPage() <= 0 { listReq.SetPage(1) } if listReq.GetSize() <= 0 || listReq.GetSize() > 100 { listReq.SetSize(20) } } // 调用DAO层的具体分页查询逻辑 return dao.List(ctx, req) }
第二步:为具体Proto服务实现DAO和服务接口
以OplAccount服务为例:
package service import ( "context" "github.com/your-project/protos" ) // OplAccountDAO 实现GenericDAO接口,处理OplAccount的数据库操作 type OplAccountDAO struct{} func (d *OplAccountDAO) Get(ctx context.Context, req *protos.OplAccountGetRequest) (*protos.OplAccountGetResponse, error) { // 这里写具体的数据库查询逻辑 return &protos.OplAccountGetResponse{Account: &protos.OplAccount{Id: req.Id, Name: "test_account"}}, nil } func (d *OplAccountDAO) List(ctx context.Context, req *protos.OplAccountListRequest) (*protos.OplAccountListResponse, error) { // 这里写具体的数据库分页查询逻辑 return &protos.OplAccountListResponse{ Accounts: []*protos.OplAccount{{Id: 1, Name: "user1"}, {Id: 2, Name: "user2"}}, }, nil } // OplAccountService 实现Proto定义的OplAccountGlobalObjectDefaultService接口 type OplAccountService struct { protos.UnimplementedOplAccountGlobalObjectDefaultServiceServer dao *OplAccountDAO } func NewOplAccountService() *OplAccountService { return &OplAccountService{dao: &OplAccountDAO{}} } // Get 调用通用Get方法 func (s *OplAccountService) Get(ctx context.Context, req *protos.OplAccountGetRequest) (*protos.OplAccountGetResponse, error) { return GenericGet(ctx, req, s.dao) } // List 调用通用List方法 func (s *OplAccountService) List(ctx context.Context, req *protos.OplAccountListRequest) (*protos.OplAccountListResponse, error) { return GenericList(ctx, req, s.dao) }
第三步:DeliveryAccount服务的实现(复用通用逻辑)
package service import ( "context" "github.com/your-project/protos" ) type DeliveryAccountDAO struct{} func (d *DeliveryAccountDAO) Get(ctx context.Context, req *protos.DelAccountGetRequest) (*protos.DelAccountGetResponse, error) { // 这里写DeliveryAccount的具体查询逻辑 return &protos.DelAccountGetResponse{Account: &protos.DelAccount{Id: req.Id, Address: "test_address"}}, nil } func (d *DeliveryAccountDAO) List(ctx context.Context, req *protos.DelAccountListRequest) (*protos.DelAccountListResponse, error) { // 这里写DeliveryAccount的具体分页查询逻辑 return &protos.DelAccountListResponse{ Accounts: []*protos.DelAccount{{Id: 1, Address: "addr1"}, {Id: 2, Address: "addr2"}}, }, nil } type DeliveryAccountService struct { protos.UnimplementedDelAccountGlobalObjectDefaultServiceServer dao *DeliveryAccountDAO } func NewDeliveryAccountService() *DeliveryAccountService { return &DeliveryAccountService{dao: &DeliveryAccountDAO{}} } func (s *DeliveryAccountService) Get(ctx context.Context, req *protos.DelAccountGetRequest) (*protos.DelAccountGetResponse, error) { return GenericGet(ctx, req, s.dao) } func (s *DeliveryAccountService) List(ctx context.Context, req *protos.DelAccountListRequest) (*protos.DelAccountListResponse, error) { return GenericList(ctx, req, s.dao) }
扩展:处理特殊业务逻辑
如果某个服务需要在通用逻辑之外添加自定义处理,可以在服务层方法中追加逻辑,或修改泛型函数注入钩子:
// 修改GenericGet,支持注入前置/后置钩子 func GenericGet[Req any, Resp any](ctx context.Context, req Req, dao GenericDAO[Req, Resp], preHook func(ctx context.Context, req Req) error, postHook func(ctx context.Context, req Req, resp *Resp) error) (*Resp, error) { if preHook != nil { if err := preHook(ctx, req); err != nil { return nil, err } } resp, err := dao.Get(ctx, req) if err != nil { return nil, err } if postHook != nil { if err := postHook(ctx, req, resp); err != nil { return nil, err } } return resp, nil } // 在OplAccount的Get方法中使用钩子添加自定义校验 func (s *OplAccountService) Get(ctx context.Context, req *protos.OplAccountGetRequest) (*protos.OplAccountGetResponse, error) { preHook := func(ctx context.Context, req *protos.OplAccountGetRequest) error { if req.Id <= 0 { return errors.New("invalid account ID") } return nil } return GenericGet(ctx, req, s.dao, preHook, nil) }
内容的提问来源于stack exchange,提问作者newbieitTech
相关产品推荐
相关产品推荐

