Files
huang 984ccccc63 docs(constitution): 新增数据库设计原则(v2.4.0)
在项目宪章中新增第九条原则"数据库设计原则",明确禁止使用数据库外键约束和ORM关联标签。

主要变更:
- 新增原则IX:数据库设计原则(Database Design Principles)
- 强制要求:数据库表不得使用外键约束
- 强制要求:GORM模型不得使用ORM关联标签(foreignKey、hasMany等)
- 强制要求:表关系必须通过ID字段手动维护
- 强制要求:关联数据查询必须显式编写,避免ORM魔法
- 强制要求:时间字段由GORM处理,不使用数据库触发器

设计理念:
- 提升业务逻辑灵活性(无数据库约束限制)
- 优化高并发性能(无外键检查开销)
- 增强代码可读性(显式查询,无隐式预加载)
- 简化数据库架构和迁移流程
- 支持分布式和微服务场景

版本升级:2.3.0 → 2.4.0(MINOR)
2025-11-13 13:40:19 +08:00

16 KiB

Feature Specification: 数据持久化与异步任务处理集成

Feature Branch: 002-gorm-postgres-asynq
Created: 2025-11-12
Status: Draft
Input: User description: "集成gorm、Postgresql数据库和asynq任务队列系统"

Clarifications

Session 2025-11-12

  • Q: PostgreSQL连接应如何处理凭证管理和传输安全? → A: 凭证直接写在配置文件(config.yaml)中,明文存储
  • Q: 任务失败后应该如何重试? → A: 最大重试5次,指数退避策略(1s、2s、4s、8s、16s)
  • Q: 数据库表结构的创建和变更应该如何执行? → A: 完全不使用GORM迁移,使用外部迁移工具(如golang-migrate)管理SQL迁移文件
  • Q: Worker进程应该如何配置并发任务处理? → A: 支持多个worker进程,每个进程可配置并发数(默认10);不同任务类型可配置独立的队列优先级
  • Q: 系统重启时,正在执行中或排队中的任务应该如何处理? → A: 所有未完成任务(包括执行中)自动重新排队,重启后继续执行;任务处理逻辑需保证幂等性

Session 2025-11-13

  • Q: 数据库慢查询(>100ms)和任务执行状态应该如何进行监控和指标收集? → A: 仅记录日志文件,不收集指标
  • Q: 当数据库连接池耗尽时,新的数据库请求应该如何处理? → A: 请求排队等待直到获得连接(带超时,如5秒)
  • Q: 当数据库执行慢查询时,系统应该如何避免请求超时? → A: 使用context超时控制(如3秒),超时后取消查询
  • Q: 当PostgreSQL主从切换时,系统应该如何感知并重新连接? → A: 依赖GORM的自动重连机制,连接失败时重试
  • Q: 当并发事务产生死锁时,系统应该如何检测和恢复? → A: 依赖PostgreSQL自动检测,捕获死锁错误并重试(最多3次)

User Scenarios & Testing (mandatory)

User Story 1 - 可靠的数据存储与检索 (Priority: P1)

作为系统,需要能够可靠地持久化存储业务数据(如用户信息、业务记录等),并支持高效的数据查询和修改操作,确保数据的一致性和完整性。

Why this priority: 这是系统的核心基础能力,没有数据持久化就无法提供任何有意义的业务功能。所有后续功能都依赖于数据存储能力。

Independent Test: 可以通过创建、读取、更新、删除(CRUD)测试数据来独立验证。测试应包括基本的数据操作、事务提交、数据一致性验证等场景。

Acceptance Scenarios:

  1. Given 系统接收到新的业务数据, When 执行数据保存操作, Then 数据应成功持久化到数据库,并可以被后续查询检索到
  2. Given 需要修改已存在的数据, When 执行更新操作, Then 数据应被正确更新,且旧数据被新数据替换
  3. Given 需要删除数据, When 执行删除操作, Then 数据应从数据库中移除,后续查询不应返回该数据
  4. Given 多个数据操作需要原子性执行, When 在事务中执行这些操作, Then 要么全部成功提交,要么全部回滚,保证数据一致性
  5. Given 执行数据查询, When 查询条件匹配多条记录, Then 系统应返回所有匹配的记录,支持分页和排序

