28c - Spec-Driven 业务逻辑
本文是《AI Agent 实战手册》第 28 章第 3 节。 上一节:28b-API设计与生成 | 下一节:28d-AI辅助认证实现
概述
业务逻辑是后端系统的核心——它决定了数据如何流转、规则如何执行、状态如何变迁。在 AI 辅助开发时代,“随意对话生成代码”的 Vibe Coding 方式在业务逻辑层暴露出严重问题:逻辑遗漏、边界条件缺失、事务不一致。Spec-Driven 开发方法论通过”先规约、后生成”的结构化流程,将业务逻辑实现从”AI 猜测意图”升级为”AI 按规约执行”。本节系统覆盖 Spec-Driven 开发方法论、从需求到代码的结构化流程、领域模型 AI 生成(DDD 概念)、服务层模式(Repository/Service/Controller)、AI 辅助业务规则实现、事务管理与数据一致性,以及 Kiro Spec 工作流实战。
1. Spec-Driven 开发方法论
1.1 什么是 Spec-Driven Development
Spec-Driven Development(SDD,规约驱动开发)是一种以规约(Specification)为核心制品的开发方法论。与传统的”先写代码再补文档”或 Vibe Coding 的”对话式生成”不同,SDD 要求在编写任何代码之前,先产出结构化的规约文档,然后由 AI Agent 按照规约生成、验证和维护实现代码。
核心公式:
Spec(需求 + 设计 + 任务) + AI Agent + Human Review = 生产级代码SDD 与其他方法论的对比:
| 维度 | Vibe Coding | 传统敏捷 | Spec-Driven Development |
|---|---|---|---|
| 规划投入 | 几乎为零 | 中等(Sprint Planning) | 前置 15-30 分钟结构化规划 |
| 需求表达 | 自然语言对话 | User Story + AC | EARS 格式验收标准 |
| 设计产出 | 无 | 可选的设计文档 | 强制的技术设计文档 |
| 任务分解 | AI 自行决定 | 人工分解 | AI 生成 + 人工审核 |
| 代码质量 | 不可预测 | 依赖开发者水平 | 可追溯、可验证 |
| 适用场景 | 原型、探索 | 团队协作项目 | AI 辅助的生产级开发 |
| 上下文持久性 | 会话结束即丢失 | 在 Jira/Confluence 中 | 在 Spec 文件中持久化 |
1.2 为什么业务逻辑需要 Spec-Driven
业务逻辑是 AI 生成代码中最容易出错的部分,原因在于:
- 隐含规则多:业务规则往往存在大量隐含条件(“VIP 用户免运费”、“库存不足时自动预订”),AI 无法从简单描述中推断所有规则
- 边界条件复杂:金额计算的精度、时区处理、并发冲突——这些边界条件需要明确规约
- 状态转换严格:订单状态机、审批流程、支付状态——状态转换必须遵循严格的业务规则
- 一致性要求高:跨表、跨服务的数据一致性不能靠 AI “猜测”
- 可审计性:金融、医疗等领域的业务逻辑需要可追溯的实现依据
Spec-Driven 如何解决这些问题:
传统 Vibe Coding 流程:
"帮我写一个订单系统" → AI 生成代码 → 发现缺少退款逻辑 → 补丁式修复 → 发现并发问题 → 再修复...
Spec-Driven 流程:
需求规约(所有业务规则) → 设计文档(领域模型 + 服务层) → 任务分解(原子任务) → AI 按任务生成 → 人工审核1.3 SDD 的三阶段流程
Spec-Driven Development 将开发过程分为三个明确的阶段:
┌─────────────────────────────────────────────────────────────┐
│ Spec-Driven Development 流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 阶段 1:需求规约(Requirements) │
│ ┌─────────────────────────────────────────────┐ │
│ │ • 用户故事(User Story) │ │
│ │ • EARS 格式验收标准 │ │
│ │ • 业务规则清单 │ │
│ │ • 边界条件和异常场景 │ │
│ └─────────────────────────────────────────────┘ │
│ ↓ │
│ 阶段 2:技术设计(Design) │
│ ┌─────────────────────────────────────────────┐ │
│ │ • 领域模型(Entity/Value Object/Aggregate) │ │
│ │ • 服务层架构(Repository/Service/Controller) │ │
│ │ • 数据流图和序列图 │ │
│ │ • 接口定义(TypeScript Interface/Type) │ │
│ │ • 数据库 Schema │ │
│ └─────────────────────────────────────────────┘ │
│ ↓ │
│ 阶段 3:任务执行(Tasks) │
│ ┌─────────────────────────────────────────────┐ │
│ │ • 原子化任务列表(可独立执行和验证) │ │
│ │ • 每个任务关联需求和设计 │ │
│ │ • AI Agent 逐任务执行 │ │
│ │ • 人工审核每个任务的输出 │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘1.4 工具推荐
| 工具 | 用途 | 核心能力 | 价格 | 适用场景 |
|---|---|---|---|---|
| Kiro | Agentic IDE | 内置 Spec 工作流(需求→设计→任务),EARS 格式验收标准,Steering 规则,Hooks 自动化 | 免费(预览期) | Spec-Driven 业务逻辑开发的首选工具 |
| Claude Code | CLI AI 工具 | 全项目理解,Plan Mode 规划,Subagent 并行执行,CLAUDE.md 规则文件 | $20/月(Max 5x)/ API 按量 | CLI 偏好的 Spec-Driven 开发,复杂重构 |
| Cursor | AI IDE | Composer 多文件编辑,Agent 模式,.cursorrules 规则文件 | 免费 / $20/月(Pro) | 日常业务逻辑编码,快速迭代 |
| OpenAI Codex | 云端 Agent | 沙箱自主执行,自动测试,GitHub 集成 | ChatGPT Pro 订阅内含 | 独立业务模块的自主实现 |
| Prisma | ORM | 声明式 Schema,类型安全查询,自动迁移,AI 友好的 schema 语法 | 免费(开源)/ $29/月(Accelerate) | TypeScript 后端的领域模型层 |
| Drizzle ORM | 轻量 ORM | SQL-like API,零依赖,类型推断,声明式 schema | 免费(开源) | 追求性能的 TypeScript 后端 |
| TypeORM | ORM | 装饰器模式,Active Record/Data Mapper,迁移工具 | 免费(开源) | 企业级 TypeScript/Node.js 项目 |
| SQLAlchemy | ORM(Python) | 声明式映射,Unit of Work,复杂查询构建 | 免费(开源) | Python 后端的领域模型层 |
| Zod | 验证库 | TypeScript-first schema 验证,类型推断,可组合 | 免费(开源) | 业务规则输入验证 |
| Pydantic | 验证库(Python) | 数据验证,序列化,Settings 管理 | 免费(开源) | Python 业务逻辑的数据验证 |
2. 从需求到代码的结构化流程
2.1 阶段一:需求规约编写
需求规约是 Spec-Driven 业务逻辑的起点。好的需求规约应该让 AI Agent 能够准确理解每一条业务规则,不留歧义。
EARS 格式验收标准
EARS(Easy Approach to Requirements Syntax)是 Kiro 采用的需求编写格式,它通过结构化的语法消除自然语言的歧义:
EARS 格式模板:
WHEN [触发条件], THE [系统/组件] SHALL [行为描述]
示例:
WHEN a customer places an order with total amount > $100,
THE OrderService SHALL apply a 10% discount automatically
AND record the discount reason as "bulk_order_discount"
WHEN a customer attempts to cancel an order that is already shipped,
THE OrderService SHALL reject the cancellation
AND return error code ORDER_ALREADY_SHIPPED with HTTP 409操作步骤:编写业务逻辑需求规约
步骤 1:识别业务领域和核心实体
列出你的业务领域中的核心实体:
- 用户(User)
- 订单(Order)
- 商品(Product)
- 支付(Payment)
- 库存(Inventory)步骤 2:梳理业务规则清单
为每个实体列出所有业务规则:
【订单规则】
1. 订单总金额 = Σ(商品单价 × 数量) - 折扣 + 运费
2. VIP 用户(等级 ≥ 3)免运费
3. 订单金额 > 100 元自动享受 9 折
4. 库存不足时不允许下单
5. 同一用户 24 小时内最多下 10 单(防刷单)
6. 订单创建后 30 分钟未支付自动取消
【状态转换规则】
创建 → 待支付 → 已支付 → 备货中 → 已发货 → 已签收 → 已完成
↘ 退货中 → 已退货
待支付 → 已取消(用户主动/超时自动)
已支付 → 退款中 → 已退款(发货前可退款)步骤 3:用 EARS 格式编写验收标准
提示词模板:生成业务逻辑需求规约
你是一位资深业务分析师。请根据以下业务描述,生成结构化的需求规约文档。
## 业务描述
[在此粘贴你的业务需求描述]
## 输出要求
1. 使用 EARS 格式编写验收标准(WHEN...THE...SHALL...)
2. 每条规则必须包含:
- 触发条件(WHEN)
- 执行主体(THE)
- 预期行为(SHALL)
- 异常情况处理
3. 列出所有状态转换规则(用状态机图表示)
4. 列出所有边界条件:
- 数值边界(最大值、最小值、精度)
- 时间边界(超时、有效期)
- 并发边界(竞态条件、幂等性)
5. 列出所有业务约束:
- 唯一性约束
- 引用完整性约束
- 业务规则约束
## 格式
使用 Markdown,按实体分组组织规则。2.2 阶段二:技术设计文档
技术设计文档将业务需求转化为可实现的技术方案,是 AI Agent 生成代码的蓝图。
设计文档核心内容
技术设计文档结构:
1. 领域模型(Domain Model)
- Entity 定义(含属性和类型)
- Value Object 定义
- Aggregate Root 识别
- 领域事件(Domain Event)
2. 服务层架构
- Repository 接口定义
- Service 层职责划分
- Controller 路由映射
3. 数据模型
- 数据库 Schema(表结构)
- 索引策略
- 关联关系
4. 接口定义
- TypeScript Interface / Type
- API 请求/响应类型
- 错误码定义
5. 序列图
- 核心业务流程的时序图
- 异常处理流程提示词模板:生成技术设计文档
你是一位资深后端架构师。请根据以下需求规约,生成技术设计文档。
## 需求规约
[粘贴阶段一产出的需求规约]
## 技术栈
- 语言:[TypeScript / Python / Go / Java]
- 框架:[Express / NestJS / FastAPI / Gin / Spring Boot]
- ORM:[Prisma / Drizzle / SQLAlchemy / GORM]
- 数据库:[PostgreSQL / MySQL / MongoDB]
## 输出要求
1. **领域模型**:使用 DDD 概念,识别 Entity、Value Object、Aggregate Root
2. **TypeScript 接口定义**:为每个实体和值对象定义完整的类型
3. **服务层架构**:定义 Repository 接口、Service 类、Controller 路由
4. **数据库 Schema**:使用 [ORM] 语法定义表结构
5. **序列图**:使用 Mermaid 语法绘制核心业务流程
6. **错误处理**:定义业务错误码和异常类型
## 架构原则
- 遵循 Clean Architecture / 六边形架构
- Repository 模式隔离数据访问
- Service 层包含所有业务逻辑
- Controller 层只负责 HTTP 协议转换
- 使用依赖注入2.3 阶段三:任务分解与执行
将设计文档分解为原子化的实现任务,每个任务可独立执行和验证。
任务分解原则
好的任务分解:
✅ 1.1 创建 Order Entity 和 OrderItem Value Object 的 TypeScript 类型定义
✅ 1.2 创建 orders 和 order_items 数据库表的 Prisma Schema
✅ 1.3 实现 OrderRepository 接口和 Prisma 实现
✅ 1.4 实现 OrderService.createOrder() 方法(含库存检查和金额计算)
✅ 1.5 实现 OrderService.cancelOrder() 方法(含状态验证和退款触发)
✅ 1.6 为 OrderService 编写单元测试
✅ 1.7 实现 OrderController 的 POST /orders 和 DELETE /orders/:id 路由
坏的任务分解:
❌ 1.1 实现订单系统(太大,不可原子化验证)
❌ 1.2 写所有测试(没有明确范围)提示词模板:生成任务列表
你是一位技术项目经理。请根据以下技术设计文档,生成实现任务列表。
## 技术设计
[粘贴阶段二产出的设计文档]
## 任务分解要求
1. 每个任务必须是原子化的(可在 30 分钟内完成)
2. 每个任务必须有明确的输入和输出
3. 每个任务必须关联到需求规约中的验收标准
4. 任务之间的依赖关系必须明确
5. 每个任务包含验证标准(如何确认任务完成)
## 输出格式
- [ ] 1. [模块名]
- [ ] 1.1 [具体任务描述]
- 输入:[前置条件]
- 输出:[产出物]
- 验证:[如何验证完成]
- _Requirements: [关联的需求编号]_3. 领域模型 AI 生成(DDD 概念)
3.1 DDD 核心概念速览
领域驱动设计(Domain-Driven Design,DDD)是构建复杂业务逻辑的经典方法论。在 AI 辅助开发中,DDD 的概念为 AI 提供了清晰的建模框架。
| DDD 概念 | 定义 | AI 生成要点 | 示例 |
|---|---|---|---|
| Entity(实体) | 有唯一标识的领域对象,生命周期内标识不变 | 必须定义 ID 类型和生成策略 | Order、User、Product |
| Value Object(值对象) | 无唯一标识,通过属性值定义相等性,不可变 | 必须实现 equals() 和不可变性 | Money、Address、DateRange |
| Aggregate(聚合) | 一组相关对象的集合,通过 Aggregate Root 访问 | 定义聚合边界和一致性规则 | Order(含 OrderItem) |
| Aggregate Root(聚合根) | 聚合的入口点,外部只能通过它访问聚合内部 | 所有修改必须通过聚合根 | Order 是 OrderItem 的聚合根 |
| Domain Event(领域事件) | 领域中发生的有意义的事件 | 定义事件名、载荷、发布时机 | OrderCreated、PaymentCompleted |
| Repository(仓储) | 提供聚合的持久化和检索接口 | 接口在领域层,实现在基础设施层 | OrderRepository |
| Domain Service(领域服务) | 不属于任何实体的领域逻辑 | 跨实体的业务规则 | PricingService、InventoryService |
| Application Service(应用服务) | 编排领域对象完成用例 | 事务管理、权限检查、事件发布 | OrderApplicationService |
3.2 AI 生成领域模型的结构化流程
┌──────────────────────────────────────────────────────┐
│ AI 生成领域模型的 4 步流程 │
├──────────────────────────────────────────────────────┤
│ │
│ Step 1:识别核心实体和值对象 │
│ ┌────────────────────────────────────────┐ │
│ │ 输入:业务需求描述 │ │
│ │ 输出:实体列表 + 值对象列表 + 属性定义 │ │
│ └────────────────────────────────────────┘ │
│ ↓ │
│ Step 2:划定聚合边界 │
│ ┌────────────────────────────────────────┐ │
│ │ 输入:实体关系图 │ │
│ │ 输出:聚合划分 + 聚合根标识 │ │
│ │ 原则:一个事务只修改一个聚合 │ │
│ └────────────────────────────────────────┘ │
│ ↓ │
│ Step 3:定义领域事件 │
│ ┌────────────────────────────────────────┐ │
│ │ 输入:状态转换规则 │ │
│ │ 输出:事件名 + 载荷类型 + 发布时机 │ │
│ └────────────────────────────────────────┘ │
│ ↓ │
│ Step 4:生成 TypeScript/Python 类型定义 │
│ ┌────────────────────────────────────────┐ │
│ │ 输入:以上所有产出 │ │
│ │ 输出:完整的类型定义文件 │ │
│ └────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────┘提示词模板:AI 生成领域模型
你是一位 DDD 领域建模专家。请根据以下业务需求,生成完整的领域模型。
## 业务需求
[粘贴业务需求描述]
## 建模要求
### 1. 实体识别
- 列出所有 Entity,标注唯一标识类型(UUID / 自增 ID / 业务编号)
- 列出所有 Value Object,说明为什么它是值对象而非实体
- 为每个实体/值对象定义完整属性(名称、类型、是否必填、默认值、约束)
### 2. 聚合划分
- 标识每个 Aggregate Root
- 说明聚合边界的划分理由
- 确保每个聚合内的一致性规则
### 3. 领域事件
- 列出所有领域事件(命名格式:{Entity}{PastTenseVerb},如 OrderCreated)
- 定义每个事件的载荷类型
- 说明事件的发布时机和消费者
### 4. 类型定义
- 使用 [TypeScript / Python dataclass] 语法
- Entity 必须包含 id、createdAt、updatedAt
- Value Object 必须是 readonly/frozen
- 使用枚举定义所有状态值
## 输出格式
使用代码块输出类型定义,使用 Mermaid 类图展示实体关系。3.3 领域模型代码示例
以电商订单系统为例,展示 AI 生成的领域模型:
TypeScript 领域模型
// ============================================
// Value Objects(值对象)
// ============================================
/** 金额值对象 —— 避免浮点数精度问题 */
interface Money {
readonly amount: number; // 以"分"为单位存储(整数)
readonly currency: 'CNY' | 'USD' | 'EUR';
}
/** 地址值对象 */
interface Address {
readonly province: string;
readonly city: string;
readonly district: string;
readonly street: string;
readonly zipCode: string;
}
/** 订单项值对象 */
interface OrderItem {
readonly productId: string;
readonly productName: string;
readonly unitPrice: Money;
readonly quantity: number;
readonly subtotal: Money; // unitPrice.amount * quantity
}
// ============================================
// Enums(枚举)
// ============================================
enum OrderStatus {
PENDING_PAYMENT = 'pending_payment',
PAID = 'paid',
PREPARING = 'preparing',
SHIPPED = 'shipped',
DELIVERED = 'delivered',
COMPLETED = 'completed',
CANCELLED = 'cancelled',
REFUNDING = 'refunding',
REFUNDED = 'refunded',
}
enum DiscountType {
BULK_ORDER = 'bulk_order', // 满减
VIP_DISCOUNT = 'vip_discount', // VIP 折扣
COUPON = 'coupon', // 优惠券
PROMOTION = 'promotion', // 促销活动
}
// ============================================
// Entities(实体)
// ============================================
/** 订单实体 —— Aggregate Root */
interface Order {
readonly id: string; // UUID
readonly orderNumber: string; // 业务编号:ORD-20250615-XXXX
readonly customerId: string;
readonly items: readonly OrderItem[];
readonly shippingAddress: Address;
readonly subtotal: Money; // 商品小计
readonly shippingFee: Money; // 运费
readonly discount: Money; // 折扣金额
readonly totalAmount: Money; // 最终金额
readonly discountType: DiscountType | null;
readonly status: OrderStatus;
readonly paidAt: Date | null;
readonly shippedAt: Date | null;
readonly cancelledAt: Date | null;
readonly cancelReason: string | null;
readonly createdAt: Date;
readonly updatedAt: Date;
readonly expiresAt: Date; // 支付截止时间
}
// ============================================
// Domain Events(领域事件)
// ============================================
interface OrderCreatedEvent {
readonly type: 'OrderCreated';
readonly orderId: string;
readonly customerId: string;
readonly totalAmount: Money;
readonly occurredAt: Date;
}
interface OrderPaidEvent {
readonly type: 'OrderPaid';
readonly orderId: string;
readonly paymentId: string;
readonly amount: Money;
readonly occurredAt: Date;
}
interface OrderCancelledEvent {
readonly type: 'OrderCancelled';
readonly orderId: string;
readonly reason: string;
readonly cancelledBy: 'customer' | 'system' | 'admin';
readonly occurredAt: Date;
}
type OrderDomainEvent =
| OrderCreatedEvent
| OrderPaidEvent
| OrderCancelledEvent;Python 领域模型(对比参考)
from dataclasses import dataclass, field
from datetime import datetime
from decimal import Decimal
from enum import Enum
from typing import Optional
from uuid import UUID, uuid4
# ============================================
# Value Objects
# ============================================
@dataclass(frozen=True)
class Money:
"""金额值对象 —— 使用 Decimal 避免精度问题"""
amount: Decimal
currency: str = "CNY"
def __add__(self, other: "Money") -> "Money":
assert self.currency == other.currency
return Money(amount=self.amount + other.amount, currency=self.currency)
def __mul__(self, factor: int) -> "Money":
return Money(amount=self.amount * factor, currency=self.currency)
@dataclass(frozen=True)
class Address:
province: str
city: str
district: str
street: str
zip_code: str
@dataclass(frozen=True)
class OrderItem:
product_id: UUID
product_name: str
unit_price: Money
quantity: int
@property
def subtotal(self) -> Money:
return self.unit_price * self.quantity
# ============================================
# Enums
# ============================================
class OrderStatus(str, Enum):
PENDING_PAYMENT = "pending_payment"
PAID = "paid"
PREPARING = "preparing"
SHIPPED = "shipped"
DELIVERED = "delivered"
COMPLETED = "completed"
CANCELLED = "cancelled"
REFUNDING = "refunding"
REFUNDED = "refunded"
# ============================================
# Entity (Aggregate Root)
# ============================================
@dataclass
class Order:
"""订单聚合根"""
id: UUID = field(default_factory=uuid4)
order_number: str = ""
customer_id: UUID = field(default_factory=uuid4)
items: list[OrderItem] = field(default_factory=list)
shipping_address: Optional[Address] = None
status: OrderStatus = OrderStatus.PENDING_PAYMENT
total_amount: Money = field(default_factory=lambda: Money(Decimal("0")))
shipping_fee: Money = field(default_factory=lambda: Money(Decimal("0")))
discount: Money = field(default_factory=lambda: Money(Decimal("0")))
paid_at: Optional[datetime] = None
cancelled_at: Optional[datetime] = None
cancel_reason: Optional[str] = None
created_at: datetime = field(default_factory=datetime.utcnow)
updated_at: datetime = field(default_factory=datetime.utcnow)
# 领域事件收集
_domain_events: list = field(default_factory=list, repr=False)
def add_item(self, item: OrderItem) -> None:
"""添加订单项 —— 业务规则在聚合根内执行"""
self.items.append(item)
self._recalculate_total()
def cancel(self, reason: str, cancelled_by: str = "customer") -> None:
"""取消订单 —— 包含状态验证"""
allowed = {OrderStatus.PENDING_PAYMENT, OrderStatus.PAID}
if self.status not in allowed:
raise ValueError(f"Cannot cancel order in status: {self.status}")
self.status = OrderStatus.CANCELLED
self.cancelled_at = datetime.utcnow()
self.cancel_reason = reason
self._domain_events.append({
"type": "OrderCancelled",
"order_id": str(self.id),
"reason": reason,
"cancelled_by": cancelled_by,
})
def _recalculate_total(self) -> None:
subtotal = sum(
(item.subtotal.amount for item in self.items), Decimal("0")
)
self.total_amount = Money(
subtotal + self.shipping_fee.amount - self.discount.amount
)3.4 聚合边界划分的 AI 辅助决策
聚合边界的划分是 DDD 中最关键也最容易出错的决策。以下是让 AI 辅助划分聚合边界的提示词模板:
提示词模板:聚合边界划分
你是一位 DDD 架构师。请分析以下实体关系,划分聚合边界。
## 实体列表
[列出所有实体及其关系]
## 划分原则
1. **事务一致性**:一个事务只修改一个聚合
2. **业务不变量**:聚合内的所有不变量必须在一次操作中保证
3. **最小化聚合**:聚合应尽可能小,只包含必须一起修改的对象
4. **引用规则**:聚合之间通过 ID 引用,不直接持有对象引用
## 分析要求
对于每个聚合,请说明:
1. 聚合根是谁?
2. 聚合内包含哪些实体/值对象?
3. 聚合的不变量(invariant)是什么?
4. 聚合之间如何通信(领域事件 vs 直接调用)?
## 常见陷阱提醒
- 不要把所有相关实体放在一个大聚合里("上帝聚合"反模式)
- 不要让聚合跨越数据库表的自然边界
- 考虑并发修改的场景:如果两个用户同时修改同一个聚合,会发生什么?4. 服务层模式(Repository / Service / Controller)
4.1 三层架构概览
┌─────────────────────────────────────────────────────────┐
│ 三层服务架构 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Controller 层(HTTP 协议转换) │ │
│ │ • 解析请求参数 │ │
│ │ • 调用 Service 层 │ │
│ │ • 格式化响应 │ │
│ │ • 错误码映射 │ │
│ └──────────────────┬──────────────────────────┘ │
│ │ │
│ ┌──────────────────▼──────────────────────────┐ │
│ │ Service 层(业务逻辑编排) │ │
│ │ • 业务规则执行 │ │
│ │ • 事务管理 │ │
│ │ • 权限检查 │ │
│ │ • 领域事件发布 │ │
│ │ • 跨聚合协调 │ │
│ └──────────────────┬──────────────────────────┘ │
│ │ │
│ ┌──────────────────▼──────────────────────────┐ │
│ │ Repository 层(数据访问抽象) │ │
│ │ • CRUD 操作 │ │
│ │ • 查询构建 │ │
│ │ • 数据映射(DB Record ↔ Domain Entity) │ │
│ │ • 接口在领域层,实现在基础设施层 │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘4.2 Repository 模式
Repository 模式将数据访问逻辑从业务逻辑中隔离出来,使业务逻辑不依赖具体的数据库实现。
TypeScript Repository 示例(Prisma)
// ============================================
// Repository 接口(领域层)
// ============================================
interface OrderRepository {
findById(id: string): Promise<Order | null>;
findByCustomerId(customerId: string, options?: {
status?: OrderStatus;
limit?: number;
offset?: number;
}): Promise<Order[]>;
findByOrderNumber(orderNumber: string): Promise<Order | null>;
save(order: Order): Promise<Order>;
update(order: Order): Promise<Order>;
countByCustomerIdInPeriod(
customerId: string,
startTime: Date,
endTime: Date
): Promise<number>;
}
// ============================================
// Repository 实现(基础设施层 —— Prisma)
// ============================================
class PrismaOrderRepository implements OrderRepository {
constructor(private readonly prisma: PrismaClient) {}
async findById(id: string): Promise<Order | null> {
const record = await this.prisma.order.findUnique({
where: { id },
include: { items: true },
});
return record ? this.toDomain(record) : null;
}
async findByCustomerId(
customerId: string,
options?: { status?: OrderStatus; limit?: number; offset?: number }
): Promise<Order[]> {
const records = await this.prisma.order.findMany({
where: {
customerId,
...(options?.status && { status: options.status }),
},
include: { items: true },
take: options?.limit ?? 20,
skip: options?.offset ?? 0,
orderBy: { createdAt: 'desc' },
});
return records.map(r => this.toDomain(r));
}
async save(order: Order): Promise<Order> {
const record = await this.prisma.order.create({
data: this.toPersistence(order),
include: { items: true },
});
return this.toDomain(record);
}
async countByCustomerIdInPeriod(
customerId: string,
startTime: Date,
endTime: Date
): Promise<number> {
return this.prisma.order.count({
where: {
customerId,
createdAt: { gte: startTime, lte: endTime },
},
});
}
// 数据映射:DB Record → Domain Entity
private toDomain(record: any): Order {
return {
id: record.id,
orderNumber: record.orderNumber,
customerId: record.customerId,
items: record.items.map((item: any) => ({
productId: item.productId,
productName: item.productName,
unitPrice: { amount: item.unitPriceCents, currency: 'CNY' },
quantity: item.quantity,
subtotal: { amount: item.unitPriceCents * item.quantity, currency: 'CNY' },
})),
status: record.status as OrderStatus,
totalAmount: { amount: record.totalAmountCents, currency: 'CNY' },
shippingFee: { amount: record.shippingFeeCents, currency: 'CNY' },
discount: { amount: record.discountCents, currency: 'CNY' },
// ... 其他字段映射
createdAt: record.createdAt,
updatedAt: record.updatedAt,
} as Order;
}
// 数据映射:Domain Entity → DB Record
private toPersistence(order: Order): any {
return {
id: order.id,
orderNumber: order.orderNumber,
customerId: order.customerId,
status: order.status,
totalAmountCents: order.totalAmount.amount,
shippingFeeCents: order.shippingFee.amount,
discountCents: order.discount.amount,
items: {
create: order.items.map(item => ({
productId: item.productId,
productName: item.productName,
unitPriceCents: item.unitPrice.amount,
quantity: item.quantity,
})),
},
};
}
}4.3 Service 层模式
Service 层是业务逻辑的核心所在,负责编排领域对象、执行业务规则、管理事务。
TypeScript Service 示例
// ============================================
// Application Service(应用服务)
// ============================================
interface CreateOrderInput {
customerId: string;
items: Array<{
productId: string;
quantity: number;
}>;
shippingAddress: Address;
couponCode?: string;
}
interface CreateOrderResult {
order: Order;
events: OrderDomainEvent[];
}
class OrderService {
constructor(
private readonly orderRepo: OrderRepository,
private readonly productRepo: ProductRepository,
private readonly customerRepo: CustomerRepository,
private readonly inventoryService: InventoryService,
private readonly pricingService: PricingService,
private readonly eventBus: EventBus,
) {}
/**
* 创建订单 —— 核心业务流程
*
* 业务规则:
* 1. 验证客户存在且状态正常
* 2. 验证所有商品存在且上架中
* 3. 检查库存是否充足
* 4. 计算订单金额(含折扣)
* 5. 检查防刷单限制(24h 内最多 10 单)
* 6. 创建订单并预扣库存
* 7. 发布 OrderCreated 事件
*/
async createOrder(input: CreateOrderInput): Promise<CreateOrderResult> {
// 1. 验证客户
const customer = await this.customerRepo.findById(input.customerId);
if (!customer) {
throw new BusinessError('CUSTOMER_NOT_FOUND', '客户不存在');
}
if (customer.status === 'suspended') {
throw new BusinessError('CUSTOMER_SUSPENDED', '账户已被冻结');
}
// 2. 验证商品并获取价格
const products = await this.productRepo.findByIds(
input.items.map(i => i.productId)
);
const orderItems = this.buildOrderItems(input.items, products);
// 3. 检查库存
for (const item of orderItems) {
const available = await this.inventoryService.checkAvailability(
item.productId,
item.quantity
);
if (!available) {
throw new BusinessError(
'INSUFFICIENT_INVENTORY',
`商品 ${item.productName} 库存不足`
);
}
}
// 4. 计算金额
const pricing = await this.pricingService.calculate({
items: orderItems,
customerId: input.customerId,
customerLevel: customer.vipLevel,
couponCode: input.couponCode,
});
// 5. 防刷单检查
const now = new Date();
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const recentOrderCount = await this.orderRepo.countByCustomerIdInPeriod(
input.customerId,
oneDayAgo,
now
);
if (recentOrderCount >= 10) {
throw new BusinessError(
'ORDER_RATE_LIMIT_EXCEEDED',
'24 小时内下单次数已达上限(10 单)'
);
}
// 6. 创建订单
const order: Order = {
id: generateUUID(),
orderNumber: generateOrderNumber(),
customerId: input.customerId,
items: orderItems,
shippingAddress: input.shippingAddress,
subtotal: pricing.subtotal,
shippingFee: pricing.shippingFee,
discount: pricing.discount,
totalAmount: pricing.totalAmount,
discountType: pricing.discountType,
status: OrderStatus.PENDING_PAYMENT,
paidAt: null,
shippedAt: null,
cancelledAt: null,
cancelReason: null,
createdAt: now,
updatedAt: now,
expiresAt: new Date(now.getTime() + 30 * 60 * 1000), // 30 分钟后过期
};
const savedOrder = await this.orderRepo.save(order);
// 7. 预扣库存
for (const item of orderItems) {
await this.inventoryService.reserve(item.productId, item.quantity, savedOrder.id);
}
// 8. 发布领域事件
const event: OrderCreatedEvent = {
type: 'OrderCreated',
orderId: savedOrder.id,
customerId: savedOrder.customerId,
totalAmount: savedOrder.totalAmount,
occurredAt: now,
};
await this.eventBus.publish(event);
return { order: savedOrder, events: [event] };
}
/**
* 取消订单
*
* 业务规则:
* 1. 只有 pending_payment 和 paid 状态可以取消
* 2. 已发货的订单不能取消(需走退货流程)
* 3. 取消后释放预扣库存
* 4. 如果已支付,触发退款流程
*/
async cancelOrder(
orderId: string,
reason: string,
cancelledBy: 'customer' | 'system' | 'admin' = 'customer'
): Promise<Order> {
const order = await this.orderRepo.findById(orderId);
if (!order) {
throw new BusinessError('ORDER_NOT_FOUND', '订单不存在');
}
// 状态验证
const cancellableStatuses = [OrderStatus.PENDING_PAYMENT, OrderStatus.PAID];
if (!cancellableStatuses.includes(order.status)) {
throw new BusinessError(
'ORDER_CANNOT_CANCEL',
`当前状态 ${order.status} 不允许取消`
);
}
// 更新订单状态
const updatedOrder: Order = {
...order,
status: OrderStatus.CANCELLED,
cancelledAt: new Date(),
cancelReason: reason,
updatedAt: new Date(),
};
await this.orderRepo.update(updatedOrder);
// 释放库存
for (const item of order.items) {
await this.inventoryService.release(item.productId, item.quantity, orderId);
}
// 如果已支付,触发退款
if (order.status === OrderStatus.PAID) {
await this.eventBus.publish({
type: 'RefundRequested',
orderId: order.id,
amount: order.totalAmount,
reason,
occurredAt: new Date(),
});
}
// 发布取消事件
await this.eventBus.publish({
type: 'OrderCancelled',
orderId: order.id,
reason,
cancelledBy,
occurredAt: new Date(),
});
return updatedOrder;
}
private buildOrderItems(
inputItems: Array<{ productId: string; quantity: number }>,
products: Product[]
): OrderItem[] {
return inputItems.map(input => {
const product = products.find(p => p.id === input.productId);
if (!product) {
throw new BusinessError('PRODUCT_NOT_FOUND', `商品 ${input.productId} 不存在`);
}
if (product.status !== 'active') {
throw new BusinessError('PRODUCT_NOT_AVAILABLE', `商品 ${product.name} 已下架`);
}
return {
productId: product.id,
productName: product.name,
unitPrice: product.price,
quantity: input.quantity,
subtotal: {
amount: product.price.amount * input.quantity,
currency: product.price.currency,
},
};
});
}
}4.4 Controller 层模式
Controller 层只负责 HTTP 协议转换,不包含任何业务逻辑。
// ============================================
// Controller 层(Express / NestJS 风格)
// ============================================
// Express 风格
const orderController = {
async createOrder(req: Request, res: Response, next: NextFunction) {
try {
// 1. 解析和验证请求参数
const input = CreateOrderSchema.parse(req.body);
// 2. 调用 Service 层
const result = await orderService.createOrder({
customerId: req.user.id, // 从认证中间件获取
items: input.items,
shippingAddress: input.shippingAddress,
couponCode: input.couponCode,
});
// 3. 格式化响应
res.status(201).json({
success: true,
data: {
orderId: result.order.id,
orderNumber: result.order.orderNumber,
totalAmount: result.order.totalAmount,
status: result.order.status,
expiresAt: result.order.expiresAt,
},
});
} catch (error) {
next(error); // 交给错误处理中间件
}
},
async cancelOrder(req: Request, res: Response, next: NextFunction) {
try {
const { orderId } = req.params;
const { reason } = CancelOrderSchema.parse(req.body);
const order = await orderService.cancelOrder(orderId, reason, 'customer');
res.status(200).json({
success: true,
data: { orderId: order.id, status: order.status },
});
} catch (error) {
next(error);
}
},
};
// 错误处理中间件 —— 将业务错误映射为 HTTP 状态码
function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
if (err instanceof BusinessError) {
const statusMap: Record<string, number> = {
CUSTOMER_NOT_FOUND: 404,
ORDER_NOT_FOUND: 404,
PRODUCT_NOT_FOUND: 404,
CUSTOMER_SUSPENDED: 403,
INSUFFICIENT_INVENTORY: 409,
ORDER_RATE_LIMIT_EXCEEDED: 429,
ORDER_CANNOT_CANCEL: 409,
PRODUCT_NOT_AVAILABLE: 422,
};
const status = statusMap[err.code] ?? 400;
return res.status(status).json({
success: false,
error: { code: err.code, message: err.message },
});
}
// 未知错误
console.error('Unhandled error:', err);
res.status(500).json({
success: false,
error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' },
});
}提示词模板:生成服务层代码
你是一位后端开发专家。请根据以下技术设计,生成完整的服务层代码。
## 技术设计
[粘贴领域模型和服务层设计]
## 代码生成要求
### Repository 层
- 定义接口(interface)在领域层
- 实现类使用 [Prisma / Drizzle / SQLAlchemy]
- 包含数据映射方法(toDomain / toPersistence)
- 所有查询方法返回领域对象,不返回 ORM 对象
### Service 层
- 每个公开方法对应一个业务用例
- 方法内注释标注对应的业务规则编号
- 所有业务规则验证在 Service 层执行
- 使用依赖注入接收 Repository
- 错误使用自定义 BusinessError 类
### Controller 层
- 只负责 HTTP 协议转换
- 使用 Zod/Pydantic 验证请求参数
- 不包含任何业务逻辑
- 错误映射到合适的 HTTP 状态码
### 通用要求
- 所有方法包含 JSDoc/docstring 注释
- 标注每个方法关联的需求编号
- 使用 TypeScript 严格模式 / Python type hints5. AI 辅助业务规则实现
5.1 业务规则的分类与实现策略
| 规则类型 | 描述 | AI 生成难度 | 实现位置 | 示例 |
|---|---|---|---|---|
| 验证规则 | 输入数据的合法性检查 | ⭐⭐(简单) | Controller / Service 入口 | 邮箱格式、金额范围、必填字段 |
| 计算规则 | 基于输入计算输出 | ⭐⭐⭐(中等) | Service / Domain Service | 订单金额计算、折扣计算、税费计算 |
| 状态转换规则 | 实体状态的合法转换 | ⭐⭐⭐(中等) | Entity / Aggregate Root | 订单状态机、审批流程 |
| 授权规则 | 谁可以执行什么操作 | ⭐⭐⭐⭐(较难) | Service / Middleware | 只有订单所有者可以取消、管理员可以退款 |
| 跨聚合规则 | 涉及多个聚合的业务约束 | ⭐⭐⭐⭐⭐(困难) | Application Service | 下单时检查库存、支付时检查订单状态 |
| 时间规则 | 基于时间的业务逻辑 | ⭐⭐⭐⭐(较难) | Scheduled Job / Service | 超时自动取消、促销活动时间窗口 |
5.2 状态机模式
状态机是业务逻辑中最常见的模式之一。AI 生成状态机代码时,需要明确定义所有合法的状态转换。
// ============================================
// 状态机实现
// ============================================
/** 状态转换定义 */
const ORDER_TRANSITIONS: Record<OrderStatus, OrderStatus[]> = {
[OrderStatus.PENDING_PAYMENT]: [OrderStatus.PAID, OrderStatus.CANCELLED],
[OrderStatus.PAID]: [OrderStatus.PREPARING, OrderStatus.CANCELLED, OrderStatus.REFUNDING],
[OrderStatus.PREPARING]: [OrderStatus.SHIPPED],
[OrderStatus.SHIPPED]: [OrderStatus.DELIVERED],
[OrderStatus.DELIVERED]: [OrderStatus.COMPLETED, OrderStatus.REFUNDING],
[OrderStatus.COMPLETED]: [], // 终态
[OrderStatus.CANCELLED]: [], // 终态
[OrderStatus.REFUNDING]: [OrderStatus.REFUNDED],
[OrderStatus.REFUNDED]: [], // 终态
};
/** 状态转换守卫(额外条件检查) */
type TransitionGuard = (order: Order, context: TransitionContext) => Promise<boolean>;
const TRANSITION_GUARDS: Partial<Record<string, TransitionGuard>> = {
// 从 paid 到 cancelled 需要检查是否已发货
[`${OrderStatus.PAID}->${OrderStatus.CANCELLED}`]: async (order) => {
// 已开始备货的订单不能直接取消
return order.status === OrderStatus.PAID;
},
// 从 delivered 到 refunding 需要在签收后 7 天内
[`${OrderStatus.DELIVERED}->${OrderStatus.REFUNDING}`]: async (order) => {
if (!order.shippedAt) return false;
const daysSinceDelivery = (Date.now() - order.shippedAt.getTime()) / (1000 * 60 * 60 * 24);
return daysSinceDelivery <= 7;
},
};
class OrderStateMachine {
/**
* 执行状态转换
* @throws BusinessError 如果转换不合法
*/
async transition(
order: Order,
targetStatus: OrderStatus,
context: TransitionContext
): Promise<Order> {
// 1. 检查转换是否合法
const allowedTargets = ORDER_TRANSITIONS[order.status];
if (!allowedTargets.includes(targetStatus)) {
throw new BusinessError(
'INVALID_STATUS_TRANSITION',
`不允许从 ${order.status} 转换到 ${targetStatus}`
);
}
// 2. 执行转换守卫
const guardKey = `${order.status}->${targetStatus}`;
const guard = TRANSITION_GUARDS[guardKey];
if (guard) {
const allowed = await guard(order, context);
if (!allowed) {
throw new BusinessError(
'TRANSITION_GUARD_FAILED',
`状态转换条件不满足:${guardKey}`
);
}
}
// 3. 执行转换
return {
...order,
status: targetStatus,
updatedAt: new Date(),
};
}
}5.3 定价引擎模式
复杂的定价逻辑是 AI 生成业务规则的典型场景。使用策略模式可以让定价规则可扩展、可测试。
// ============================================
// 定价引擎(策略模式)
// ============================================
interface PricingContext {
items: OrderItem[];
customerId: string;
customerLevel: number; // VIP 等级 1-5
couponCode?: string;
}
interface PricingResult {
subtotal: Money;
shippingFee: Money;
discount: Money;
totalAmount: Money;
discountType: DiscountType | null;
appliedRules: string[]; // 记录应用了哪些规则(可审计)
}
/** 折扣规则接口 */
interface DiscountRule {
name: string;
priority: number; // 优先级越高越先执行
canApply(context: PricingContext, subtotal: number): boolean;
calculate(context: PricingContext, subtotal: number): number; // 返回折扣金额(分)
}
/** VIP 免运费规则 */
const vipFreeShippingRule: DiscountRule = {
name: 'VIP 免运费',
priority: 10,
canApply: (ctx) => ctx.customerLevel >= 3,
calculate: () => 0, // 运费单独处理
};
/** 满 100 元打 9 折规则 */
const bulkOrderDiscountRule: DiscountRule = {
name: '满 100 元 9 折',
priority: 20,
canApply: (_, subtotal) => subtotal >= 10000, // 10000 分 = 100 元
calculate: (_, subtotal) => Math.round(subtotal * 0.1), // 10% 折扣
};
class PricingService {
private rules: DiscountRule[];
constructor(rules: DiscountRule[]) {
// 按优先级排序
this.rules = [...rules].sort((a, b) => a.priority - b.priority);
}
async calculate(context: PricingContext): Promise<PricingResult> {
// 1. 计算商品小计
const subtotalCents = context.items.reduce(
(sum, item) => sum + item.unitPrice.amount * item.quantity,
0
);
// 2. 计算运费
const shippingFeeCents = context.customerLevel >= 3 ? 0 : 800; // VIP 免运费,否则 8 元
// 3. 应用折扣规则(只应用第一个匹配的规则)
let discountCents = 0;
let discountType: DiscountType | null = null;
const appliedRules: string[] = [];
for (const rule of this.rules) {
if (rule.canApply(context, subtotalCents)) {
discountCents = rule.calculate(context, subtotalCents);
appliedRules.push(rule.name);
break; // 只应用优先级最高的规则
}
}
// 4. 计算总金额
const totalCents = subtotalCents + shippingFeeCents - discountCents;
return {
subtotal: { amount: subtotalCents, currency: 'CNY' },
shippingFee: { amount: shippingFeeCents, currency: 'CNY' },
discount: { amount: discountCents, currency: 'CNY' },
totalAmount: { amount: Math.max(totalCents, 0), currency: 'CNY' },
discountType,
appliedRules,
};
}
}6. 事务管理与数据一致性
6.1 事务管理策略
事务管理是 AI 生成业务逻辑中最容易被忽略的部分。AI 倾向于生成”快乐路径”代码,忽略失败回滚和并发冲突。
| 策略 | 适用场景 | 复杂度 | AI 生成质量 |
|---|---|---|---|
| 数据库事务 | 单数据库、单服务内的操作 | 低 | ⭐⭐⭐⭐(较好) |
| 乐观锁 | 读多写少、冲突概率低 | 中 | ⭐⭐⭐(一般) |
| 悲观锁 | 写多读少、冲突概率高 | 中 | ⭐⭐⭐(一般) |
| Saga 模式 | 跨服务的分布式事务 | 高 | ⭐⭐(较差,需人工审核) |
| 事件溯源 | 需要完整审计轨迹 | 高 | ⭐⭐(较差,需人工审核) |
| 幂等性设计 | 所有写操作 | 中 | ⭐⭐⭐(一般) |
6.2 数据库事务实现
TypeScript + Prisma 事务
class OrderService {
/**
* 创建订单 —— 使用数据库事务确保一致性
*
* 事务范围:
* 1. 创建订单记录
* 2. 创建订单项记录
* 3. 预扣库存
* 4. 记录操作日志
*
* 如果任何步骤失败,所有操作回滚
*/
async createOrderWithTransaction(input: CreateOrderInput): Promise<Order> {
return this.prisma.$transaction(async (tx) => {
// 1. 在事务内检查库存(使用 SELECT FOR UPDATE 防止并发超卖)
for (const item of input.items) {
const inventory = await tx.inventory.findUnique({
where: { productId: item.productId },
});
if (!inventory || inventory.available < item.quantity) {
throw new BusinessError('INSUFFICIENT_INVENTORY', '库存不足');
}
}
// 2. 创建订单
const order = await tx.order.create({
data: {
id: generateUUID(),
orderNumber: generateOrderNumber(),
customerId: input.customerId,
status: 'pending_payment',
totalAmountCents: input.totalAmountCents,
items: {
create: input.items.map(item => ({
productId: item.productId,
quantity: item.quantity,
unitPriceCents: item.unitPriceCents,
})),
},
},
include: { items: true },
});
// 3. 预扣库存
for (const item of input.items) {
await tx.inventory.update({
where: { productId: item.productId },
data: {
available: { decrement: item.quantity },
reserved: { increment: item.quantity },
},
});
}
// 4. 记录操作日志
await tx.auditLog.create({
data: {
entityType: 'order',
entityId: order.id,
action: 'create',
performedBy: input.customerId,
details: JSON.stringify({ items: input.items }),
},
});
return order;
}, {
// 事务配置
maxWait: 5000, // 最大等待时间 5 秒
timeout: 10000, // 事务超时 10 秒
isolationLevel: 'Serializable', // 最高隔离级别防止并发问题
});
}
}Python + SQLAlchemy 事务
from sqlalchemy.orm import Session
from contextlib import contextmanager
class OrderService:
def __init__(self, session_factory):
self.session_factory = session_factory
@contextmanager
def _transaction(self):
"""事务上下文管理器"""
session: Session = self.session_factory()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
def create_order(self, input_data: CreateOrderInput) -> Order:
"""创建订单 —— 使用事务确保一致性"""
with self._transaction() as session:
# 1. 检查库存(使用 FOR UPDATE 锁定行)
for item in input_data.items:
inventory = (
session.query(InventoryModel)
.filter_by(product_id=item.product_id)
.with_for_update() # 悲观锁
.first()
)
if not inventory or inventory.available < item.quantity:
raise BusinessError("INSUFFICIENT_INVENTORY", "库存不足")
# 2. 创建订单
order = OrderModel(
id=str(uuid4()),
customer_id=input_data.customer_id,
status=OrderStatus.PENDING_PAYMENT,
total_amount=input_data.total_amount,
)
session.add(order)
# 3. 预扣库存
for item in input_data.items:
session.query(InventoryModel).filter_by(
product_id=item.product_id
).update({
"available": InventoryModel.available - item.quantity,
"reserved": InventoryModel.reserved + item.quantity,
})
# 事务在 with 块结束时自动提交
return self._to_domain(order)6.3 乐观锁实现
// ============================================
// 乐观锁 —— 使用版本号防止并发冲突
// ============================================
// Prisma Schema
// model Order {
// id String @id
// version Int @default(0) // 版本号
// status String
// ...
// }
class OrderRepository {
/**
* 使用乐观锁更新订单
* 如果版本号不匹配(被其他事务修改过),抛出冲突错误
*/
async updateWithOptimisticLock(order: Order): Promise<Order> {
const result = await this.prisma.order.updateMany({
where: {
id: order.id,
version: order.version, // 只有版本号匹配才更新
},
data: {
...this.toPersistence(order),
version: { increment: 1 }, // 版本号 +1
},
});
if (result.count === 0) {
throw new ConcurrencyError(
'ORDER_CONCURRENT_MODIFICATION',
'订单已被其他操作修改,请重试'
);
}
return { ...order, version: order.version + 1 };
}
}
// Service 层使用重试机制处理乐观锁冲突
class OrderService {
async updateOrderWithRetry(
orderId: string,
updateFn: (order: Order) => Order,
maxRetries: number = 3
): Promise<Order> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const order = await this.orderRepo.findById(orderId);
if (!order) throw new BusinessError('ORDER_NOT_FOUND', '订单不存在');
const updatedOrder = updateFn(order);
return await this.orderRepo.updateWithOptimisticLock(updatedOrder);
} catch (error) {
if (error instanceof ConcurrencyError && attempt < maxRetries) {
// 等待随机时间后重试(指数退避)
await sleep(Math.pow(2, attempt) * 100 + Math.random() * 100);
continue;
}
throw error;
}
}
throw new BusinessError('MAX_RETRIES_EXCEEDED', '操作重试次数已达上限');
}
}6.4 幂等性设计
// ============================================
// 幂等性 —— 确保重复请求不会产生副作用
// ============================================
class IdempotentOrderService {
constructor(
private readonly orderService: OrderService,
private readonly idempotencyStore: IdempotencyStore,
) {}
/**
* 幂等创建订单
* 使用 idempotencyKey 确保同一请求只执行一次
*/
async createOrder(
idempotencyKey: string,
input: CreateOrderInput
): Promise<CreateOrderResult> {
// 1. 检查是否已处理过
const existing = await this.idempotencyStore.get(idempotencyKey);
if (existing) {
return existing as CreateOrderResult; // 返回之前的结果
}
// 2. 标记为处理中(防止并发重复请求)
const locked = await this.idempotencyStore.lock(idempotencyKey, 60); // 60 秒锁
if (!locked) {
throw new BusinessError('REQUEST_IN_PROGRESS', '请求正在处理中,请稍后');
}
try {
// 3. 执行业务逻辑
const result = await this.orderService.createOrder(input);
// 4. 存储结果(24 小时过期)
await this.idempotencyStore.set(idempotencyKey, result, 86400);
return result;
} catch (error) {
// 5. 失败时释放锁,允许重试
await this.idempotencyStore.unlock(idempotencyKey);
throw error;
}
}
}
// Controller 层从请求头获取幂等键
async function createOrderHandler(req: Request, res: Response) {
const idempotencyKey = req.headers['idempotency-key'] as string;
if (!idempotencyKey) {
return res.status(400).json({ error: '缺少 Idempotency-Key 请求头' });
}
const result = await idempotentOrderService.createOrder(idempotencyKey, req.body);
res.status(201).json(result);
}提示词模板:生成事务管理代码
你是一位后端架构师,擅长数据一致性设计。请为以下业务场景生成事务管理代码。
## 业务场景
[描述需要事务保护的业务操作]
## 一致性要求
1. 哪些操作必须在同一个事务中?
2. 失败时如何回滚?
3. 并发场景下如何处理冲突?
4. 是否需要幂等性保证?
## 技术约束
- 数据库:[PostgreSQL / MySQL]
- ORM:[Prisma / Drizzle / SQLAlchemy]
- 是否涉及跨服务调用:[是/否]
## 输出要求
1. 事务边界明确标注
2. 包含错误处理和回滚逻辑
3. 如果涉及并发,使用乐观锁或悲观锁
4. 如果需要幂等性,实现幂等键机制
5. 包含重试逻辑(指数退避)
6. 添加审计日志记录7. Kiro Spec 工作流实战
7.1 Kiro Spec 工作流概述
Kiro 是目前唯一内置 Spec-Driven 工作流的 IDE。它将 SDD 的三阶段流程(需求→设计→任务)直接集成到开发环境中,开发者只需描述业务需求,Kiro 自动生成结构化的 Spec 文件。
Kiro Spec 工作流:
1. 开发者输入业务需求描述
↓
2. Kiro 生成 requirements.md(EARS 格式验收标准)
↓
3. 开发者审核并修改需求
↓
4. Kiro 生成 design.md(技术设计文档)
↓
5. 开发者审核并修改设计
↓
6. Kiro 生成 tasks.md(原子化任务列表)
↓
7. 开发者逐任务执行,Kiro Agent 辅助实现
↓
8. 每个任务完成后人工审核7.2 Kiro Spec 文件结构
.kiro/
└── specs/
└── order-system/ # 一个 Spec 对应一个功能模块
├── requirements.md # 需求规约
├── design.md # 技术设计
└── tasks.md # 任务列表requirements.md 示例
# Requirements: 订单系统
## Introduction
实现电商平台的核心订单系统,支持下单、支付、取消、退款等完整订单生命周期管理。
## Requirements
### Requirement 1: 创建订单
**User Story:** As a 客户, I want 将购物车中的商品下单,
so that 我能购买所需商品。
#### Acceptance Criteria
1. WHEN a customer submits an order with valid items,
THE OrderService SHALL create an order with status "pending_payment"
AND generate a unique order number in format ORD-YYYYMMDD-XXXX
2. WHEN a customer submits an order with items exceeding available inventory,
THE OrderService SHALL reject the order
AND return error INSUFFICIENT_INVENTORY
3. WHEN a customer has placed 10 or more orders in the past 24 hours,
THE OrderService SHALL reject the new order
AND return error ORDER_RATE_LIMIT_EXCEEDED
4. WHEN an order is created successfully,
THE system SHALL reserve inventory for all order items
AND set payment expiration to 30 minutes from creation time
### Requirement 2: 取消订单
**User Story:** As a 客户, I want 取消未发货的订单,
so that 我能在改变主意时获得退款。
#### Acceptance Criteria
1. WHEN a customer cancels an order with status "pending_payment",
THE OrderService SHALL update status to "cancelled"
AND release reserved inventory
2. WHEN a customer cancels an order with status "paid",
THE OrderService SHALL update status to "cancelled"
AND release reserved inventory
AND trigger a refund process
3. WHEN a customer attempts to cancel an order with status "shipped",
THE OrderService SHALL reject the cancellation
AND return error ORDER_ALREADY_SHIPPED with HTTP 409design.md 示例(关键片段)
# Design: 订单系统
## Architecture
### 领域模型
- **Order(聚合根)**:订单实体,包含订单项、状态、金额
- **OrderItem(值对象)**:订单项,包含商品信息和数量
- **Money(值对象)**:金额,以分为单位存储
### 服务层
- **OrderRepository**:订单数据访问接口
- **OrderService**:订单业务逻辑编排
- **PricingService**:定价计算
- **InventoryService**:库存管理
### 数据库 Schema
| 表名 | 字段 | 类型 | 说明 |
|------|------|------|------|
| orders | id | UUID | 主键 |
| orders | order_number | VARCHAR(20) | 业务编号,唯一索引 |
| orders | customer_id | UUID | 外键 → customers |
| orders | status | VARCHAR(20) | 订单状态枚举 |
| orders | total_amount_cents | INTEGER | 总金额(分) |
| orders | version | INTEGER | 乐观锁版本号 |
| order_items | id | UUID | 主键 |
| order_items | order_id | UUID | 外键 → orders |
| order_items | product_id | UUID | 外键 → products |
| order_items | quantity | INTEGER | 数量 |
| order_items | unit_price_cents | INTEGER | 单价(分) |tasks.md 示例
# Tasks: 订单系统
- [ ] 1. 领域模型层
- [ ] 1.1 创建 Order Entity 和 OrderItem Value Object 的 TypeScript 类型定义
- _Requirements: 1.1_
- [ ] 1.2 创建 Money Value Object 和 OrderStatus 枚举
- _Requirements: 1.1_
- [ ] 1.3 定义 OrderDomainEvent 类型(OrderCreated, OrderCancelled)
- _Requirements: 1.1, 2.1_
- [ ] 2. 数据访问层
- [ ] 2.1 创建 Prisma Schema(orders, order_items 表)
- _Requirements: 1.1_
- [ ] 2.2 实现 OrderRepository 接口和 Prisma 实现
- _Requirements: 1.1, 1.2_
- [ ] 3. 业务逻辑层
- [ ] 3.1 实现 PricingService(金额计算、折扣规则)
- _Requirements: 1.1_
- [ ] 3.2 实现 OrderService.createOrder()(含库存检查、防刷单)
- _Requirements: 1.1, 1.2, 1.3, 1.4_
- [ ] 3.3 实现 OrderService.cancelOrder()(含状态验证、退款触发)
- _Requirements: 2.1, 2.2, 2.3_
- [ ] 4. API 层
- [ ] 4.1 实现 POST /orders 路由和请求验证
- _Requirements: 1.1_
- [ ] 4.2 实现 DELETE /orders/:id 路由
- _Requirements: 2.1_
- [ ] 5. 测试
- [ ] 5.1 OrderService.createOrder() 单元测试
- _Requirements: 1.1, 1.2, 1.3, 1.4_
- [ ] 5.2 OrderService.cancelOrder() 单元测试
- _Requirements: 2.1, 2.2, 2.3_7.3 Steering 规则配合 Spec 工作流
Kiro 的 Steering 规则可以引导 AI Agent 在执行 Spec 任务时遵循特定的架构模式和编码规范。
# .kiro/steering/backend-architecture.md
## 架构规则
### 服务层架构
- 所有业务逻辑必须在 Service 层实现,Controller 层禁止包含业务逻辑
- Repository 接口定义在 src/domain/ 目录,实现在 src/infrastructure/ 目录
- Service 层使用构造函数注入依赖,不使用全局单例
### 领域模型规则
- Entity 必须包含 id、createdAt、updatedAt 字段
- Value Object 必须是 readonly(TypeScript)或 frozen(Python)
- 所有金额使用整数(分)存储,不使用浮点数
- 状态枚举使用 string enum,不使用数字
### 事务规则
- 所有写操作必须在事务中执行
- 跨聚合的操作使用领域事件,不在同一事务中修改多个聚合
- 所有写 API 必须支持幂等性(通过 Idempotency-Key 请求头)
### 错误处理
- 使用自定义 BusinessError 类,包含 code 和 message
- 错误码使用 UPPER_SNAKE_CASE 格式
- Controller 层的错误处理中间件负责将 BusinessError 映射为 HTTP 状态码
### 测试要求
- 每个 Service 方法必须有对应的单元测试
- 测试覆盖正常路径和所有异常路径
- 使用 Repository 接口的 mock 实现进行单元测试7.4 Hooks 自动化
Kiro Hooks 可以在特定事件触发时自动执行操作,配合 Spec 工作流实现自动化质量保证。
# .kiro/hooks/backend-quality.yaml
# 当 Service 文件被修改时,自动运行相关测试
- name: "Auto Test on Service Change"
event: fileEdited
filePatterns: "src/services/**/*.ts"
action: askAgent
prompt: "运行与修改文件相关的单元测试,报告测试结果"
# 当创建新的 Repository 实现时,检查是否遵循接口
- name: "Repository Interface Check"
event: fileCreated
filePatterns: "src/infrastructure/repositories/**/*.ts"
action: askAgent
prompt: "检查新创建的 Repository 实现是否完整实现了对应的接口,是否包含 toDomain 和 toPersistence 映射方法"
# 提交前检查业务逻辑是否有事务保护
- name: "Transaction Check"
event: fileEdited
filePatterns: "src/services/**/*.ts"
action: askAgent
prompt: "检查修改的 Service 方法中,所有写操作是否在事务中执行"实战案例:用 Kiro Spec 工作流构建 SaaS 订阅计费系统
案例背景
一个 SaaS 产品需要实现订阅计费系统,支持多种计费模式(月付/年付)、试用期、升降级、按量计费附加项。这是一个典型的复杂业务逻辑场景。
步骤 1:在 Kiro 中创建 Spec
在 Kiro 中输入业务需求描述:
我需要一个 SaaS 订阅计费系统,支持以下功能:
1. 计划管理:Free / Pro / Enterprise 三个计划,支持月付和年付
2. 订阅生命周期:创建、激活、暂停、取消、过期
3. 试用期:Pro 计划提供 14 天免费试用
4. 升降级:支持计划间的升降级,按比例计算差价
5. 按量计费:API 调用次数超出计划配额后按量计费
6. 发票生成:每月自动生成发票步骤 2:Kiro 生成需求规约(requirements.md)
Kiro 自动生成 EARS 格式的验收标准:
### Requirement 1: 订阅创建
**User Story:** As a 用户, I want 选择并订阅一个计划,
so that 我能使用对应的产品功能。
#### Acceptance Criteria
1. WHEN a user selects the Pro plan with monthly billing,
THE SubscriptionService SHALL create a subscription with:
- status: "trialing" (if first-time Pro subscriber)
- trial_ends_at: current_time + 14 days
- billing_cycle: "monthly"
- amount: 2900 cents ($29/month)
2. WHEN a user selects the Enterprise plan,
THE SubscriptionService SHALL create a subscription with:
- status: "active" (no trial for Enterprise)
- billing_cycle: as selected (monthly: $99, yearly: $990)
3. WHEN a trial period ends without payment method on file,
THE system SHALL update subscription status to "expired"
AND downgrade user to Free plan features步骤 3:审核需求并生成设计
开发者审核需求后,Kiro 生成技术设计:
## 领域模型
### Subscription(聚合根)
- id: UUID
- userId: UUID
- planId: string (free / pro / enterprise)
- status: SubscriptionStatus
- billingCycle: 'monthly' | 'yearly'
- currentPeriodStart: Date
- currentPeriodEnd: Date
- trialEndsAt: Date | null
- cancelledAt: Date | null
- amountCents: number
- version: number (乐观锁)
### UsageRecord(值对象)
- subscriptionId: UUID
- metric: string (api_calls / storage_gb / team_members)
- quantity: number
- recordedAt: Date
### Invoice(实体)
- id: UUID
- subscriptionId: UUID
- periodStart: Date
- periodEnd: Date
- subtotalCents: number
- taxCents: number
- totalCents: number
- status: 'draft' | 'open' | 'paid' | 'void'步骤 4:逐任务执行
Kiro 生成任务列表后,开发者逐任务执行。以”实现升降级逻辑”为例:
// Task 3.4: 实现 SubscriptionService.changePlan()
// Requirements: 4.1, 4.2
class SubscriptionService {
/**
* 变更订阅计划(升级或降级)
*
* 业务规则:
* - 升级立即生效,按剩余天数计算差价
* - 降级在当前计费周期结束时生效
* - Free → Pro/Enterprise:视为新订阅
* - Enterprise → Pro:降级,周期结束生效
* - 任何计划 → Free:等同于取消订阅
*/
async changePlan(
subscriptionId: string,
newPlanId: string,
newBillingCycle: 'monthly' | 'yearly'
): Promise<SubscriptionChangeResult> {
const subscription = await this.subscriptionRepo.findById(subscriptionId);
if (!subscription) {
throw new BusinessError('SUBSCRIPTION_NOT_FOUND', '订阅不存在');
}
const currentPlan = PLANS[subscription.planId];
const newPlan = PLANS[newPlanId];
if (!newPlan) {
throw new BusinessError('INVALID_PLAN', '无效的计划');
}
// 判断是升级还是降级
const isUpgrade = newPlan.tier > currentPlan.tier;
const isDowngrade = newPlan.tier < currentPlan.tier;
if (isUpgrade) {
return this.handleUpgrade(subscription, newPlan, newBillingCycle);
} else if (isDowngrade) {
return this.handleDowngrade(subscription, newPlan, newBillingCycle);
} else {
// 同级别切换计费周期
return this.handleBillingCycleChange(subscription, newBillingCycle);
}
}
private async handleUpgrade(
subscription: Subscription,
newPlan: Plan,
billingCycle: 'monthly' | 'yearly'
): Promise<SubscriptionChangeResult> {
// 计算剩余天数的差价
const now = new Date();
const remainingDays = Math.ceil(
(subscription.currentPeriodEnd.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
);
const totalDays = Math.ceil(
(subscription.currentPeriodEnd.getTime() - subscription.currentPeriodStart.getTime())
/ (1000 * 60 * 60 * 24)
);
const oldDailyRate = subscription.amountCents / totalDays;
const newAmount = billingCycle === 'monthly' ? newPlan.monthlyPriceCents : newPlan.yearlyPriceCents;
const newDailyRate = newAmount / (billingCycle === 'monthly' ? 30 : 365);
const proratedCredit = Math.round(oldDailyRate * remainingDays);
const proratedCharge = Math.round(newDailyRate * remainingDays);
const amountDue = Math.max(proratedCharge - proratedCredit, 0);
// 立即生效
const updatedSubscription = {
...subscription,
planId: newPlan.id,
billingCycle,
amountCents: newAmount,
updatedAt: now,
};
await this.subscriptionRepo.updateWithOptimisticLock(updatedSubscription);
return {
subscription: updatedSubscription,
effectiveDate: now,
proratedAmountCents: amountDue,
type: 'upgrade',
};
}
private async handleDowngrade(
subscription: Subscription,
newPlan: Plan,
billingCycle: 'monthly' | 'yearly'
): Promise<SubscriptionChangeResult> {
// 降级在当前周期结束时生效
const scheduledChange = {
subscriptionId: subscription.id,
newPlanId: newPlan.id,
newBillingCycle: billingCycle,
effectiveDate: subscription.currentPeriodEnd,
createdAt: new Date(),
};
await this.scheduledChangeRepo.save(scheduledChange);
return {
subscription,
effectiveDate: subscription.currentPeriodEnd,
proratedAmountCents: 0,
type: 'downgrade_scheduled',
};
}
}步骤 5:人工审核
每个任务完成后,开发者审核 AI 生成的代码,重点检查:
审核清单:
✅ 业务规则是否完整覆盖了需求规约中的验收标准?
✅ 边界条件是否处理?(金额为 0、剩余天数为 0、同计划切换)
✅ 事务是否正确?(升级的扣费和状态更新是否在同一事务中?)
✅ 并发安全?(两个请求同时升级同一订阅会怎样?)
✅ 错误处理是否完善?(每个失败路径都有明确的错误码?)
✅ 可审计性?(计费变更是否记录了审计日志?)案例总结
| 维度 | Vibe Coding 方式 | Spec-Driven 方式 |
|---|---|---|
| 需求遗漏 | 高(AI 猜测意图) | 低(EARS 格式明确每条规则) |
| 边界条件 | 经常遗漏 | 在需求阶段就明确 |
| 代码可追溯性 | 无(对话历史丢失) | 高(每行代码关联需求编号) |
| 重构安全性 | 低(缺少规约参照) | 高(Spec 文件持久化) |
| 团队协作 | 困难(上下文在个人会话中) | 容易(Spec 文件可共享) |
| 开发时间 | 初始快,后期慢(返工多) | 初始慢 15 分钟,后期快(返工少) |
避坑指南
❌ 常见错误
-
跳过 Spec 直接让 AI 写业务逻辑
- 问题:AI 会遗漏隐含的业务规则,生成的代码看起来能运行但逻辑不完整。例如订单系统缺少防刷单检查、缺少超时自动取消、缺少并发库存扣减保护
- 正确做法:先花 15-30 分钟编写需求规约,明确所有业务规则和边界条件,再让 AI 按规约生成代码
-
把所有实体放在一个大聚合里(“上帝聚合”反模式)
- 问题:AI 倾向于将所有相关实体放在一起(Order 包含 User、Product、Inventory),导致聚合过大、事务范围过广、并发冲突严重
- 正确做法:遵循”一个事务只修改一个聚合”原则,聚合之间通过 ID 引用和领域事件通信
-
在 Controller 层编写业务逻辑
- 问题:AI 经常将验证逻辑、计算逻辑、状态转换逻辑直接写在路由处理函数中,导致代码不可测试、不可复用
- 正确做法:在 Steering 规则中明确禁止 Controller 包含业务逻辑,所有业务规则必须在 Service 层实现
-
忽略事务管理
- 问题:AI 生成的代码通常只处理”快乐路径”,不考虑部分失败的回滚。例如创建订单成功但库存扣减失败,导致数据不一致
- 正确做法:在 Spec 的设计文档中明确事务边界,要求 AI 在事务中执行所有相关写操作
-
使用浮点数存储金额
- 问题:AI 默认使用
number类型存储金额(如price: 29.99),浮点数精度问题会导致计算错误(0.1 + 0.2 ≠ 0.3) - 正确做法:在 Steering 规则中要求所有金额以整数(分/cents)存储,使用
amountCents: number或 Python 的Decimal类型
- 问题:AI 默认使用
-
缺少幂等性设计
- 问题:网络重试、用户重复点击会导致重复创建订单、重复扣款。AI 生成的 API 通常不考虑幂等性
- 正确做法:所有写操作 API 要求客户端传递
Idempotency-Key请求头,服务端实现幂等性检查
-
状态转换缺少守卫条件
- 问题:AI 生成的状态机只检查”当前状态是否允许转换到目标状态”,不检查额外的业务条件(如”签收后 7 天内才能退货”)
- 正确做法:为每个状态转换定义守卫条件(Guard),在 Spec 中明确列出
-
领域事件丢失
- 问题:AI 在 Service 方法中直接调用其他 Service(如 OrderService 直接调用 InventoryService),导致紧耦合和事务边界混乱
- 正确做法:跨聚合的操作通过领域事件解耦,使用事件总线或消息队列
✅ 最佳实践
-
“15 分钟瀑布”规划法:每个功能模块开始前,花 15 分钟编写 Spec(需求 5 分钟 + 设计 5 分钟 + 任务 5 分钟),这 15 分钟的投入可以节省数小时的返工时间
-
Steering 规则前置:在项目开始时就编写后端架构的 Steering 规则,确保 AI 从第一行代码就遵循正确的架构模式
-
金额计算使用整数:所有金额以”分”为单位存储和计算,只在展示层转换为”元”。这是金融系统的行业标准
-
每个 Service 方法对应一个用例:Service 方法的粒度应该与用户故事一一对应,方法名使用业务语言(
createOrder、cancelOrder),不使用技术语言(insertRecord、updateStatus) -
测试驱动验证:每个 Spec 任务完成后,先运行测试验证业务规则的正确性,再进行人工代码审查
-
版本化 Spec 文件:将
.kiro/specs/目录纳入 Git 版本控制,Spec 文件的变更历史就是业务规则的演进历史 -
聚合边界 = 微服务边界:如果未来需要拆分微服务,聚合边界就是天然的服务边界。在 Spec 阶段就做好聚合划分,为未来的架构演进打好基础
相关资源与延伸阅读
- Spec-Driven Development: Building Software in the AI Era — SDD 方法论的系统介绍,涵盖从概念到实践的完整指南
- Why Spec-Driven Development Will Shape Elite Engineers — 分析 SDD 如何改变工程师角色,从代码编写者到规约架构师
- Kiro: AWS’s Spec-Driven Agentic IDE — Kiro IDE 的深度分析,包括 Spec 工作流和 Hooks 机制
- How to Enforce Architectural Patterns When AI Generates Your Code — 当 AI 生成代码时如何维护架构一致性,包括 Repository 模式违规的实际案例
- Domain-Driven Design Reference — Eric Evans 的 DDD 参考手册,领域建模的权威资源
- Spec-Driven Development for Tech Companies — SDD 在企业团队中的实践,包括将交付时间从 6 个月缩短到 6 周的案例
- Prisma ORM Documentation — Prisma ORM 官方文档,TypeScript 后端领域模型层的首选工具
- Patterns of Enterprise Application Architecture — Martin Fowler 的企业应用架构模式目录,Repository、Service Layer、Unit of Work 等模式的权威参考
参考来源
- Spec-Driven Development: Building Software in the AI Era (2026-01)
- Why Spec-Driven Development Will Shape Elite Engineers (2025-12)
- Spec-Driven Development Explained (2025-10)
- AWS Backs Spec-Driven Dev with Kiro (2025-07)
- AWS Launches Kiro, A Specification-Driven Agentic IDE (2025-07)
- Pro AI Coding Workflow: Spec-Driven & Agentic Development (2026-01)
- How to Enforce Architectural Patterns When AI Generates Your Code (2025-10)
- Enhancing Domain-Driven Design with Generative AI (2023-06)
- The End of Agile? Introducing Spec Driven Development (2026-02)
- Spec-Driven Development with AI: Complete 2025 Guide (2025)
📖 返回 总览与导航 | 上一节:28b-API设计与生成 | 下一节:28d-AI辅助认证实现