这本前言解决什么问题
序讲的是“为什么写”,前言讲的是“怎么用”。具体包括:
- arch-lab 到底是什么,覆盖哪些范围,不覆盖哪些;
- 书中代码按什么标准分级,读者需要跑到哪一级;
- 代码块里的标记是什么意思,怎么抽取、怎么验证;
- 领域模型、状态机和 API 的边界在哪里;
- 版本、环境和数字的口径是什么。
如果只想尽快开始读正文,可以先跳到 1.1,遇到代码标记再回来查。但如果你打算把书里的代码真正跑起来,建议先花十分钟读完这篇。
arch-lab 是什么
arch-lab 是一个订单交易系统,用 Go 1.26 编写,围绕订单、库存、支付三个核心概念展开。它的目标不是“做一个能上线的电商系统”,而是提供一个足够小、又能承载完整架构决策的样本。
它包含:
- 一个订单聚合,带显式状态机和版本号;
- 一个库存预留模型,支持预留、释放和超时回收;
- 一个支付模型,支持支付成功、失败和退款;
- 一组端口接口,隔离存储、库存和支付实现;
- 内存与 PostgreSQL 两套适配器;
- 一个基于 outbox 的领域事件发布链路;
- 一组可执行的依赖、契约和风险检查工具。
它不包含:
- 用户注册、登录、找回密码等身份功能;
- 商品管理、购物车、优惠券、发票;
- 物流轨迹、售后工单、客服系统;
- 多租户、国际化、复杂权限模型;
- 生产级的监控、告警、灰度平台。
这些不是“不重要”,而是与卷一的核心问题(边界、决策、可验证性)关系较弱。卷四会把平台工程和治理单独展开。
目录结构
arch-lab 的权威源码在书稿代码块里,不在仓库根目录。抽取后的结构如下:
archlab/
go.mod module archlab,go 1.26.0
cmd/
orderd/main.go 装配适配器、启动 HTTP 服务
loadgen/main.go 卷二使用的压测器
internal/
order/ 聚合、状态机、领域事件
catalog/ SKU 与价格
inventory/ 库存预留与释放
payment/ 支付状态与退款
ports/ 仓储、库存、支付、事件接口
adapters/
memory/ 内存实现,用于 L1/L2
postgres/ PostgreSQL 实现,用于 L3
platform/
config/ 配置加载与校验
httpserver/ 路由、中间件、错误映射
idempotency/ 幂等键与去重窗口
migrations/ PostgreSQL schema
testdata/ 固定场景数据
tools/
deplint/ 包依赖方向检查
riskreg/ 风险台账校验
deploy/
docker-compose.yml PostgreSQL 18.4
这个结构对应 1.3 的模块化单体决策:cmd 负责装配,internal 按业务能力分模块,adapters 实现 ports,platform 提供与业务无关的基础设施。
版本与环境
书中所有“实测”结论都基于下面这套环境。版本不同,结果可能不同,尤其是性能数字。
| 组件 | 版本 | 用途 |
|---|---|---|
| Go | 1.26.0 | 全书主线代码 |
| PostgreSQL | 18.4 | L3 持久化与事务验证 |
| Docker | 29.7.2 | 数据库与 Java 21 容器 |
| Java | 21(Temurin 21.0.13;镜像不可达时用同版本官方包) | 跨语言对照 |
| Hugo | 0.163.3+extended | 书稿构建 |
| 操作系统 | macOS darwin/arm64 | 本机测量环境 |
几条口径:
- 本机默认 Java 不是 21,所有 Java 21 结论必须通过容器运行,先确认
java -version; - 没有在容器里跑过的 Java 结论,写成“预期行为”,不写“实测”;
- PostgreSQL 相关结论必须注明是否使用 Docker、是否首次启动、是否有预热;
- 性能数字必须附命令、并发模型、数据规模和失败率,单次运行不包装成稳定结论。
四级可运行标准
不是每段代码都值得启动数据库。书里把可运行性分成四级,读者可以按需选择验证深度。
L1:纯逻辑或单包
不依赖外部进程,只依赖标准库和项目内包。
验收:
gofmt -l .
go vet ./...
go test ./...
并发代码再加 go test -race ./...。L1 是卷一的默认级别,大部分领域模型、状态机、校验器、端口契约都属于这一级。
L2:进程内端到端
使用 httptest 或 127.0.0.1:0 启动真实 HTTP 服务,适配器用内存实现,不连数据库。
验收:
go test -run TestOrderFlow -v ./...
一个脚本要完成创建、查询、取消三条断言,并检查状态迁移和错误码。第 12 章至少有一个 L2 场景。
L3:真实依赖
使用 Docker Compose 启动 PostgreSQL 18.4,验证事务、条件更新、outbox 和迁移脚本。
验收:
docker compose -f deploy/docker-compose.yml up -d
go test -tags=integration ./...
只承诺实际跑过的场景。没有跑过的数据库行为,写成“预期”或“需要验证”,不写成结论。
L4:多节点与故障注入
多实例、网络分区、主备切换、消息重复与乱序。卷一不承诺 L4,相关内容留给卷三和卷四。书中出现 L4 场景时,会明确标注“需真实环境”,不伪造结果。
代码标记与抽取
书里的 Go 代码块用三行注释标记来源:
// file: internal/order/order.go
// tier: L1
// verify: go test ./internal/order/
package order
三个字段的含义:
| 字段 | 含义 | 约束 |
|---|---|---|
file | 抽取后的相对路径 | 必须在 arch-lab 目录白名单内,不含 .. |
tier | 运行级别 | 只能是 L1/L2/L3/L4 |
verify | 验证命令 | 必须能在抽取后的模块里执行 |
抽取脚本会按文章分目录,把代码块写入独立模块,避免不同文章的包名和类型互相冲突。这样做的原因是:书稿不是一个大仓库,每篇文章的代码需要能单独编译,读者不必先理解全部上下文。
一个代码块只应包含一个完整文件。不要把同一个文件拆成“上半段/下半段”两个代码块,也不要用“(续)”之类的标记;抽取器会把它当成非法路径。如果确实需要多个文件,就给每个文件一个合法的 file 路径。
领域模型
arch-lab 的核心类型如下:
type Order struct {
ID string
CustomerID string
Status Status
Items []OrderItem
TotalCents int64
Currency string
Version int64
CreatedAt time.Time
UpdatedAt time.Time
}
type OrderItem struct {
SKU string
Quantity int
UnitPriceCents int64
}
状态机:
CREATED ──► INVENTORY_RESERVED ──► PAID ──► FULFILLED
│ │ │
└──► CANCELLED ◄──┘ └──► REFUNDED
约束:
- 状态只能通过
TransitionTo修改; - 每次修改递增
Version; - 非法迁移返回
INVALID_STATE,且不修改聚合; - 金额以分为单位,不使用浮点数;
- 订单创建时计算总额,之后不允许直接改
TotalCents。
这些约束会在第 1、3、6 章反复出现。它们不是“建议”,而是测试会检查的规则。
API 边界
卷一确定五个端点,足够支撑订单主流程,不追求完整电商 API:
| 方法 | 路径 | 语义 |
|---|---|---|
| POST | /api/v1/orders | 创建订单,支持 Idempotency-Key |
| GET | /api/v1/orders/{id} | 查询订单,校验归属 |
| POST | /api/v1/orders/{id}/cancel | 取消订单,按状态机校验 |
| POST | /api/v1/orders/{id}/pay | 支付回调,验签后迁移状态 |
| GET | /api/v1/orders?customer_id=&status=&limit= | 列表查询,分页 |
错误响应统一为:
{
"code": "INVALID_STATE",
"message": "order ord-1 cannot transition from CANCELLED to PAID",
"request_id": "req-7f3c"
}
code 是契约,调用方按码分支;message 是给人看的,可以改;request_id 用于排查。第 5 章会展开错误模型和版本演进。
测试数据与场景
为了避免每篇文章都造一套数据,arch-lab 固定三组场景:
| 场景 | 客户 | 商品 | 金额 | 用途 |
|---|---|---|---|---|
| S1 正常下单 | cus-001 | sku-book × 1 | 4999 分 | 创建、预留、支付、履约 |
| S2 库存不足 | cus-002 | sku-limited × 2 | 19998 分 | 预留失败、订单取消 |
| S3 重复回调 | cus-001 | sku-book × 1 | 4999 分 | 幂等、重复支付、对账 |
书里出现具体数字时,优先复用这三组,避免读者在多个示例之间来回对照。需要新场景时,会在当篇明确说明。
怎么验证一篇的代码
以 1.1 的状态机为例,最小验证流程是:
- 从代码块中复制
// file:标记和代码,放到临时模块的对应路径; - 在模块根目录执行
go mod init archlab,写入go 1.26.0; - 执行
gofmt -l .、go vet ./...、go test ./...; - 如果失败,先检查 Go 版本和包路径,再检查代码块是否被截断。
书里的代码块都经过这一步验证,但抽取过程可能有复制误差。发现跑不通时,先对照文章中的完整代码块,而不是直接怀疑结论。
关于数字
书里会出现三类数字,读的时候要分清:
| 类型 | 特征 | 例子 |
|---|---|---|
| 实测值 | 附命令、环境、输出 | “本机 darwin/arm64,go test -bench 输出如下” |
| 目标值 | 设计目标或 SLO | “下单 P99 目标 300ms” |
| 示例参数 | 用于说明配置形状 | “超时 90 秒后释放库存” |
目标值和示例参数不是实测值,不能拿来证明系统性能。反过来,实测值也只代表当次环境,不代表生产容量。卷二会用专门的章节讲怎么设计压测、怎么读结果。
本书不覆盖的内容
- Go 语言语法、标准库细节;
- PostgreSQL 安装、调优、备份恢复操作;
- Kubernetes、服务网格、CI/CD 平台的具体配置;
- 分布式共识、复制协议、分区算法的推导;
- 组织变革、绩效管理、招聘。
这些内容分别在语言书、数据库手册、卷三/卷四中处理。卷一只在必要时提到它们,并说明边界。
勘误与反馈
书中的代码、命令和数字都经过本机验证,但环境差异仍然可能导致问题。发现错误时,最有用的反馈是:
- 你执行的完整命令;
- 你的环境(Go/Java/PostgreSQL/Docker 版本、操作系统、架构);
- 完整输出,而不只是“报错了”;
- 你期望的结果和实际结果的差异。
有了这些信息,才能判断是代码问题、环境问题,还是书里的结论需要修正。
小结
arch-lab 是一个小而有边界的订单系统:三个核心聚合、五个 API、一套状态机、两套适配器、一组可执行检查。书里的代码按 L1–L4 分级,L1 是默认,L2 用于端到端,L3 用于真实数据库,L4 留给后续卷。
记住三条使用约定:代码块带 file/tier/verify 标记;每篇文章的代码应能独立编译;数字要分清实测值、目标值和示例参数。剩下的,从 1.1 开始。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。