User Story 2 - 异步任务处理能力 (Priority: P2)

作为系统,需要能够将耗时的操作(如发送邮件、生成报表、数据同步等)放到后台异步执行,避免阻塞用户请求,提升用户体验和系统响应速度。

Why this priority: 许多业务操作需要较长时间完成,如果在用户请求中同步执行会导致超时和糟糕的用户体验。异步任务处理是提升系统性能和用户体验的关键。

Independent Test: 可以通过提交一个耗时任务(如模拟发送邮件),验证任务被成功加入队列,然后在后台完成执行,用户请求立即返回而不等待任务完成。

Acceptance Scenarios:

  1. Given 系统需要执行一个耗时操作, When 将任务提交到任务队列, Then 任务应被成功加入队列,用户请求立即返回,不阻塞等待
  2. Given 任务队列中有待处理的任务, When 后台工作进程运行, Then 任务应按顺序被取出并执行
  3. Given 任务执行过程中发生错误, When 任务失败, Then 系统应记录错误信息,并根据配置进行重试
  4. Given 任务需要定时执行, When 到达指定时间, Then 任务应自动触发执行
  5. Given 需要查看任务执行状态, When 查询任务信息, Then 应能获取任务的当前状态(等待、执行中、成功、失败)和执行历史

User Story 3 - 数据库连接管理与监控 (Priority: P3)

作为系统管理员,需要能够监控数据库连接状态、查询性能和任务队列健康度,及时发现和解决潜在问题,确保系统稳定运行。

Why this priority: 虽然不是核心业务功能,但对系统的稳定性和可维护性至关重要。良好的监控能力可以预防故障和提升运维效率。

Independent Test: 可以通过健康检查接口验证数据库连接状态和任务队列状态,模拟连接失败场景验证系统的容错能力。

Acceptance Scenarios:

  1. Given 系统启动时, When 初始化数据库连接池, Then 应成功建立连接,并验证数据库可访问性
  2. Given 数据库连接出现问题, When 检测到连接失败, Then 系统应记录错误日志,并尝试重新建立连接
  3. Given 需要监控系统健康状态, When 调用健康检查接口, Then 应返回数据库和任务队列的当前状态(正常/异常)
  4. Given 系统关闭时, When 执行清理操作, Then 应优雅地关闭数据库连接和任务队列,等待正在执行的任务完成

Edge Cases

  • 当数据库连接池耗尽时,新的数据库请求会排队等待可用连接,等待超时时间为5秒。超时后返回503 Service Unavailable错误,错误消息提示"数据库连接池繁忙,请稍后重试"
  • 当任务队列积压过多任务(超过 10,000 个待处理任务或 Redis 内存使用超过 80%)时,系统应触发告警,并考虑暂停低优先级任务提交或扩展 Worker 进程数量
  • 当数据库执行慢查询时,系统使用context.WithTimeout为每个数据库操作设置超时时间(默认3秒)。超时后自动取消查询并返回504 Gateway Timeout错误,错误消息提示"数据库查询超时,请优化查询条件或联系管理员"
  • 当任务重复执行5次后仍然失败时,任务应被标记为"最终失败"状态,记录完整错误历史,并可选择发送告警通知或进入死信队列等待人工处理
  • 当PostgreSQL主从切换时,系统依赖GORM的自动重连机制。当检测到连接失败或不可用时,GORM会自动尝试重新建立连接。失败的查询会返回数据库连接错误,应用层应在合理范围内进行重试(建议重试1-3次,每次间隔100ms)
  • 当并发事务产生死锁时,PostgreSQL会自动检测并中止其中一个事务(返回SQLSTATE 40P01错误)。应用层捕获死锁错误后,应自动重试该事务(建议最多重试3次,每次间隔50-100ms随机延迟)。超过重试次数后,返回409 Conflict错误,提示"数据库操作冲突,请稍后重试"
  • 当系统重启时,所有未完成的任务(包括排队中和执行中的任务)会利用Asynq的Redis持久化机制自动重新排队,重启后Worker进程会继续处理这些任务。所有任务处理逻辑必须设计为幂等操作,确保任务重复执行不会产生副作用或数据不一致

