选择软件架构书籍,先写清当前约束:领域事件的含义和所有者是否明确,服务提交后是否可能重投,路径是固定工作流还是需要模型动态选择工具。模式、微服务、事件驱动和智能体都是候选结构,不能在问题尚未定义时直接当答案。
本文比较四本书的真实职责,并用一个本站原创的“阅读任务完成事件”本地实验说明:进程提交后在确认前退出时,重投、去重、事务边界和事件身份分别解决什么。
软件架构书籍:按约束选择阅读层
| 已知约束 | 阅读层与读物 | 可检查产出 |
|---|---|---|
| 事件含义不清 | DDD:《AI 增强型领域驱动设计》 | 事件名、拥有上下文、状态转换与待核事实 |
| 消息会重投 | 分布式/消息:《构建 AI 时代的分布式系统》与《Go 分布式系统》 | 事件ID、去重键、事务边界、确认时点和重复预期 |
| 路径固定 | 预定义工作流与 DDD 护栏 | 每步输入输出、停止条件和人工检查 |
| 模型动态选工具 | 智能体:《智能体 AI 系统思维》 | 工具权限、状态、评估、预算、停止与人工接管 |
需要先分清教程、项目书和原理书职责时,可参考编程书籍推荐;需要把算法步骤与系统故障区分开,可以参考数据结构与算法阅读路线。
设计模式书籍为什么不能从模式名反推业务
模式应当回答已经命名的问题。例如幂等消费用于处理可重复投递的副作用风险,限界上下文用于维护不同语义模型的边界。当前四本库存书没有可核的完整 GoF 模式教材,因此本篇不以“设计模式全覆盖”推动选书。
Martin Fowler 的 Bounded Context 说明,不同上下文可以对共享概念使用不同模型,并通过显式映射建立关系。这支持“先确定谁拥有事件语义,再设计集成”,而不是先拆服务或先选消息中间件。
四本架构设计书籍分别解决什么
领域语义与人类接受:《AI 增强型领域驱动设计》
《AI 增强型领域驱动设计》由 Alberto Acerbis、Alessandro Colla 合著,面向已经熟悉限界上下文、聚合和领域事件的开发者、架构师与技术负责人。目录覆盖领域探索、规则、上下文、护栏智能体、工件/消息协议、合法状态转换和人工关卡。
如果“阅读任务完成”由谁定义、何时成立、谁能接受仍不清楚,这本书适合先整理领域语义。AI 生成的事件名、命令和边界图只能作为候选,不能替代客户业务事实或人类接受。
分布式系统书籍的层次比较:《构建 AI 时代的分布式系统》
Max Hopei 的《构建 AI 时代的分布式系统》目录覆盖微服务、消息驱动、流式、智能体、可观测性、面向 AI 的接口以及团队与隐私。第 2 章表达作者偏向微服务起步的观点,也把团队、规模、部署和领域边界列为条件。
它适合已有规模、团队或部署约束,想比较多种架构层次的读者。作者偏好不是无条件最佳实践;单体、模块化系统、消息驱动或微服务都应回到当前边界、故障和组织能力判断。
已有 Go 服务后的实现参考:《Go 分布式系统》
Joel Bryan Juliano 的《Go 分布式系统》要求读者会写 Go、HTTP 与数据库服务,并熟悉函数、结构体、接口和错误处理。目录覆盖并发、队列、HTTP、认证、数据库、Kafka、gRPC、一致性、可观测性、测试和配置。
如果约束已经落到服务调用、重试、幂等、故障和观测,可以按章查阅。它不是没有 Go 服务基础的架构入门,也不证明第三方依赖或生产集群已经通过验证。
动态工具循环:《智能体 AI 系统思维》
Ediz Najim 的《智能体 AI 系统思维:软件架构师指南》讨论受控执行循环、工具、规划与停止、记忆、架构边界、可靠性、安全、评估和可观测性。它适合确实需要模型根据状态动态选择工具或反复规划的系统。
Anthropic 2024 年文章把工作流定义为预设代码路径协调模型和工具,把智能体定义为模型动态决定过程和工具使用,并建议先采用最简单方案、只在需要时增加复杂性。固定路径能解决问题时,可以先用确定工作流。
本地崩溃与重投实验:事件边界怎样暴露
实验使用虚构的“阅读任务完成”事件、本地 Python 3.13.9 和 SQLite 3.51.0。worker 在同一 SQLite 事务提交并关闭连接后,以 os._exit(70) 退出;stdout 已打印 applied: committed,但没有打印本地 ack 标记。测试 harness 随后手动再次调用同一 consumer/event,模拟确认前退出后的重投。

