kiwi-go-backend
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseKiwi Go Backend Development
Kiwi Go 后端开发规范
This skill defines the standards for building Go backends in the kiwi ecosystem. All code generation, refactoring, and new feature work MUST follow these rules.
本规范定义了kiwi生态中Go后端服务的开发标准。所有代码生成、重构及新功能开发必须遵循以下规则。
Architecture Overview
架构概览
Four-layer DDD architecture with strict dependency direction: .
api -> application -> domain <- infrastructure| Layer | Responsibility | Key Rule |
|---|---|---|
| API | HTTP handling, param parsing, response serialization | Thin layer. No business logic. |
| Application | Orchestrates domain services for use cases | Can READ from repo. CANNOT WRITE directly. |
| Domain | Business logic, entities, aggregate roots, VOs | Pure Go. No framework dependencies. |
| Infrastructure | Repository implementations, external clients, DI | Implements domain interfaces. |
See references/project-structure.md for full directory layout.
采用四层DDD架构,依赖方向严格遵循:
api -> application -> domain <- infrastructure| 层级 | 职责 | 核心规则 |
|---|---|---|
| API | HTTP请求处理、参数解析、响应序列化 | 轻量级层级,不包含业务逻辑 |
| 应用层 | 编排领域服务实现用例 | 可从仓库读取数据,禁止直接写入 |
| 领域层 | 业务逻辑、实体、聚合根、值对象 | 纯Go实现,无框架依赖 |
| 基础设施层 | 仓库实现、外部客户端、依赖注入 | 实现领域层定义的接口 |
完整目录结构请参考 references/project-structure.md。
Domain Layer Rules
领域层规则
Naming Conventions
命名规范
- Entities: suffix (e.g.,
*Entity)UserEntity - Aggregate Roots: suffix (e.g.,
*AggregateRoot)OrderAggregateRoot - Interfaces: prefix (e.g.,
I)IOrderRepository
- 实体:以为后缀(例如:
*Entity)UserEntity - 聚合根:以为后缀(例如:
*AggregateRoot)OrderAggregateRoot - 接口:以为前缀(例如:
I)IOrderRepository
Aggregate Root = Pure Container
聚合根 = 纯容器
- NO direct data attributes on aggregate root
- Structure: +
KeyEntity+ business logic methods[]ChildEntity - All data lives in the KeyEntity
- 聚合根上禁止直接定义数据属性
- 结构:+
KeyEntity+ 业务逻辑方法[]ChildEntity - 所有数据存储在KeyEntity中
Value Objects
值对象
- Do NOT create a VO with fewer than 3 fields
- 1-2 field concepts stay as primitives on the Entity
- Store VOs as in ent schema, NOT separate tables
field.JSON
- 字段数少于3个的概念不要定义为值对象
- 1-2个字段的概念直接作为实体的原生类型字段
- 值对象在ent schema中以存储,不单独建表
field.JSON
Enums
枚举类型
Every enum type MUST have a function returning all values. This is used by ent schemas.
GetAll*()go
type OrderStatus string
const (
OrderStatusPending OrderStatus = "pending"
OrderStatusCompleted OrderStatus = "completed"
)
func GetAllOrderStatuses() []OrderStatus {
return []OrderStatus{OrderStatusPending, OrderStatusCompleted}
}每个枚举类型必须包含函数返回所有枚举值,供ent schema使用。
GetAll*()go
type OrderStatus string
const (
OrderStatusPending OrderStatus = "pending"
OrderStatusCompleted OrderStatus = "completed"
)
func GetAllOrderStatuses() []OrderStatus {
return []OrderStatus{OrderStatusPending, OrderStatusCompleted}
}Repository Interfaces (Triple Pattern)
仓库接口(三重模式)
Defined in :
domain/{domain}/repository/interface.gogo
type IOrderRepositoryRead interface {
FindByID(ctx context.Context, id uuid.UUID) (*aggregate.OrderAggregateRoot, error)
}
type IOrderRepositoryWrite interface {
Save(ctx context.Context, root *aggregate.OrderAggregateRoot) (*aggregate.OrderAggregateRoot, error)
}
type IOrderRepository interface {
IOrderRepositoryRead
IOrderRepositoryWrite
contract.ITransactionDecorator
}- ReadOnly: can return or
*Entity*AggregateRoot - Write: MUST operate on only
*AggregateRoot
定义在中:
domain/{domain}/repository/interface.gogo
type IOrderRepositoryRead interface {
FindByID(ctx context.Context, id uuid.UUID) (*aggregate.OrderAggregateRoot, error)
}
type IOrderRepositoryWrite interface {
Save(ctx context.Context, root *aggregate.OrderAggregateRoot) (*aggregate.OrderAggregateRoot, error)
}
type IOrderRepository interface {
IOrderRepositoryRead
IOrderRepositoryWrite
contract.ITransactionDecorator
}- 只读接口:可返回或
*Entity*AggregateRoot - 写入接口:仅允许操作
*AggregateRoot
Transaction Contract
事务契约
Defined in :
domain/common/contract/transaction.gogo
type ITransactionDecorator interface {
WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
}See references/domain-layer.md for detailed patterns.
定义在中:
domain/common/contract/transaction.gogo
type ITransactionDecorator interface {
WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
}详细模式请参考 references/domain-layer.md。
Application Layer Rules
应用层规则
- Orchestrates domain services to implement use cases
- Can inject and use interfaces for queries
RepositoryRead - CANNOT call directly -- MUST go through Domain Service
RepositoryWrite - Input: can reuse API layer DTOs or define own
- Output: application-defined response models
- Translates domain errors to types
libfacade.Error
go
// application/service/error/error.go
var (
ErrOrderNotFound = libfacade.NewNotFoundError("order not found")
ErrForbidden = libfacade.NewForbiddenError("access denied")
)See references/api-application-layer.md for full patterns.
- 编排领域服务实现业务用例
- 可注入并使用接口进行查询
RepositoryRead - 禁止直接调用,必须通过领域服务完成写入
RepositoryWrite - 输入:可复用API层DTO或自定义
- 输出:应用层自定义响应模型
- 将领域层错误转换为类型
libfacade.Error
go
// application/service/error/error.go
var (
ErrOrderNotFound = libfacade.NewNotFoundError("订单不存在")
ErrForbidden = libfacade.NewForbiddenError("访问被拒绝")
)完整模式请参考 references/api-application-layer.md。
API Layer Rules
API层规则
- Thin layer: parse params, call application service, return response
- No business logic, no direct domain service calls, no repository usage
- DTOs in with
api/dto/andjsontagsbinding - Use generic handler wrappers for consistent error/response formatting:
go
// Normal endpoint (no auth)
NormalHandler[T any](f func(*gin.Context) (T, *libfacade.Error))
// Auth-required endpoint (extracts userID from middleware)
RequireUserHandler[T any](f func(*gin.Context, string) (T, *libfacade.Error))
// SSE streaming
EventStreamHandler(f func(*gin.Context) *libfacade.Error)
EventStreamRequireUserHandler(f func(*gin.Context, string) *libfacade.Error)Handler wrappers auto-wrap responses in and handle panics.
libfacade.BaseResponse{Status, Data}- 轻量级层级:解析参数、调用应用服务、返回响应
- 无业务逻辑,禁止直接调用领域服务或仓库
- DTO定义在目录,包含
api/dto/和json标签binding - 使用通用处理器包装器实现统一的错误/响应格式:
go
// 普通端点(无需鉴权)
NormalHandler[T any](f func(*gin.Context) (T, *libfacade.Error))
// 需要鉴权的端点(从中间件提取userID)
RequireUserHandler[T any](f func(*gin.Context, string) (T, *libfacade.Error))
// SSE流
EventStreamHandler(f func(*gin.Context) *libfacade.Error)
EventStreamRequireUserHandler(f func(*gin.Context, string) *libfacade.Error)处理器包装器会自动将响应包装为格式,并处理panic。
libfacade.BaseResponse{Status, Data}Infrastructure Layer Rules
基础设施层规则
Repository Implementation
仓库实现
Every repository embeds + . ALL methods use pattern:
transactionDecoratorclientGetterwithDbClientgo
func (r *repo) FindByID(ctx context.Context, id uuid.UUID) (*aggregate.Root, error) {
var root *aggregate.Root
err := r.clientGetter.withDbClient(ctx, func(ctx context.Context, dbClient *ent.Client) error {
entItem, err := dbClient.Order.Query().Where(order.ID(id)).Only(ctx)
if err != nil {
if ent.IsNotFound(err) { return OrderNotFoundError }
return xerror.Wrap(err)
}
root = r.entToDomain(entItem)
return nil
})
return root, err
}每个仓库需嵌入 + 。所有方法必须使用模式:
transactionDecoratorclientGetterwithDbClientgo
func (r *repo) FindByID(ctx context.Context, id uuid.UUID) (*aggregate.Root, error) {
var root *aggregate.Root
err := r.clientGetter.withDbClient(ctx, func(ctx context.Context, dbClient *ent.Client) error {
entItem, err := dbClient.Order.Query().Where(order.ID(id)).Only(ctx)
if err != nil {
if ent.IsNotFound(err) { return OrderNotFoundError }
return xerror.Wrap(err)
}
root = r.entToDomain(entItem)
return nil
})
return root, err
}Ent Schema Rules
Ent Schema规则
Mandatory Fields — EVERY table MUST have these 3 fields:
- — UUIDv7 primary key, immutable:
idfield.UUID("id", uuid.UUID{}).Default(func() uuid.UUID { id, _ := uuid.NewV7(); return id }).Immutable() - — Auto-set on creation, never modified afterward
created_at - — Auto-updated on every mutation
updated_at
Add to every schema's method. This provides and with correct auto-behavior: defaults to at creation and is immutable; defaults to and auto-updates on every save via ent's .
mixin.Time{}Mixin()created_atupdated_atcreated_attime.Now()updated_attime.Now()UpdateDefaultNo exceptions. A table without , , is invalid.
idcreated_atupdated_atOther schema rules:
- Enums: -- NEVER hardcode
field.Enum("status").Values(utils.EnumToStrings(vo.GetAllOrderStatuses())...) - VOs as JSON:
field.JSON("metadata", &vo.OrderMetadata{}).Optional()
See references/infrastructure-layer.md for full patterns.
必填字段 — 每张表必须包含以下3个字段:
- — UUIDv7主键,不可变:
idfield.UUID("id", uuid.UUID{}).Default(func() uuid.UUID { id, _ := uuid.NewV7(); return id }).Immutable() - — 创建时自动设置,后续不可修改
created_at - — 每次变更时自动更新
updated_at
在每个schema的方法中添加,该mixin会自动提供和的正确行为:默认设为创建时的且不可变;默认设为,并通过ent的在每次保存时自动更新。
Mixin()mixin.Time{}created_atupdated_atcreated_attime.Now()updated_attime.Now()UpdateDefault无例外。缺少、、的表视为无效。
idcreated_atupdated_at其他schema规则:
- 枚举类型:— 禁止硬编码
field.Enum("status").Values(utils.EnumToStrings(vo.GetAllOrderStatuses())...) - 值对象存储为JSON:
field.JSON("metadata", &vo.OrderMetadata{}).Optional()
完整模式请参考 references/infrastructure-layer.md。
Database Workflow
数据库工作流
- Define/modify ent schema in
infrastructure/repository/ent/schema/ - Generate ent code:
make generate-repository-code - Generate migration:
make generate-migration-repository-db - Apply migration:
make apply-migration-repository-db
See references/database-workflow.md for Makefile and details.
- 在中定义/修改ent schema
infrastructure/repository/ent/schema/ - 生成ent代码:
make generate-repository-code - 生成迁移文件:
make generate-migration-repository-db - 执行迁移:
make apply-migration-repository-db
Makefile及详细说明请参考 references/database-workflow.md。
Dependency Injection (fx)
依赖注入(fx)
All wiring uses . Each layer exports a variable.
go.uber.org/fxModulego
// main.go
app := fx.New(
fx.NopLogger,
api.Module,
application.Module,
domain.Module,
infrastructure.Module,
)
app.Run()Repository binding uses + to map concrete types to all three interfaces:
fx.Annotatefx.Asgo
fx.Annotate(
repository.NewOrderRepository,
fx.As(new(orderrepo.IOrderRepository)),
fx.As(new(orderrepo.IOrderRepositoryRead)),
fx.As(new(orderrepo.IOrderRepositoryWrite)),
),See references/dependency-injection.md for module patterns.
所有组件 wiring 使用。每个层级需导出变量。
go.uber.org/fxModulego
// main.go
app := fx.New(
fx.NopLogger,
api.Module,
application.Module,
domain.Module,
infrastructure.Module,
)
app.Run()仓库绑定需使用 + 将具体类型映射到三个接口:
fx.Annotatefx.Asgo
fx.Annotate(
repository.NewOrderRepository,
fx.As(new(orderrepo.IOrderRepository)),
fx.As(new(orderrepo.IOrderRepositoryRead)),
fx.As(new(orderrepo.IOrderRepositoryWrite)),
),模块模式请参考 references/dependency-injection.md。
Error Handling
错误处理
| Layer | Method | Example |
|---|---|---|
| Domain/Infrastructure | | |
| Application | | |
| API | Handler wrappers auto-format | Automatic |
NEVER use , , or return raw unwrapped errors.
fmt.Errorf()errors.New()| 层级 | 方式 | 示例 |
|---|---|---|
| 领域层/基础设施层 | | |
| 应用层 | | |
| API层 | 处理器包装器自动格式化 | 自动处理 |
禁止使用、或返回未包装的原生错误。
fmt.Errorf()errors.New()Key Import Paths
核心导入路径
go
import (
libgin "github.com/Yet-Another-AI-Project/kiwi-lib/server/gin"
libfacade "github.com/Yet-Another-AI-Project/kiwi-lib/server/facade"
"github.com/futurxlab/golanggraph/logger" // logger.ILogger
"github.com/futurxlab/golanggraph/xerror" // xerror.Wrap, xerror.New
"entgo.io/ent" // ORM
"go.uber.org/fx" // DI
"github.com/google/uuid" // uuid.NewV7()
"github.com/gookit/config/v2" // Config loading
"github.com/redis/go-redis/v9" // Redis client
)go
import (
libgin "github.com/Yet-Another-AI-Project/kiwi-lib/server/gin"
libfacade "github.com/Yet-Another-AI-Project/kiwi-lib/server/facade"
"github.com/futurxlab/golanggraph/logger" // logger.ILogger
"github.com/futurxlab/golanggraph/xerror" // xerror.Wrap, xerror.New
"entgo.io/ent" // ORM
"go.uber.org/fx" // DI
"github.com/google/uuid" // uuid.NewV7()
"github.com/gookit/config/v2" // 配置加载
"github.com/redis/go-redis/v9" // Redis客户端
)Config Pattern
配置模式
Use with YAML driver. Config struct uses tags. Load via flag-based config file path.
gookit/config/v2config:"field_name"go
type Config struct {
Server ServerConfig `config:"server"`
Postgresql PostgresqlConfig `config:"postgres"`
Redis RedisConfig `config:"redis"`
Log LogConfig `config:"log"`
}使用搭配YAML驱动。配置结构体使用标签。通过命令行指定配置文件路径加载配置。
gookit/config/v2config:"field_name"go
type Config struct {
Server ServerConfig `config:"server"`
Postgresql PostgresqlConfig `config:"postgres"`
Redis RedisConfig `config:"redis"`
Log LogConfig `config:"log"`
}Coding Checklist
编码检查清单
Before submitting any code, verify:
- All enum types have function
GetAll*() - All use
return errxerror.Wrap(err) - All new errors use
xerror.New("message") - No or
fmt.Errorf()usageerrors.New() - Aggregate roots have no direct attributes (use KeyEntity)
- VOs have 3+ fields (otherwise use primitives)
- Ent schemas use for enums, not hardcoded strings
utils.EnumToStrings() - Every ent schema has UUIDv7 field (immutable) and
idformixin.Time{}/created_atupdated_at - Repository methods use pattern
withDbClient - Application layer reads via , writes via Domain Service
RepositoryRead - API layer is thin -- no business logic
- fx modules use +
fx.Annotatefor repository bindingfx.As - Run after schema changes
make generate-repository-code
提交代码前,请验证以下内容:
- 所有枚举类型都有函数
GetAll*() - 所有均使用
return errxerror.Wrap(err) - 所有新错误均使用
xerror.New("message") - 未使用或
fmt.Errorf()errors.New() - 聚合根无直接属性(使用KeyEntity)
- 值对象字段数≥3(否则使用原生类型)
- Ent schema使用处理枚举,未硬编码字符串
utils.EnumToStrings() - 每张ent schema都有UUIDv7类型的字段(不可变),并通过
id实现mixin.Time{}/created_atupdated_at - 仓库方法使用模式
withDbClient - 应用层通过读取数据,通过领域服务写入数据
RepositoryRead - API层为轻量级,无业务逻辑
- Fx模块使用+
fx.Annotate进行仓库绑定fx.As - 修改schema后执行
make generate-repository-code
Utility: EnumToStrings
工具函数:EnumToStrings
go
// utils/enum_converter.go
type EnumType interface{ ~string }
func EnumToStrings[T EnumType](enums []T) []string {
result := make([]string, len(enums))
for i, e := range enums { result[i] = string(e) }
return result
}go
// utils/enum_converter.go
type EnumType interface{ ~string }
func EnumToStrings[T EnumType](enums []T) []string {
result := make([]string, len(enums))
for i, e := range enums { result[i] = string(e) }
return result
}