kiwi-go-backend

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Kiwi 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
.
LayerResponsibilityKey Rule
APIHTTP handling, param parsing, response serializationThin layer. No business logic.
ApplicationOrchestrates domain services for use casesCan READ from repo. CANNOT WRITE directly.
DomainBusiness logic, entities, aggregate roots, VOsPure Go. No framework dependencies.
InfrastructureRepository implementations, external clients, DIImplements domain interfaces.
See references/project-structure.md for full directory layout.
采用四层DDD架构,依赖方向严格遵循:
api -> application -> domain <- infrastructure
层级职责核心规则
APIHTTP请求处理、参数解析、响应序列化轻量级层级,不包含业务逻辑
应用层编排领域服务实现用例可从仓库读取数据,禁止直接写入
领域层业务逻辑、实体、聚合根、值对象纯Go实现,无框架依赖
基础设施层仓库实现、外部客户端、依赖注入实现领域层定义的接口
完整目录结构请参考 references/project-structure.md

Domain Layer Rules

领域层规则

Naming Conventions

命名规范

  • Entities:
    *Entity
    suffix (e.g.,
    UserEntity
    )
  • Aggregate Roots:
    *AggregateRoot
    suffix (e.g.,
    OrderAggregateRoot
    )
  • Interfaces:
    I
    prefix (e.g.,
    IOrderRepository
    )
  • 实体:以
    *Entity
    为后缀(例如:
    UserEntity
  • 聚合根:以
    *AggregateRoot
    为后缀(例如:
    OrderAggregateRoot
  • 接口:以
    I
    为前缀(例如:
    IOrderRepository

Aggregate Root = Pure Container

聚合根 = 纯容器

  • NO direct data attributes on aggregate root
  • Structure:
    KeyEntity
    +
    []ChildEntity
    + business logic methods
  • 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
    field.JSON
    in ent schema, NOT separate tables
  • 字段数少于3个的概念不要定义为值对象
  • 1-2个字段的概念直接作为实体的原生类型字段
  • 值对象在ent schema中以
    field.JSON
    存储,不单独建表

Enums

枚举类型

Every enum type MUST have a
GetAll*()
function returning all values. This is used by ent schemas.
go
type OrderStatus string
const (
    OrderStatusPending   OrderStatus = "pending"
    OrderStatusCompleted OrderStatus = "completed"
)
func GetAllOrderStatuses() []OrderStatus {
    return []OrderStatus{OrderStatusPending, OrderStatusCompleted}
}
每个枚举类型必须包含
GetAll*()
函数返回所有枚举值,供ent schema使用。
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.go
:
go
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
    *Entity
    or
    *AggregateRoot
  • Write: MUST operate on
    *AggregateRoot
    only
定义在
domain/{domain}/repository/interface.go
中:
go
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.go
:
go
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.go
中:
go
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
    RepositoryRead
    interfaces for queries
  • CANNOT call
    RepositoryWrite
    directly -- MUST go through Domain Service
  • Input: can reuse API layer DTOs or define own
  • Output: application-defined response models
  • Translates domain errors to
    libfacade.Error
    types
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
    api/dto/
    with
    json
    and
    binding
    tags
  • 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
libfacade.BaseResponse{Status, Data}
and handle panics.
  • 轻量级层级:解析参数、调用应用服务、返回响应
  • 无业务逻辑,禁止直接调用领域服务或仓库
  • 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)
处理器包装器会自动将响应包装为
libfacade.BaseResponse{Status, Data}
格式,并处理panic。

Infrastructure Layer Rules

基础设施层规则

Repository Implementation

仓库实现

Every repository embeds
transactionDecorator
+
clientGetter
. ALL methods use
withDbClient
pattern:
go
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
}
每个仓库需嵌入
transactionDecorator
+
clientGetter
所有方法必须使用
withDbClient
模式:
go
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:
  1. id
    — UUIDv7 primary key, immutable:
    field.UUID("id", uuid.UUID{}).Default(func() uuid.UUID { id, _ := uuid.NewV7(); return id }).Immutable()
  2. created_at
    — Auto-set on creation, never modified afterward
  3. updated_at
    — Auto-updated on every mutation
Add
mixin.Time{}
to every schema's
Mixin()
method. This provides
created_at
and
updated_at
with correct auto-behavior:
created_at
defaults to
time.Now()
at creation and is immutable;
updated_at
defaults to
time.Now()
and auto-updates on every save via ent's
UpdateDefault
.
No exceptions. A table without
id
,
created_at
,
updated_at
is invalid.
Other schema rules:
  • Enums:
    field.Enum("status").Values(utils.EnumToStrings(vo.GetAllOrderStatuses())...)
    -- NEVER hardcode
  • VOs as JSON:
    field.JSON("metadata", &vo.OrderMetadata{}).Optional()
See references/infrastructure-layer.md for full patterns.
必填字段 — 每张表必须包含以下3个字段:
  1. id
    — UUIDv7主键,不可变:
    field.UUID("id", uuid.UUID{}).Default(func() uuid.UUID { id, _ := uuid.NewV7(); return id }).Immutable()
  2. created_at
    — 创建时自动设置,后续不可修改
  3. updated_at
    — 每次变更时自动更新
在每个schema的
Mixin()
方法中添加
mixin.Time{}
,该mixin会自动提供
created_at
updated_at
的正确行为:
created_at
默认设为创建时的
time.Now()
且不可变;
updated_at
默认设为
time.Now()
,并通过ent的
UpdateDefault
在每次保存时自动更新。
无例外。缺少
id
created_at
updated_at
的表视为无效。
其他schema规则:
  • 枚举类型:
    field.Enum("status").Values(utils.EnumToStrings(vo.GetAllOrderStatuses())...)
    禁止硬编码
  • 值对象存储为JSON:
    field.JSON("metadata", &vo.OrderMetadata{}).Optional()
完整模式请参考 references/infrastructure-layer.md

Database Workflow

数据库工作流

  1. Define/modify ent schema in
    infrastructure/repository/ent/schema/
  2. Generate ent code:
    make generate-repository-code
  3. Generate migration:
    make generate-migration-repository-db
  4. Apply migration:
    make apply-migration-repository-db
See references/database-workflow.md for Makefile and details.
  1. infrastructure/repository/ent/schema/
    中定义/修改ent schema
  2. 生成ent代码:
    make generate-repository-code
  3. 生成迁移文件:
    make generate-migration-repository-db
  4. 执行迁移:
    make apply-migration-repository-db
Makefile及详细说明请参考 references/database-workflow.md

Dependency Injection (fx)

依赖注入(fx)

All wiring uses
go.uber.org/fx
. Each layer exports a
Module
variable.
go
// main.go
app := fx.New(
    fx.NopLogger,
    api.Module,
    application.Module,
    domain.Module,
    infrastructure.Module,
)
app.Run()
Repository binding uses
fx.Annotate
+
fx.As
to map concrete types to all three interfaces:
go
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/fx
。每个层级需导出
Module
变量。
go
// main.go
app := fx.New(
    fx.NopLogger,
    api.Module,
    application.Module,
    domain.Module,
    infrastructure.Module,
)
app.Run()
仓库绑定需使用
fx.Annotate
+
fx.As
将具体类型映射到三个接口:
go
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

错误处理

LayerMethodExample
Domain/Infrastructure
xerror.Wrap(err)
,
xerror.New("msg")
return xerror.Wrap(err)
Application
libfacade.NewNotFoundError()
,
NewForbiddenError()
return nil, ErrOrderNotFound
APIHandler wrappers auto-format
*libfacade.Error
Automatic
NEVER use
fmt.Errorf()
,
errors.New()
, or return raw unwrapped errors.
层级方式示例
领域层/基础设施层
xerror.Wrap(err)
xerror.New("msg")
return xerror.Wrap(err)
应用层
libfacade.NewNotFoundError()
NewForbiddenError()
return nil, ErrOrderNotFound
API层处理器包装器自动格式化
*libfacade.Error
自动处理
禁止使用
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
gookit/config/v2
with YAML driver. Config struct uses
config:"field_name"
tags. Load via flag-based config file path.
go
type Config struct {
    Server     ServerConfig     `config:"server"`
    Postgresql PostgresqlConfig `config:"postgres"`
    Redis      RedisConfig      `config:"redis"`
    Log        LogConfig        `config:"log"`
}
使用
gookit/config/v2
搭配YAML驱动。配置结构体使用
config:"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
    GetAll*()
    function
  • All
    return err
    use
    xerror.Wrap(err)
  • All new errors use
    xerror.New("message")
  • No
    fmt.Errorf()
    or
    errors.New()
    usage
  • Aggregate roots have no direct attributes (use KeyEntity)
  • VOs have 3+ fields (otherwise use primitives)
  • Ent schemas use
    utils.EnumToStrings()
    for enums, not hardcoded strings
  • Every ent schema has UUIDv7
    id
    field (immutable) and
    mixin.Time{}
    for
    created_at
    /
    updated_at
  • Repository methods use
    withDbClient
    pattern
  • Application layer reads via
    RepositoryRead
    , writes via Domain Service
  • API layer is thin -- no business logic
  • fx modules use
    fx.Annotate
    +
    fx.As
    for repository binding
  • Run
    make generate-repository-code
    after schema changes
提交代码前,请验证以下内容:
  • 所有枚举类型都有
    GetAll*()
    函数
  • 所有
    return err
    均使用
    xerror.Wrap(err)
  • 所有新错误均使用
    xerror.New("message")
  • 未使用
    fmt.Errorf()
    errors.New()
  • 聚合根无直接属性(使用KeyEntity)
  • 值对象字段数≥3(否则使用原生类型)
  • Ent schema使用
    utils.EnumToStrings()
    处理枚举,未硬编码字符串
  • 每张ent schema都有UUIDv7类型的
    id
    字段(不可变),并通过
    mixin.Time{}
    实现
    created_at
    /
    updated_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
}