开发最佳实践
本文档总结了使用 Firefly Go 模板开发微服务时的最佳实践和建议。
1. 核心原则
1.1 协议驱动开发 (Schema-First)
始终先定义 API 协议(Protobuf),再编写代码。
- 禁止直接修改
dep/protobuf/gen下的代码。 - 禁止在 Service 层手动编写复杂的参数校验逻辑,应优先使用
protovalidate在 Proto 中定义规则。
1.2 依赖注入
所有组件依赖必须通过构造函数传递,禁止使用全局变量获取依赖。
- 正确:
func NewDemoUseCase(repo repo.DemoRepo) *DemoUseCase - 错误: 在方法内部直接调用
data.GlobalDB或conf.GetConfig()。
每次修改了依赖关系(如新增了 Repo 或 UseCase),都必须重新运行 wire ./cmd/server。
2. 错误处理
2.1 分层错误处理
- Data 层:返回原始错误(如
gorm.ErrRecordNotFound)或包装后的错误。 - Biz 层:根据业务逻辑处理错误。如果是业务错误(如“余额不足”),应定义专门的 Error 类型或常量。
- Service 层:是错误的终点。必须将所有内部错误转换为 gRPC 状态码(Status Code)和错误信息。
- 使用
status.Errorf(codes.NotFound, "user not found")返回错误。 - 不要直接将底层的数据库错误信息暴露给前端/客户端。
- 使用
2.2 日志记录
- 错误日志:通常在 Service 层捕获到错误并准备返回给客户端时记录。避免在 Data、Biz、Service 层重复记录同一个错误。
- 上下文:gRPC 入口会注入
go-micro/service.Context,访问日志和 SQL 日志可读取用户、租户、服务实例和 TraceID。
2.3 服务上下文
- 推荐:Service 层通过
service.FromContext(ctx)读取go-micro/service.Context。 - 禁止:在 Biz/Data 层重复解析 gRPC metadata。
- 禁止:继续使用旧
UserContextMeta作为服务内主上下文。
3. 数据库与事务
3.1 事务管理
使用 internal/data/transaction.go 中提供的事务闭包。
go
func (uc *DemoUseCase) CreateOrder(ctx context.Context, order *Order) error {
return uc.tx.ExecTx(ctx, func(ctx context.Context) error {
if err := uc.repo.CreateOrder(ctx, order); err != nil {
return err
}
if err := uc.repo.DeductInventory(ctx, order.ItemID); err != nil {
return err
}
return nil
})
}3.2 避免在循环中查询数据库
- 禁止: 在
for循环中执行repo.GetByID(id)。 - 推荐: 先收集所有 ID,调用
repo.ListByIDs(ids)批量查询,然后在内存中映射。
4. 配置管理
- 环境隔离:不同环境(Dev, Test, Prod)通过
bootstrap.json.app.env与运行期配置 Store 隔离。 - 敏感信息:数据库密码、API Key 等敏感信息禁止直接硬编码在代码中,应通过环境变量或加密配置加载。
- 配置边界:
bootstrap.json只放服务启动必需配置;MySQL、Redis 等运行期组件配置通过go-micro/config.Store读取。
5. 代码风格
- 命名:遵循 Go 官方命名规范。接口名以
er结尾(如Reader),实现类通常去掉后缀或加上Impl(但在本模板中,Repo 实现通常命名为demoRepo,UseCase 命名为DemoUseCase)。 - DTO 转换:禁止在业务代码中手动进行复杂的字段拷贝,应在
internal/biz/convert中定义接口并使用goverter生成。