Files
junhong_cmp_fiber/.specify/memory/constitution.md

25 KiB
Raw Blame History

君鸿卡管系统 Constitution

Core Principles

I. Tech Stack Adherence (技术栈遵守)

规则 (RULES):

  • 开发时 MUST 严格遵守项目定义的技术栈Fiber + GORM + Viper + Zap + Lumberjack.v2 + Validator + sonic JSON + Asynq + PostgreSQL
  • MUST NOT 使用原生调用或绕过框架的快捷方式(禁止 database/sql 直接调用、禁止 net/http 替代 Fiber、禁止 encoding/json 替代 sonic
  • 所有 HTTP 路由和中间件 MUST 使用 Fiber 框架
  • 所有数据库操作 MUST 通过 GORM 进行
  • 所有配置管理 MUST 使用 Viper
  • 所有日志记录 MUST 使用 Zap + Lumberjack.v2
  • 所有 JSON 序列化 MUST 使用 sonic
  • 所有异步任务 MUST 使用 Asynq
  • MUST 使用 Go 官方工具链:go fmtgo vetgolangci-lint
  • MUST 使用 Go Modules 进行依赖管理

理由 (RATIONALE):

一致的技术栈使用确保代码可维护性、团队协作效率和长期技术债务可控。绕过框架的"快捷方式"会导致代码碎片化、难以调试、性能不一致和安全漏洞。框架选择已经过深思熟虑必须信任并充分利用其生态系统。Go 官方工具链确保代码风格一致性和质量。


II. Code Quality Standards (代码质量标准)

规则 (RULES):

  • 代码 MUST 遵循项目分层架构:Handler → Service → Store → Model
  • Handler 层 MUST ONLY 处理 HTTP 请求/响应,不得包含业务逻辑
  • Service 层 MUST 包含所有业务逻辑,支持跨模块调用
  • Store 层 MUST 统一管理所有数据访问,支持事务处理
  • Model 层 MUST 定义清晰的数据结构和 DTO
  • 所有依赖 MUST 通过结构体字段进行依赖注入(不使用构造函数模式)
  • 所有公共错误 MUSTpkg/errors/ 中定义,使用统一错误码
  • 所有 API 响应 MUST 使用 pkg/response/ 的统一格式
  • 所有常量 MUSTpkg/constants/ 中定义和管理
  • 所有 Redis key MUST 通过 pkg/constants/ 中的 Key 生成函数统一管理
  • MUST 为所有导出的函数、类型和常量编写 Go 风格的文档注释(// FunctionName does something...
  • MUST 避免 magic numbers 和 magic strings使用常量定义

Go 代码风格要求:

  • MUST 使用 gofmt 格式化所有代码
  • MUST 遵循 Effective GoGo Code Review Comments
  • 变量命名 MUST 使用 Go 风格:userID(不是 userId)、HTTPServer(不是 HttpServer
  • 缩写词 MUST 全部大写或全部小写:URLIDHTTP(导出)或 urlidhttp(未导出)
  • 包名 MUST 简短、小写、单数、无下划线:userorderpkg(不是 usersuserServiceuser_service
  • 接口命名 SHOULD 使用 -er 后缀:ReaderWriterLogger(不是 ILoggerLoggerInterface

常量管理规范 (Constants Management):

  • 业务常量(状态码、类型枚举等)MUST 定义在 pkg/constants/constants.go 或按模块分文件
  • Redis key MUST 使用函数生成,不允许硬编码字符串拼接
  • Redis key 生成函数 MUST 遵循命名规范:Redis{Module}{Purpose}Key(params...)
  • Redis key 格式 MUST 使用冒号分隔:{module}:{purpose}:{identifier}
  • 示例:
    // 正确:使用常量和生成函数
    constants.SIMStatusActive
    constants.RedisSIMStatusKey(iccid)  // 生成 "sim:status:{iccid}"
    
    // 错误:硬编码和拼接
    status := "active"
    key := "sim:status:" + iccid
    

理由 (RATIONALE):

清晰的分层架构和代码组织使代码易于理解、测试和维护。统一的错误处理和响应格式提升 API 一致性和客户端集成体验。依赖注入模式便于单元测试和模块替换。集中管理常量和 Redis key 避免拼写错误、重复定义和命名不一致提升代码可维护性和重构安全性。Redis key 统一管理便于监控、调试和缓存策略调整。遵循 Go 官方代码风格确保代码一致性和可读性。


III. Testing Standards (测试标准)

规则 (RULES):

  • 所有核心业务逻辑Service 层)MUST 有单元测试覆盖
  • 所有 API 端点 MUST 有集成测试覆盖
  • 所有数据库操作 SHOULD 有事务回滚测试
  • 测试 MUST 使用 Go 标准测试框架(testing 包)
  • 测试文件 MUST 与源文件同目录,命名为 *_test.go
  • 测试函数 MUST 使用 Test 前缀:func TestUserCreate(t *testing.T)
  • 基准测试 MUST 使用 Benchmark 前缀:func BenchmarkUserCreate(b *testing.B)
  • 测试 MUST 可独立运行,不依赖外部服务(使用 mock 或 testcontainers
  • 单元测试 MUST 在 100ms 内完成
  • 集成测试 SHOULD 在 1s 内完成
  • 测试覆盖率 SHOULD 达到 70% 以上(核心业务代码必须 90% 以上)
  • 测试 MUST 使用 table-driven tests 处理多个测试用例
  • 测试 MUST 使用 t.Helper() 标记辅助函数

Table-Driven Test 示例:

func TestUserValidate(t *testing.T) {
    tests := []struct {
        name    string
        user    User
        wantErr bool
    }{
        {"valid user", User{Name: "John", Email: "john@example.com"}, false},
        {"empty name", User{Name: "", Email: "john@example.com"}, true},
        {"invalid email", User{Name: "John", Email: "invalid"}, true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := tt.user.Validate()
            if (err != nil) != tt.wantErr {
                t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
            }
        })
    }
}

理由 (RATIONALE):

高质量的测试是代码质量的基石。单元测试确保业务逻辑正确性,集成测试确保模块间协作正常。快速的测试执行时间保证开发效率。测试独立性避免环境依赖导致的 flaky tests。Table-driven tests 使测试更简洁、易于扩展和维护,这是 Go 社区的最佳实践。


IV. User Experience Consistency (用户体验一致性)

规则 (RULES):

  • 所有 API 响应 MUST 使用统一的 JSON 格式:
    {
      "code": 0,
      "message": "success",
      "data": {},
      "timestamp": "2025-11-10T15:30:00Z"
    }
    
  • 所有错误响应 MUST 包含明确的错误码和错误消息(中英文双语)
  • 所有 API 端点 MUST 遵循 RESTful 设计原则
  • 所有分页 API MUST 使用统一的分页参数:pagepage_sizetotal
  • 所有时间字段 MUST 使用 ISO 8601 格式RFC3339
  • 所有货币金额 MUST 使用整数表示(分为单位),避免浮点精度问题
  • 所有布尔字段 MUST 使用 true/false,不使用 0/1
  • API 版本 MUST 通过 URL 路径管理(如 /api/v1/...

理由 (RATIONALE):

一致的 API 设计降低客户端开发成本,减少集成错误。统一的数据格式和错误处理提升系统可预测性。清晰的时间和金额表示避免常见的数据处理错误。


V. Performance Requirements (性能要求)

规则 (RULES):

  • API 响应时间P95MUST < 200ms数据库查询 < 50ms
  • API 响应时间P99MUST < 500ms
  • 批量操作 MUST 使用批量查询/插入,避免 N+1 查询问题
  • 所有数据库查询 MUST 有适当的索引支持
  • 列表查询 MUST 实现分页,默认 page_size=20,最大 page_size=100
  • 异步任务 MUST 用于非实时操作(批量同步、分佣计算等)
  • 内存使用API 服务)SHOULD < 500MB正常负载
  • 内存使用Worker 服务)SHOULD < 1GB正常负载
  • 数据库连接池 MUST 配置合理(MaxOpenConns=25, MaxIdleConns=10, ConnMaxLifetime=5m
  • Redis 连接池 MUST 配置合理(PoolSize=10, MinIdleConns=5
  • 并发操作 SHOULD 使用 goroutines 和 channels不是线程池模式
  • MUST 使用 context.Context 进行超时和取消控制
  • MUST 使用 sync.Pool 复用频繁分配的对象(如缓冲区)

理由 (RATIONALE):

性能要求确保系统在生产环境下的稳定性和用户体验。批量操作和异步任务避免阻塞主流程。合理的连接池配置平衡性能和资源消耗。明确的性能指标便于监控和优化。使用 Go 的并发原语goroutines、channels而不是传统的线程池模式充分发挥 Go 的并发优势。


VI. Go Idiomatic Design Principles (Go 语言惯用设计原则)

核心理念:写 Go 味道的代码,不要写 Java 味道的代码

包组织 (Package Organization)

MUST 遵循的原则:

  • 包结构 MUST 扁平化,避免深层嵌套(最多 2-3 层)
  • MUST 按功能组织,不是按层次组织
  • 包名 MUST 描述功能,不是类型(http 不是 httputilshandlers

正确的 Go 风格:

internal/
├── user/          # user 功能的所有代码
│   ├── handler.go # HTTP handlers
│   ├── service.go # 业务逻辑
│   ├── store.go   # 数据访问
│   └── model.go   # 数据模型
├── order/
│   ├── handler.go
│   ├── service.go
│   └── store.go
└── sim/
    ├── handler.go
    ├── service.go
    └── store.go

错误的 Java 风格(禁止):

internal/
├── handlers/
│   ├── user/
│   │   └── UserHandler.go      # ❌ 类名式命名
│   └── order/
│       └── OrderHandler.go
├── services/
│   ├── user/
│   │   ├── IUserService.go     # ❌ 接口前缀 I
│   │   └── UserServiceImpl.go  # ❌ Impl 后缀
│   └── impls/                   # ❌ 过度抽象
├── repositories/                # ❌ Repository 模式过度使用
│   └── interfaces/
└── models/
    └── entities/
        └── dto/                 # ❌ 过度分层

接口设计 (Interface Design)

MUST 遵循的原则:

  • 接口 MUST 小而专注1-3 个方法),不是大而全
  • 接口 SHOULD 在使用方定义,不是实现方(依赖倒置)
  • 接口命名 SHOULD 使用 -er 后缀:ReaderWriterStorer
  • MUST NOT 使用 I 前缀或 Interface 后缀
  • MUST NOT 创建只有一个实现的接口(除非明确需要抽象)

正确的 Go 风格:

// 小接口,在使用方定义
type UserStorer interface {
    Create(ctx context.Context, user *User) error
    GetByID(ctx context.Context, id string) (*User, error)
}

// 具体实现
type PostgresStore struct {
    db *gorm.DB
}

func (s *PostgresStore) Create(ctx context.Context, user *User) error {
    return s.db.WithContext(ctx).Create(user).Error
}

错误的 Java 风格(禁止):

// ❌ 大接口,方法过多
type IUserRepository interface {
    Create(user *User) error
    Update(user *User) error
    Delete(id string) error
    FindByID(id string) (*User, error)
    FindByEmail(email string) (*User, error)
    FindAll() ([]*User, error)
    FindByStatus(status string) ([]*User, error)
    Count() (int, error)
    // ... 更多方法
}

// ❌ I 前缀
type IUserService interface {}

// ❌ 不必要的抽象层
type UserRepositoryImpl struct {}  // 只有一个实现

错误处理 (Error Handling)

MUST 遵循的原则:

  • 错误 MUST 显式返回和检查不使用异常panic/recover
  • 错误处理 MUST 紧跟错误产生的代码
  • MUST 使用 errors.Is()errors.As() 检查错误类型
  • MUST 使用 fmt.Errorf() 包装错误,保留错误链
  • 自定义错误 SHOULD 实现 error 接口
  • panic MUST ONLY 用于不可恢复的程序错误

正确的 Go 风格:

func (s *Service) GetUser(ctx context.Context, id string) (*User, error) {
    user, err := s.store.GetByID(ctx, id)
    if err != nil {
        // 包装错误,添加上下文
        return nil, fmt.Errorf("get user by id %s: %w", id, err)
    }
    return user, nil
}

// 自定义错误类型
type NotFoundError struct {
    Resource string
    ID       string
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s not found: %s", e.Resource, e.ID)
}

错误的 Java 风格(禁止):

// ❌ 使用 panic 代替错误返回
func GetUser(id string) *User {
    user, err := db.Find(id)
    if err != nil {
        panic(err)  // ❌ 不要这样做
    }
    return user
}

// ❌ try-catch 风格
func DoSomething() {
    defer func() {
        if r := recover(); r != nil {  // ❌ 滥用 recover
            log.Println("recovered:", r)
        }
    }()
    // ...
}

// ❌ 异常类层次结构
type Exception interface {         // ❌ 不需要
    GetMessage() string
    GetStackTrace() []string
}
type BusinessException struct {}   // ❌ 过度设计
type ValidationException struct {} // ❌ 过度设计

结构体和方法 (Structs and Methods)

MUST 遵循的原则:

  • 结构体 MUST 简单直接不是类class的替代品
  • MUST NOT 为每个字段创建 getter/setter 方法
  • MUST 直接访问导出的字段(大写开头)
  • MUST 使用组合composition而不是继承inheritance
  • 构造函数 SHOULD 命名为 NewNewXxx,返回具体类型
  • MUST NOT 使用构造器模式Builder Pattern除非真正需要

正确的 Go 风格:

// 简单结构体,导出字段直接访问
type User struct {
    ID    string
    Name  string
    Email string
}

// 简单构造函数
func NewUser(name, email string) *User {
    return &User{
        ID:    generateID(),
        Name:  name,
        Email: email,
    }
}

// 使用组合
type Service struct {
    store  UserStorer
    logger *zap.Logger
}

func NewService(store UserStorer, logger *zap.Logger) *Service {
    return &Service{
        store:  store,
        logger: logger,
    }
}

错误的 Java 风格(禁止):

// ❌ 私有字段 + getter/setter
type User struct {
    id    string  // ❌ 不需要私有
    name  string
    email string
}

func (u *User) GetID() string { return u.id }       // ❌ 不需要
func (u *User) SetID(id string) { u.id = id }      // ❌ 不需要
func (u *User) GetName() string { return u.name }  // ❌ 不需要
func (u *User) SetName(name string) { u.name = name } // ❌ 不需要

// ❌ 构造器模式(不必要)
type UserBuilder struct {
    user *User
}

func NewUserBuilder() *UserBuilder {  // ❌ 过度设计
    return &UserBuilder{user: &User{}}
}

func (b *UserBuilder) WithName(name string) *UserBuilder {
    b.user.name = name
    return b
}

func (b *UserBuilder) Build() *User {
    return b.user
}

// ❌ 工厂模式(过度抽象)
type UserFactory interface {          // ❌ 不需要
    CreateUser(name string) *User
}

并发模式 (Concurrency Patterns)

MUST 遵循的原则:

  • MUST 使用 goroutines 和 channels不是线程和锁大多数情况
  • MUST 使用 context.Context 传递取消信号
  • MUST 遵循"通过通信共享内存,不要通过共享内存通信"
  • SHOULD 使用 sync.WaitGroup 等待 goroutines 完成
  • SHOULD 使用 sync.Once 确保只执行一次
  • MUST NOT 创建线程池类Go 运行时已处理)

正确的 Go 风格:

// 使用 goroutines 和 channels
func ProcessBatch(ctx context.Context, items []Item) error {
    results := make(chan Result, len(items))
    errors := make(chan error, len(items))

    for _, item := range items {
        go func(item Item) {
            result, err := process(ctx, item)
            if err != nil {
                errors <- err
                return
            }
            results <- result
        }(item)
    }

    // 收集结果
    for i := 0; i < len(items); i++ {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case err := <-errors:
            return err
        case <-results:
            // 处理结果
        }
    }
    return nil
}

// 使用 sync.WaitGroup
func ProcessConcurrently(items []Item) {
    var wg sync.WaitGroup
    for _, item := range items {
        wg.Add(1)
        go func(item Item) {
            defer wg.Done()
            process(item)
        }(item)
    }
    wg.Wait()
}

错误的 Java 风格(禁止):

// ❌ 线程池模式
type ThreadPool struct {              // ❌ 不需要
    workers   int
    taskQueue chan Task
    wg        sync.WaitGroup
}

func NewThreadPool(workers int) *ThreadPool { // ❌ Go 运行时已处理
    return &ThreadPool{
        workers:   workers,
        taskQueue: make(chan Task, 100),
    }
}

// ❌ Future/Promise 模式(不需要单独实现)
type Future struct {                  // ❌ 直接使用 channel
    result chan interface{}
    err    chan error
}

// ❌ 过度使用 mutex应该用 channel
type SafeMap struct {                 // ❌ 考虑使用 sync.Map 或 channel
    mu   sync.Mutex
    data map[string]interface{}
}

func (m *SafeMap) Get(key string) interface{} {
    m.mu.Lock()
    defer m.mu.Unlock()
    return m.data[key]
}

命名约定 (Naming Conventions)

MUST 遵循的原则:

  • 变量名 MUST 简短且符合上下文:
    • 短作用域用短名字:i, j, k 用于循环
    • 长作用域用描述性名字:userRepository, configManager
  • 缩写词 MUST 保持一致的大小写:URL, HTTP, ID(不是 Url, Http, Id
  • MUST NOT 使用匈牙利命名法或类型前缀:strName, arrUsers(禁止)
  • MUST NOT 使用下划线连接(除了测试和包名):user_service(禁止,应该是 userserviceUserService
  • 方法接收者名称 SHOULD 使用 1-2 个字母的缩写,全文件保持一致

正确的 Go 风格:

// 短作用域,短名字
for i, user := range users {
    fmt.Println(i, user.Name)
}

// 长作用域,描述性名字
func ProcessUserRegistration(ctx context.Context, req *RegistrationRequest) error {
    // ...
}

// 缩写词大小写一致
type UserAPI struct {
    BaseURL string
    APIKey  string
    UserID  int64
}

// 方法接收者简短一致
type UserService struct {}

func (s *UserService) Create(user *User) error { // s 用于 service
    return s.store.Create(user)
}

func (s *UserService) Update(user *User) error { // 保持一致使用 s
    return s.store.Update(user)
}

错误的 Java 风格(禁止):

// ❌ 过长的变量名
func ProcessRegistration() {
    userRegistrationRequest := &Request{}     // ❌ 太长
    userRegistrationValidator := NewValidator() // ❌ 太长
    userRegistrationService := NewService()   // ❌ 太长
}

// ❌ 匈牙利命名
strUserName := "John"    // ❌
intUserAge := 25         // ❌
arrUserList := []User{}  // ❌

// ❌ 下划线命名
type User_Service struct {}  // ❌ 应该是 UserService
func Get_User_By_Id() {}     // ❌ 应该是 GetUserByID

// ❌ 缩写词大小写不一致
type UserApi struct {    // ❌ 应该是 UserAPI
    BaseUrl string       // ❌ 应该是 BaseURL
    ApiKey  string       // ❌ 应该是 APIKey
    UserId  int64        // ❌ 应该是 UserID
}

// ❌ 方法接收者不一致或过长
func (userService *UserService) Create() {}  // ❌ 太长
func (us *UserService) Update() {}           // ❌ 与上面不一致
func (this *UserService) Delete() {}         // ❌ 不要使用 this/self

反模式清单 (Anti-Patterns to Avoid)

严格禁止的 Java 风格模式:

  1. 过度抽象:不需要的接口、工厂、构造器
  2. Getter/Setter:直接访问导出字段
  3. 继承层次:使用组合,不是嵌入
  4. 异常处理:使用错误返回,不是 panic/recover
  5. 单例模式:使用包级别变量或 sync.Once
  6. 线程池:直接使用 goroutines
  7. 深层包嵌套:保持扁平结构
  8. 类型前缀IService, AbstractBase, ServiceImpl
  9. Bean 风格:不需要 POJO/JavaBean 模式
  10. 过度 DI 框架:简单直接的依赖注入

理由 (RATIONALE):

Go 语言的设计哲学强调简单性、直接性和实用性。Java 风格的模式(深层继承、过度抽象、复杂的设计模式)违背了 Go 的核心理念。Go 提供了更简单、更高效的方式来解决相同的问题:接口的隐式实现、结构体组合、显式错误处理、轻量级并发。遵循 Go 惯用法不仅使代码更地道,还能充分发挥 Go 的性能优势和简洁性。


Development Workflow (开发工作流程)

分支管理

  • MUSTmain 分支创建功能分支
  • 功能分支命名格式:feature/###-brief-descriptionfix/###-brief-description
  • MUST 在合并前保持分支与 main 同步rebase 或 merge
  • 合并到 main MUST 通过 Pull Request 并经过代码审查

提交规范

  • 提交信息 MUST 遵循 Conventional Commits 格式:
    • feat: 新功能描述
    • fix: 修复问题描述
    • refactor: 重构描述
    • test: 测试相关
    • docs: 文档更新
    • chore: 构建/工具相关
  • 提交信息 SHOULD 简洁明了(中文或英文)

代码审查

  • 所有 PR MUST 至少有一人审查通过
  • 审查者 MUST 验证:
    • 代码符合本宪章所有原则(特别是 Go 惯用法原则)
    • 无 Java 风格的反模式getter/setter、过度抽象等
    • 测试覆盖充分且通过
    • 无安全漏洞SQL 注入、XSS、命令注入等
    • 性能影响可接受
    • 代码通过 gofmtgo vetgolangci-lint 检查

Quality Gates (质量关卡)

代码合并前检查

  • 所有测试通过(go test ./...
  • 代码格式化(gofmt -l . 无输出)
  • 代码静态检查通过(go vet ./...
  • 代码质量检查通过(golangci-lint run
  • 测试覆盖率符合标准(go test -cover ./...
  • 无 TODO/FIXME 遗留(或已记录 Issue
  • API 文档已更新(如有 API 变更)
  • 数据库迁移文件已创建(如有 schema 变更)
  • 常量和 Redis key 使用符合规范(无硬编码字符串)
  • 无 Java 风格反模式(无 getter/setter、无不必要接口、无过度抽象
  • 遵循 Go 命名约定(缩写大小写一致、包名简短、无下划线)
  • 使用 Go 惯用并发模式goroutines/channels无线程池类

上线前检查

  • 所有功能在 staging 环境测试通过
  • 性能测试通过(响应时间、内存使用、并发能力)
  • 数据库迁移在 staging 环境验证通过
  • 监控和告警配置完成
  • 回滚方案已准备

Governance

本宪章是所有开发实践的最高指导原则,优先级高于个人偏好或临时便利。

修订流程

  • 宪章修订 MUST 经过团队讨论并达成共识
  • 修订 MUST 包含明确的理由和影响分析
  • 修订 MUST 更新版本号(遵循语义化版本)
  • 修订 MUST 同步更新相关模板plan-template.md、spec-template.md、tasks-template.md

版本策略

  • MAJOR: 移除或重新定义核心原则(不兼容变更)
  • MINOR: 新增原则或显著扩展指导内容
  • PATCH: 澄清说明、措辞优化、错误修正

合规审查

  • 所有 PR 审查 MUST 验证是否符合本宪章
  • 违反宪章的代码 MUST 在合并前修正
  • 特别关注 Go 惯用法原则,拒绝 Java 风格的代码
  • 任何复杂性增加(新依赖、新架构层)MUST 在设计文档中明确说明必要性

运行时开发指导

开发时参考本宪章确保一致性。如有疑问,优先遵守原则,再讨论例外情况。记住:写 Go 代码,不是用 Go 语法写 Java


Version: 2.0.0 | Ratified: 2025-11-10 | Last Amended: 2025-11-10