在项目宪章中新增第九条原则"数据库设计原则",明确禁止使用数据库外键约束和ORM关联标签。 主要变更: - 新增原则IX:数据库设计原则(Database Design Principles) - 强制要求:数据库表不得使用外键约束 - 强制要求:GORM模型不得使用ORM关联标签(foreignKey、hasMany等) - 强制要求:表关系必须通过ID字段手动维护 - 强制要求:关联数据查询必须显式编写,避免ORM魔法 - 强制要求:时间字段由GORM处理,不使用数据库触发器 设计理念: - 提升业务逻辑灵活性(无数据库约束限制) - 优化高并发性能(无外键检查开销) - 增强代码可读性(显式查询,无隐式预加载) - 简化数据库架构和迁移流程 - 支持分布式和微服务场景 版本升级:2.3.0 → 2.4.0(MINOR)
353 lines
6.9 KiB
Markdown
353 lines
6.9 KiB
Markdown
# 数据持久化与异步任务处理集成 - 架构说明
|
||
|
||
**功能编号**: 002-gorm-postgres-asynq
|
||
**更新日期**: 2025-11-13
|
||
|
||
---
|
||
|
||
## 系统架构概览
|
||
|
||
```
|
||
┌─────────────────┐
|
||
│ Load Balancer │
|
||
│ (Nginx) │
|
||
└────────┬────────┘
|
||
│
|
||
┌────────────────┼────────────────┐
|
||
│ │ │
|
||
┌─────────▼────────┐ ┌───▼──────────┐ ┌──▼─────────┐
|
||
│ API Server 1 │ │ API Server 2 │ │ API N │
|
||
│ (Fiber:8080) │ │(Fiber:8080) │ │(Fiber:8080)│
|
||
└─────────┬────────┘ └───┬──────────┘ └──┬─────────┘
|
||
│ │ │
|
||
└──────────────┼───────────────┘
|
||
│
|
||
┌──────────────┼──────────────┐
|
||
│ │ │
|
||
┌─────▼────┐ ┌───▼───────┐ ┌───▼─────────┐
|
||
│PostgreSQL│ │ Redis │ │Worker Cluster│
|
||
│ (Primary)│ │ (Queue) │ │ (Asynq) │
|
||
└────┬─────┘ └───────────┘ └─────────────┘
|
||
│
|
||
┌──────▼──────┐
|
||
│ PostgreSQL │
|
||
│ (Replica) │
|
||
└─────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 双服务架构
|
||
|
||
### API 服务 (cmd/api/)
|
||
|
||
**职责**:
|
||
- HTTP 请求处理
|
||
- 业务逻辑执行
|
||
- 数据库 CRUD 操作
|
||
- 任务提交到队列
|
||
|
||
**特点**:
|
||
- 无状态设计,支持水平扩展
|
||
- RESTful API 设计
|
||
- 统一错误处理和响应格式
|
||
- 集成认证、限流、日志中间件
|
||
|
||
### Worker 服务 (cmd/worker/)
|
||
|
||
**职责**:
|
||
- 从队列消费任务
|
||
- 执行后台异步任务
|
||
- 任务重试管理
|
||
- 幂等性保障
|
||
|
||
**特点**:
|
||
- 多实例部署,自动负载均衡
|
||
- 支持多优先级队列
|
||
- 优雅关闭(等待任务完成)
|
||
- 可配置并发数
|
||
|
||
---
|
||
|
||
## 分层架构
|
||
|
||
### Handler 层 (internal/handler/)
|
||
|
||
**职责**: HTTP 请求处理
|
||
|
||
```
|
||
- 请求参数验证
|
||
- 调用 Service 层
|
||
- 响应封装
|
||
- 错误处理
|
||
```
|
||
|
||
**设计原则**:
|
||
- 不包含业务逻辑
|
||
- 薄层设计
|
||
- 统一使用 pkg/response/
|
||
|
||
### Service 层 (internal/service/)
|
||
|
||
**职责**: 业务逻辑
|
||
|
||
```
|
||
- 业务规则实现
|
||
- 跨模块协调
|
||
- 事务管理
|
||
- 错误转换
|
||
```
|
||
|
||
**设计原则**:
|
||
- 可复用的业务逻辑
|
||
- 支持依赖注入
|
||
- 使用 pkg/errors/ 错误码
|
||
|
||
### Store 层 (internal/store/)
|
||
|
||
**职责**: 数据访问
|
||
|
||
```
|
||
- CRUD 操作
|
||
- 查询构建
|
||
- 事务封装
|
||
- 数据库交互
|
||
```
|
||
|
||
**设计原则**:
|
||
- 只返回 GORM 原始错误
|
||
- 不包含业务逻辑
|
||
- 支持事务传递
|
||
|
||
### Model 层 (internal/model/)
|
||
|
||
**职责**: 数据模型定义
|
||
|
||
```
|
||
- 实体定义
|
||
- DTO 定义
|
||
- 验证规则
|
||
```
|
||
|
||
---
|
||
|
||
## 数据流
|
||
|
||
### CRUD 操作流程
|
||
|
||
```
|
||
HTTP Request
|
||
↓
|
||
Handler (参数验证)
|
||
↓
|
||
Service (业务逻辑)
|
||
↓
|
||
Store (数据访问)
|
||
↓
|
||
PostgreSQL
|
||
↓
|
||
Store (返回数据)
|
||
↓
|
||
Service (转换)
|
||
↓
|
||
Handler (响应)
|
||
↓
|
||
HTTP Response
|
||
```
|
||
|
||
### 异步任务流程
|
||
|
||
```
|
||
HTTP Request (任务提交)
|
||
↓
|
||
Handler
|
||
↓
|
||
Service (构造 Payload)
|
||
↓
|
||
Queue Client (Asynq)
|
||
↓
|
||
Redis (持久化)
|
||
↓
|
||
Worker (消费任务)
|
||
↓
|
||
Task Handler (执行任务)
|
||
↓
|
||
PostgreSQL/外部服务
|
||
```
|
||
|
||
---
|
||
|
||
## 核心设计决策
|
||
|
||
### 1. 为什么使用 GORM?
|
||
|
||
**优势**:
|
||
- Go 生态最成熟的 ORM
|
||
- 自动参数化查询(防 SQL 注入)
|
||
- 预编译语句缓存
|
||
- 软删除支持
|
||
- 钩子函数支持
|
||
|
||
### 2. 为什么使用 golang-migrate?
|
||
|
||
**理由**:
|
||
- 版本控制: 每个迁移有版本号
|
||
- 可回滚: up/down 脚本
|
||
- 团队协作: 迁移文件可 review
|
||
- 生产安全: 明确的 SQL 语句
|
||
|
||
**不用 GORM AutoMigrate**:
|
||
- 无法回滚
|
||
- 无法删除列
|
||
- 生产环境风险高
|
||
|
||
### 3. 为什么使用 Asynq?
|
||
|
||
**优势**:
|
||
- 基于 Redis,无需额外中间件
|
||
- 任务持久化(系统重启自动恢复)
|
||
- 自动重试(指数退避)
|
||
- Web UI 监控(asynqmon)
|
||
- 分布式锁支持
|
||
|
||
---
|
||
|
||
## 关键技术实现
|
||
|
||
### 幂等性设计
|
||
|
||
**方案 1: Redis 锁**
|
||
```go
|
||
key := constants.RedisTaskLockKey(requestID)
|
||
if exists, _ := rdb.SetNX(ctx, key, "1", 24*time.Hour).Result(); !exists {
|
||
return nil // 跳过重复任务
|
||
}
|
||
```
|
||
|
||
**方案 2: 数据库唯一约束**
|
||
```sql
|
||
CREATE UNIQUE INDEX idx_order_id ON tb_order(order_id);
|
||
```
|
||
|
||
**方案 3: 状态机**
|
||
```go
|
||
if order.Status != "pending" {
|
||
return nil // 状态不匹配,跳过
|
||
}
|
||
```
|
||
|
||
### 事务管理
|
||
|
||
```go
|
||
func (s *Store) Transaction(ctx context.Context, fn func(*Store) error) error {
|
||
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||
txStore := &Store{db: tx, logger: s.logger}
|
||
return fn(txStore)
|
||
})
|
||
}
|
||
```
|
||
|
||
### 连接池配置
|
||
|
||
**PostgreSQL**:
|
||
- MaxOpenConns: 25(最大连接)
|
||
- MaxIdleConns: 10(空闲连接)
|
||
- ConnMaxLifetime: 5m(连接生命周期)
|
||
|
||
**Redis**:
|
||
- PoolSize: 10
|
||
- MinIdleConns: 5
|
||
|
||
---
|
||
|
||
## 扩展性设计
|
||
|
||
### 水平扩展
|
||
|
||
**API 服务**:
|
||
- 无状态设计
|
||
- 通过负载均衡器分发请求
|
||
- 自动扩缩容(K8s HPA)
|
||
|
||
**Worker 服务**:
|
||
- 多实例连接同一 Redis
|
||
- Asynq 自动负载均衡
|
||
- 按队列权重分配任务
|
||
|
||
### 数据库扩展
|
||
|
||
**读写分离**:
|
||
```
|
||
Primary (写) → Replica (读)
|
||
```
|
||
|
||
**分库分表**:
|
||
- 按业务模块垂直分库
|
||
- 按数据量水平分表
|
||
|
||
---
|
||
|
||
## 监控与可观测性
|
||
|
||
### 健康检查
|
||
|
||
- PostgreSQL Ping
|
||
- Redis Ping
|
||
- 连接池状态
|
||
|
||
### 日志
|
||
|
||
- 访问日志: 所有 HTTP 请求
|
||
- 错误日志: 错误详情
|
||
- 慢查询日志: > 100ms
|
||
- 任务日志: 提交/执行/失败
|
||
|
||
### 指标(建议)
|
||
|
||
- API 响应时间
|
||
- 数据库连接数
|
||
- 任务队列长度
|
||
- 任务失败率
|
||
|
||
---
|
||
|
||
## 安全设计
|
||
|
||
### 数据安全
|
||
|
||
- SQL 注入防护(GORM 参数化)
|
||
- 密码哈希(bcrypt)
|
||
- 敏感字段不返回(`json:"-"`)
|
||
|
||
### 配置安全
|
||
|
||
- 生产环境使用环境变量
|
||
- 数据库 SSL 连接
|
||
- Redis 密码认证
|
||
|
||
---
|
||
|
||
## 性能优化
|
||
|
||
### 数据库
|
||
|
||
- 适当索引
|
||
- 批量操作
|
||
- 分页查询
|
||
- 慢查询监控
|
||
|
||
### 任务队列
|
||
|
||
- 优先级队列
|
||
- 并发控制
|
||
- 超时设置
|
||
- 幂等性保障
|
||
|
||
---
|
||
|
||
## 参考文档
|
||
|
||
- [功能总结](./功能总结.md)
|
||
- [使用指南](./使用指南.md)
|
||
- [项目 Constitution](../../.specify/memory/constitution.md)
|