Requirements (mandatory)

Functional Requirements

  • FR-001: 系统必须能够建立和管理与PostgreSQL数据库的连接池,支持配置最大连接数、空闲连接数等参数。数据库连接配置(包括主机地址、端口、用户名、密码、数据库名)存储在配置文件(config.yaml)中,明文形式保存。当连接池耗尽时,新请求排队等待可用连接(默认超时5秒),超时后返回503错误。系统依赖GORM的自动重连机制处理数据库连接失败或主从切换场景
  • FR-002: 系统必须支持标准的CRUD操作(创建、读取、更新、删除),并提供统一的数据访问接口。接口应包括但不限于: Create(创建记录)、GetByID(按ID查询)、Update(更新记录)、Delete(软删除)、List(分页列表查询)等基础方法,所有 Store 层接口遵循一致的命名和参数约定(详见 data-model.md)
  • FR-003: 系统必须支持数据库事务,包括事务的开始、提交、回滚操作,确保数据一致性。当发生死锁时(SQLSTATE 40P01),系统应捕获错误并自动重试事务(最多3次,每次间隔50-100ms随机延迟),超过重试次数后返回409错误
  • FR-004: 系统必须支持数据库迁移,使用外部迁移工具(如golang-migrate)通过版本化的SQL迁移文件管理表结构的创建和变更,不使用GORM AutoMigrate功能。迁移文件应包含up/down脚本以支持正向迁移和回滚
  • FR-005: 系统必须提供查询构建能力,支持条件查询、分页、排序、关联查询等常见操作。所有数据库查询必须使用context.WithTimeout设置超时时间(默认3秒),超时后自动取消查询并返回504错误
  • FR-006: 系统必须能够将任务提交到异步任务队列,任务应包含任务类型、参数、优先级等信息
  • FR-007: 系统必须提供后台工作进程,从任务队列中获取任务并执行。支持启动多个worker进程实例,每个进程可独立配置并发处理数(默认10个并发goroutine)。不同任务类型可配置到不同的队列,并设置队列优先级,实现资源隔离和灵活扩展。Worker 进程异常退出时,Asynq 会自动将执行中的任务标记为失败并重新排队;建议使用进程管理工具(如 systemd, supervisord)实现 Worker 自动重启
  • FR-008: 系统必须支持任务重试机制,当任务执行失败时能够按配置的策略自动重试。默认最大重试5次,采用指数退避策略(重试间隔为1s、2s、4s、8s、16s),每个任务类型可独立配置重试参数
  • FR-009: 系统必须支持任务优先级,高优先级任务应优先被处理
  • FR-010: 系统必须能够记录任务执行历史和状态,包括开始时间、结束时间、执行结果、错误信息等。任务执行状态通过日志文件记录,不使用外部指标收集系统
  • FR-011: 系统必须提供健康检查接口,能够验证数据库连接和任务队列的可用性
  • FR-012: 系统必须支持定时任务,能够按照cron表达式或固定间隔调度任务执行
  • FR-013: 系统必须记录慢查询日志,当数据库查询超过阈值(100ms)时记录详细信息用于优化。日志应包含 SQL 语句、执行时间、参数和上下文信息。监控采用日志文件方式,不使用 Prometheus 或其他指标收集系统
  • FR-014: 系统必须支持配置化的数据库和任务队列参数,如连接字符串、最大重试次数、任务超时时间等
  • FR-015: 系统必须在关闭时优雅地清理资源,关闭数据库连接并等待正在执行的任务完成
  • FR-016: 系统必须支持任务持久化和故障恢复。利用Asynq基于Redis的持久化机制,确保系统重启或崩溃时未完成的任务不会丢失。所有任务处理函数必须设计为幂等操作,支持任务重新执行而不产生副作用

Technical Requirements (Constitution-Driven)

Tech Stack Compliance:

  • 所有数据库操作使用GORM (不直接使用 database/sql)
  • 数据库迁移使用golang-migrate (不使用GORM AutoMigrate)
  • 所有异步任务使用Asynq
  • 所有HTTP操作使用Fiber框架 (不使用 net/http)
  • 所有JSON操作使用sonic (不使用 encoding/json)
  • 所有日志使用Zap + Lumberjack.v2
  • 所有配置使用Viper
  • 使用Go官方工具链: go fmt, go vet, golangci-lint

