这套书的四卷
《互联网产品的客户端架构》计划四卷,每卷 45 篇,按客户端能力由内向外展开:
| 卷 | 主题 | 关注点 |
|---|---|---|
| 卷一 | 端形态与体验基础 | 端上约束、分层、状态、路由、渲染、降级、观测 |
| 卷二 | 内容、社交与实时互动 | 信息流、富文本、上传、互动、实时连接、关系、通知 |
| 卷三 | 交易、增长与平台化 | 商品、购物车、下单、支付、对账、优惠、会员、增长、实验、埋点 |
| 卷四 | 规模化与架构演进 | 多端、插件化、性能治理、组织与演进 |
四卷不是四个独立专题,而是一条线:卷一定结构,卷二让它流动,卷三让它可信,卷四让它可持续。
本卷是第三卷。它假设前两卷的结构已经存在,不重复讲分层与状态归属,只讲交易场景下这些结构如何收紧。
四卷为什么这样分
分卷的依据不是「功能模块」,而是「承诺强度」:
- 卷一处理的是结构承诺:页面能不能稳定渲染、状态会不会串、路由恢复后是不是同一个页面。错了可以修一个版本。
- 卷二处理的是流动承诺:消息能不能到、到了会不会重复、断线后能不能补。错了用户能感知,但大多可恢复。
- 卷三处理的是事实承诺:钱有没有扣、订单是不是唯一、退款到没到。错了要人工介入,甚至不可逆。
- 卷四处理的是长期承诺:多端能不能一致、架构能不能演进、团队能不能维护。错了是慢性成本。
承诺越强,容错空间越小,对所有权与验证的要求越高。这也是为什么卷三反复讲「谁写的」——它不是抽象原则,而是承诺强度的直接结果。
这一卷的十三章
全卷 13 章 39 节:
- 交易端架构与 trade-lab:四条链路的失败边界、金额与状态的所有权、三级验证。
- 商品与库存展示:价格、库存、时效三份数据如何拼成一个可信的商品。
- 购物车与价格计算:本地车与服务端车如何合并,端上能算到哪一步。
- 下单与订单状态:幂等键、状态机、待支付超时。
- 支付流程与回调:拉起、轮询、回跳的编排,结果不确定时的状态机。
- 支付对账与重复支付:端上如何避免重复付款,退款状态怎么跟。
- 优惠券与营销规则:可用性判定、叠加规则、展示的诚实性。
- 会员、积分与权益:会员价、积分账本、权益到期。
- 分享、拉新与增长链路:分享参数、邀请绑定、活动页降级。
- A/B 实验与远端配置:分流、曝光、配置缓存、实验污染。
- 客服、售后与工单:上下文携带、证据留存、离线恢复。
- 数据埋点与增长分析:事件契约、上报队列、漏斗口径。
- 卷三收口:端到端流程、故障注入、质量场景。
外加序、本前言、目录篇,以及三篇附录:状态与金额速查、问题排查手册、评审清单。
写作顺序
这一卷的章节不是按「页面」排的,而是按承诺强度排的。写作时也遵循同样的顺序:
- 先定义模型(金额、订单、所有权)。
- 再写展示与预演(商品、购物车、优惠)。
- 然后进入不可逆动作(下单、支付、退款)。
- 最后写围绕交易的增长与支撑(会员、实验、客服、埋点)。
- 用端到端把所有模块串起来。
这个顺序有一个好处:每写一章,前面的模块都已经可运行,后面的例子可以直接调用,不需要「假设有一个服务端」。
贯穿案例与实验工程
贯穿案例是内容社区「回声」长出的轻交易:创作者上架周边,用户加购、下单、支付、退款,平台提供优惠券、会员与活动。
实验工程叫 trade-lab,与卷二的 social-client-lab 同构:
trade-lab/
src/core/ # 金额、订单模型、所有权、分级
src/product/ # 商品快照、SKU、库存
src/cart/ # 购物车合并、价格明细、勾选
src/order/ # 提交、状态机、超时
src/pay/ # 支付编排、结果查询、回调
src/reconcile/ # 对账、退款
src/coupon/ # 券判定、叠加、展示
src/member/ # 会员、积分、权益
src/growth/ # 分享、邀请、活动
src/experiment/ # 分流、配置、互斥
src/support/ # 客服、售后、工单
src/analytics/ # 埋点契约、上报、漏斗
src/summary/ # 端到端、故障注入、质量场景
不需要 npm install,不需要 tsc。Node 26 直接运行 .ts,node --test 是唯一的测试入口。
为什么用 Node 26 与零依赖
三个原因:
- 可复现。 没有依赖锁、没有构建产物、没有版本漂移,任何一台装了 Node 26 的机器跑出的结果都一致。
- 聚焦。 交易端的问题在状态与时序,不在框架配置。去掉构建链,读者可以把注意力放在代码本身。
- 能跑真端口。
node:http足够起一个本地回调服务,L3 不需要外部依赖。
代价是代码看起来「原始」:没有装饰器、没有依赖注入、没有 ORM。这是有意的——这一卷要验证的是判断,不是工程化。
三级可运行性
交易端的验证分三级,依据不是技术难度,而是错误会不会造成资金或状态事实错误:
- L1:纯函数。金额计算、状态迁移、规则判定。毫秒级反馈,占全部用例的七成以上。
- L2:进程内服务端加假时钟。验证提交、轮询、超时、取消。
- L3:本地 HTTP 回调。验证支付结果确认、重复通知、回调与轮询的时序竞争。
支付链路必须有 L3。只有 L2 的支付测试,抓不住「回调晚于超时」这类问题。
每节的代码块头三行会标明文件路径、级别与验证命令:
// file: src/order/submit.ts
// tier: L2
// verify: node --test test/order/submit.test.ts
这三行不是装饰。主代理会用脚本从正文反向抽取代码,与工程文件逐字节比对;对不上就说明正文与代码不同步。
代码约定
为了让例子可以在 Node 里直接跑,代码遵循几条约定:
- 用 TypeScript 的类型与
type联合,不用enum、namespace、参数属性——Node 的类型剥离不支持这些语法。 - 本地导入带
.ts后缀,例如from "./trade-model.ts"。 - 金额一律用整数分,展示时才转成元。
- 时间通过参数注入,业务逻辑里不直接调用
Date.now();测试里用假时钟推进。 - 不引入第三方依赖。HTTP 服务用
node:http,断言用node:assert,测试用node:test。
这些约定会让代码看起来比生产代码「朴素」,但换来的是可复现:任何一台装了 Node 26 的机器都能跑出相同结果。
错误处理口径
交易端的错误分四类,处理方式不同:
| 类型 | 例子 | 端上动作 |
|---|---|---|
| 用户输入错误 | 地址缺门牌号、数量超限购 | 就地提示,不发请求 |
| 业务拒绝 | 库存不足、券不可用、风控拦截 | 展示服务端原因,提供替代方案 |
| 网络错误 | 超时、断网、5xx | 标记「未知」,提供查询或重试 |
| 状态冲突 | 订单已取消、支付已完成 | 以服务端状态为准,刷新界面 |
第三类是交易端最容易被写错的。很多实现把它归入「业务拒绝」,于是用户看到「支付失败」并再次支付。正确的做法是把它标成「结果未知」,先查询再决定。
读者对象
这一卷写给三类人:
客户端工程师。 你正在或即将负责交易模块,需要知道哪些逻辑必须放服务端、哪些失败必须查询、哪些重试必须幂等。代码与测试可以直接作为实现参考。
产品与设计。 你需要判断一个方案在哪些地方可以给即时反馈、哪些地方必须等待、哪些文案不能说满。每节的「关键决策」与「失败复盘」比代码更有用。
技术负责人。 你需要评审交易方案,问出正确的问题。附录 C 的清单可以带进评审会,重点看「未验证」那一栏。
不需要读者有支付渠道接入经验,但需要了解基本的 HTTP 请求、状态机与单元测试。
章末结构
每节的结构是固定的:
- 问题场景与为什么难。
- 关键决策(通常是表格或分点)。
- 可运行代码。
- 真实测试输出。
- 一次失败复盘。
- 小结。
- 阅读导航。
- 常见错误与边界。
读第二遍时,可以只看第 2、5、8 部分——它们分别是判断、证据与边界。
怎么自检是否读懂
每节末尾有「常见错误与边界」。读完之后,可以试着回答三个问题:
- 这一节的核心判断是什么?用一句话说出来。
- 这个判断在什么条件下不成立?
- 如果不用这一节的做法,会出现什么具体故障?
如果三个问题都能答,说明读懂了。如果只能答第一个,说明记住了结论但没理解边界。
每章的测试也是自检工具:先跑测试,读用例名,再读正文。用例名通常就是这一章的小结。
练习建议
书里没有习题,但每节都留了可以动手的地方:
- 把某一节的函数改错一个边界条件,看哪条测试会失败。
- 给某一节加一条用例,覆盖它没有覆盖的失败路径。
- 把某一节的 L1 逻辑改成 L2,观察需要补哪些时序断言。
- 找一个你熟悉的交易应用,对照某一节检查它的实现。
第三种练习最有价值。很多人以为自己会写状态机,直到要把「超时」和「回调」放进同一个状态机里。
反馈与勘误
书中的代码与输出都来自本机实际运行。如果你在别的 Node 版本上跑出不同结果,先确认版本是 26;如果版本一致但结果不同,很可能是代码在正文与工程之间不同步,可以用校验脚本比对。
勘误优先修正三类问题:结论错误、代码不能运行、示例与正文矛盾。措辞与排版问题会合并处理。
关于版本与日期
书中的实验使用 Node v26.5.0,Hugo 站点构建使用 v0.163.3+extended。两者都写在对应的命令里,便于复现。
代码不依赖任何第三方包,因此没有 package.json 与锁文件。Node 版本是唯一的外部约束;低于 23 的版本不支持默认类型剥离,需要额外参数。
正文里的日期与订单号都是构造数据。示例中的「2026 年」只是为了让时间线自洽,不代表任何真实上线时间。
小结
这一卷的读法是:先看模型,再看展示,然后进入不可逆动作,最后收口。实验工程与正文一一对应,测试是结论,代码块可以反抽比对。
十三章不必一次读完,但第 1、4、5、6 章建议连读,它们构成交易端的主干。
如果时间有限,先读第 1 章与第 5 章:前者定所有权,后者定「不确定」怎么处理。这两章读懂了,剩下的章节可以按需查阅。
第 4 章讲幂等,第 6 章讲对账,它们与第 5 章构成支付链路的三段。只读到第 5 章会以为「查询」就解决了问题,其实查询之后还要有对账兜底。
如果只关心售后与客服,可以从第 4、6、11 章读起,再回看第 1 章的所有权规则。
如果只关心增长与实验,第 9、10、12 章可以单独读,但请先看完第 1 章的所有权与第 3 章的价格明细。
从目录与学习路径 进入,或直接跳到1.1 交易端的四条链路 。
常见错误与边界
- 跳过第 1 章直接看支付,所有权没定清楚,后面的幂等与对账都会歪。
- 把正文代码直接复制进生产,忽略它省略了鉴权、日志与重试框架。
- 用 Node 20 或更早版本运行实验,类型剥离不可用。
- 把 L1 测试当成支付的完整验证,忽略回调与时序。
- 只看「常见错误」不看正文,把边界当成结论。
- 把「结果未知」当成「失败」,引导用户重复支付。
- 忽略测试名,只读代码,错过每节最重要的结论。
- 把附录当正文读,附录只用于快速定位。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。