You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 01:15:19