Architecture Requirements:

  • 实现遵循 Handler → Service → Store → Model 分层架构
  • 依赖通过结构体字段注入(不使用构造函数模式)
  • 统一错误码定义在 pkg/errors/
  • 统一API响应通过 pkg/response/
  • 所有常量定义在 pkg/constants/ (不使用魔法数字/字符串)
  • 不允许硬编码值: 3次及以上相同字面量必须定义为常量
  • 已定义的常量必须使用(不允许重复硬编码)
  • 代码注释优先使用中文(实现注释用中文)
  • 日志消息使用中文(logger.Info/Warn/Error/Debug用中文)
  • 错误消息支持中文(面向用户的错误有中文文本)
  • 所有Redis键通过 pkg/constants/ 键生成函数管理
  • 包结构扁平化,按功能组织(不按层级)

Go Idiomatic Design Requirements:

  • 不使用Java风格模式: 无getter/setter方法、无I-前缀接口、无Impl-后缀
  • 接口应小型化(1-3个方法),在使用处定义
  • 错误处理显式化(返回错误,不使用panic)
  • 使用组合(结构体嵌入)而非继承
  • 使用goroutines和channels处理并发
  • 命名遵循Go约定: UserID 不是 userId, HTTPServer 不是 HttpServer
  • 不使用匈牙利命名法或类型前缀
  • 代码结构简单直接

API Design Requirements:

  • 所有API遵循RESTful原则
  • 所有响应使用统一JSON格式,包含code/message/data/timestamp
  • 所有错误消息包含错误码和双语描述
  • 所有分页使用标准参数(page, page_size, total)
  • 所有时间字段使用ISO 8601格式(RFC3339)
  • 所有货币金额使用整数(分)

Performance Requirements:

  • API响应时间(P95) < 200ms
  • 数据库查询 < 50ms
  • 批量操作使用bulk查询
  • 列表查询实现分页(默认20条,最大100条)
  • 非实时操作委托给异步任务
  • 使用 context.Context 进行超时和取消控制

Testing Requirements:

  • Service层业务逻辑必须有单元测试
  • 所有API端点必须有集成测试
  • 所有异步任务处理函数必须有幂等性测试,验证重复执行的正确性
  • 测试使用Go标准testing框架,文件名为 *_test.go
  • 多测试用例使用表驱动测试
  • 测试相互独立,使用mocks/testcontainers
  • 目标覆盖率: 总体70%+, 核心业务逻辑90%+

Key Entities

  • DatabaseConnection: 代表与PostgreSQL数据库的连接,包含连接池配置、连接状态、健康检查等属性
  • DataModel: 代表业务数据模型,通过ORM映射到数据库表,包含数据验证规则和关联关系
  • Task: 代表异步任务,包含任务类型、任务参数、优先级、重试次数、执行状态等属性
  • TaskQueue: 代表任务队列,管理任务的提交、调度、执行和状态跟踪
  • Worker: 代表后台工作进程,从任务队列中获取任务并执行。每个Worker进程支持可配置的并发数(通过goroutine池实现),可以部署多个Worker进程实例实现水平扩展。不同Worker可订阅不同的任务队列,实现任务类型的资源隔离

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: 数据库基本CRUD操作响应时间(P95)应小于50毫秒
  • SC-002: 系统应支持至少1000个并发数据库连接而不出现连接池耗尽
  • SC-003: 任务队列应能够处理每秒至少100个任务的提交速率
  • SC-004: 异步任务从提交到开始执行的延迟(空闲情况下)应小于100毫秒
  • SC-005: 数据持久化的可靠性应达到99.99%,即每10000次操作中失败不超过1次
  • SC-006: 失败任务的自动重试成功率应达到90%以上
  • SC-007: 系统启动时应在10秒内完成数据库连接和任务队列初始化
  • SC-008: 数据库查询慢查询(超过100ms)的占比应小于1%
  • SC-009: 系统关闭时应在30秒内优雅完成所有资源清理,不丢失正在执行的任务
  • SC-010: 健康检查接口应在1秒内返回系统健康状态