在项目宪章中新增第九条原则"数据库设计原则",明确禁止使用数据库外键约束和ORM关联标签。 主要变更: - 新增原则IX:数据库设计原则(Database Design Principles) - 强制要求:数据库表不得使用外键约束 - 强制要求:GORM模型不得使用ORM关联标签(foreignKey、hasMany等) - 强制要求:表关系必须通过ID字段手动维护 - 强制要求:关联数据查询必须显式编写,避免ORM魔法 - 强制要求:时间字段由GORM处理,不使用数据库触发器 设计理念: - 提升业务逻辑灵活性(无数据库约束限制) - 优化高并发性能(无外键检查开销) - 增强代码可读性(显式查询,无隐式预加载) - 简化数据库架构和迁移流程 - 支持分布式和微服务场景 版本升级:2.3.0 → 2.4.0(MINOR)
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:
- Given 系统接收到新的业务数据, When 执行数据保存操作, Then 数据应成功持久化到数据库,并可以被后续查询检索到
- Given 需要修改已存在的数据, When 执行更新操作, Then 数据应被正确更新,且旧数据被新数据替换
- Given 需要删除数据, When 执行删除操作, Then 数据应从数据库中移除,后续查询不应返回该数据
- Given 多个数据操作需要原子性执行, When 在事务中执行这些操作, Then 要么全部成功提交,要么全部回滚,保证数据一致性
- Given 执行数据查询, When 查询条件匹配多条记录, Then 系统应返回所有匹配的记录,支持分页和排序
User Story 2 - 异步任务处理能力 (Priority: P2)
作为系统,需要能够将耗时的操作(如发送邮件、生成报表、数据同步等)放到后台异步执行,避免阻塞用户请求,提升用户体验和系统响应速度。
Why this priority: 许多业务操作需要较长时间完成,如果在用户请求中同步执行会导致超时和糟糕的用户体验。异步任务处理是提升系统性能和用户体验的关键。
Independent Test: 可以通过提交一个耗时任务(如模拟发送邮件),验证任务被成功加入队列,然后在后台完成执行,用户请求立即返回而不等待任务完成。
Acceptance Scenarios:
- Given 系统需要执行一个耗时操作, When 将任务提交到任务队列, Then 任务应被成功加入队列,用户请求立即返回,不阻塞等待
- Given 任务队列中有待处理的任务, When 后台工作进程运行, Then 任务应按顺序被取出并执行
- Given 任务执行过程中发生错误, When 任务失败, Then 系统应记录错误信息,并根据配置进行重试
- Given 任务需要定时执行, When 到达指定时间, Then 任务应自动触发执行
- Given 需要查看任务执行状态, When 查询任务信息, Then 应能获取任务的当前状态(等待、执行中、成功、失败)和执行历史
User Story 3 - 数据库连接管理与监控 (Priority: P3)
作为系统管理员,需要能够监控数据库连接状态、查询性能和任务队列健康度,及时发现和解决潜在问题,确保系统稳定运行。
Why this priority: 虽然不是核心业务功能,但对系统的稳定性和可维护性至关重要。良好的监控能力可以预防故障和提升运维效率。
Independent Test: 可以通过健康检查接口验证数据库连接状态和任务队列状态,模拟连接失败场景验证系统的容错能力。
Acceptance Scenarios:
- Given 系统启动时, When 初始化数据库连接池, Then 应成功建立连接,并验证数据库可访问性
- Given 数据库连接出现问题, When 检测到连接失败, Then 系统应记录错误日志,并尝试重新建立连接
- Given 需要监控系统健康状态, When 调用健康检查接口, Then 应返回数据库和任务队列的当前状态(正常/异常)
- 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秒内返回系统健康状态