| 场景 | 实际状态 | 解释 |
|---|---|---|
| 普通计数重投 | 崩溃后 1,重投后 2 | 相同事件再次产生副作用 |
| 同库事务去重 | 崩溃后 1,重投后仍 1 | receipt 与 counter 同事务,重复被抑制 |
| 新事件 | 计数增加到 2 | 新 event ID 被视为新输入 |
| 同ID改任务 | ValueError,回滚,无 ack,状态不变 | 稳定 ID 必须对应稳定 task 身份 |
| 另一消费者 | 同事件独立计数为 1 | 去重键作用域包含 consumer |
共运行 7 个实际子进程,覆盖 5 个场景。去重表的复合主键是 (consumer, event_id),并保存对应 task_id。稳定 event ID 和重投期间 receipt 仍然存在是模型前提;同一业务动作若换了 event ID,本策略不会识别为重复。
查看本站原创事件 worker 源码
"""本站原创:本地进程崩溃与重投模型,不是真实消息代理或分布式事务。"""
import argparse
import os
import sqlite3
def apply_event(database, strategy, consumer, event_id, task_id):
db = sqlite3.connect(database, isolation_level=None, timeout=5)
try:
db.executescript("""
CREATE TABLE IF NOT EXISTS demo_counters (
consumer TEXT PRIMARY KEY, value INTEGER NOT NULL);
CREATE TABLE IF NOT EXISTS demo_receipts (
consumer TEXT NOT NULL, event_id TEXT NOT NULL, task_id TEXT NOT NULL,
PRIMARY KEY (consumer, event_id));
""")
db.execute("BEGIN IMMEDIATE")
db.execute("INSERT OR IGNORE INTO demo_counters VALUES (?, 0)", (consumer,))
previous = None
if strategy == "dedup":
previous = db.execute(
"SELECT task_id FROM demo_receipts WHERE consumer=? AND event_id=?",
(consumer, event_id)).fetchone()
if previous is not None:
if previous[0] != task_id:
raise ValueError("same event ID carries a different task")
outcome = "duplicate_suppressed"
else:
if strategy == "dedup":
db.execute("INSERT INTO demo_receipts VALUES (?, ?, ?)",
(consumer, event_id, task_id))
db.execute("UPDATE demo_counters SET value=value+1 WHERE consumer=?", (consumer,))
outcome = "applied"
db.execute("COMMIT")
return outcome
except Exception:
if db.in_transaction:
db.execute("ROLLBACK")
raise
finally:
db.close()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("database")
parser.add_argument("strategy", choices=["naive", "dedup"])
parser.add_argument("consumer")
parser.add_argument("event_id")
parser.add_argument("task_id")
parser.add_argument("--crash-after-commit", action="store_true")
args = parser.parse_args()
result = apply_event(args.database, args.strategy, args.consumer, args.event_id, args.task_id)
print(result + ": committed", flush=True)
if args.crash_after_commit:
os._exit(70) # Actual child-process exit before the modeled acknowledgement.
print("ack", flush=True) # A local marker, not a real broker acknowledgement.
确认、重投与幂等分别在哪一层
RabbitMQ 官方 Reliability Guide 源文档区分消费者确认与发布者确认,并说明故障和重发可能产生重复,消费者宜采用幂等处理。redelivered 表示消费者可能见过该投递,置位也不保证它已经处理过;这与本例自行约定的业务 event_id 是不同信息。本文实验没有使用 RabbitMQ:ack 只是 stdout 标记,重投由 harness 手动调用。
同库事务只覆盖 receipt 与 counter 的原子更新,没有测试网络确认、broker、分区、断电、并发压力、跨数据库或外部副作用。receipt 保留期也没有测试。因此结果不能推出端到端 exactly-once 或生产可靠性。
系统设计阅读路线:从事件问题回到书
- 事件含义或拥有者不清:读 DDD,先确认上下文、命令、事件和接受者。
- 提交、确认、重试与去重不清:读分布式/消息章节,画出事务和副作用边界。
- 路径固定且可检查:先用工作流,记录每步输入输出、失败和人工关卡。
- 模型必须动态选工具:再读智能体架构,增加状态、权限、评估、停止和接管。
本文于 2026 年 11 月 10 日核对资料。书目事实来自四本授权 EPUB 的扉页、版权或元数据、前言、目录及必要章节,未完整通读,也未验证书内系统。原创实验只使用虚构事件、本地子进程和 SQLite 文件,不包含真实用户、消息代理、网络、订单或生产数据。







