Ch 23 业务仓库设计与同构模式¶
面包屑
本书主页 › Part IV 基础设施与工程效能 › Ch 23
项目第 1 年 · 核心建设期——业务仓同构
本章你将学到¶
- 业务 IaC 仓的同构目录结构设计
- 为什么刻意保持结构同构:CI 复用/模板复制/人员轮岗
- 始祖仓的遗产:新旧标准混合的治理债与拆分决策
- monorepo vs polyrepo 的 IaC 治理对比
23.1 业务 IaC 仓的同构目录结构设计¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
subgraph 同构结构["所有业务仓的统一目录结构"]
TF[terraform/]
TF --> ETL[regional/etl/<br/>Glue Job 资源]
TF --> ORCH[regional/orchestration/<br/>Step Functions 资源]
TF --> DDB[regional/dynamodb/<br/>DynamoDB 配置表]
TF --> ENV[environments/<br/>dev / qa / prod]
ENV --> ENV_TF["env-all.tfvars"]
ENV --> ENV_BE["env.tfbackend"]
CI[.github/workflows/<br/>CI 文件(所有仓相同)]
end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class CI,DDB,ENV,ENV_BE,ENV_TF,ETL,ORCH,TF bpProcess
linkStyle default stroke:#697077,stroke-width:2px
图 23-1 业务 IaC 仓的同构目录结构设计
| 目录 | 内容 | 同构性 |
|---|---|---|
terraform/regional/etl/ |
Glue Job 资源定义 | 结构相同,内容不同(Job 数量/配置) |
terraform/regional/orchestration/ |
Step Functions 资源 | 结构相同 |
terraform/regional/dynamodb/ |
DynamoDB 配置表 | 结构相同 |
terraform/environments/{dev,qa,prod}/ |
环境级 tfvars + tfbackend | 结构相同 |
.github/workflows/ |
CI 流程定义 | 完全相同(调用同一 reusable workflow) |
表 23-1 业务 IaC 仓的同构目录结构设计
这个目录结构不是我一开始就定好的,而是在项目前三个月"边建边痛"逐步固化的。第一个业务仓(domain-a)的目录是开发者随手建的——glue/ 放 Job、sf/ 放 Step Functions、config/ 放 tfvars,命名随意。到第二个业务仓(domain-b)时,另一个开发者建了完全不同的目录——etl//orchestration//environments/。到第三个仓时我已经晕了——每个仓结构不同,CI 脚本不能复用,排障时要先搞懂"这个仓的目录是什么风格"。于是我停下所有开发,花了一天把三个仓统一成同一个目录结构——就是图 23-1 这个版本。同构不是"设计"出来的,是"痛"出来的——当你被异构折磨够了,自然会走向同构(M4 同构仓库模式的实践起源)。
23.2 为什么刻意保持结构同构¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
subgraph 同构的价值["结构同构的四大价值"]
V1[ CI 复用<br/>一份 workflow 服务所有仓]
V2[ 模板复制<br/>新业务仓从模板秒级创建]
V3[ 人员轮岗<br/>熟悉一个仓即可操作所有仓]
V4[ 工具通用<br/>脚本/工具跨仓复用]
end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class V1,V2,V3,V4 bpProcess
linkStyle default stroke:#697077,stroke-width:2px
图 23-2 为什么刻意保持结构同构
| 价值 | 说明 |
|---|---|
| CI 复用 | 所有仓调用同一个 reusable workflow,CI 逻辑只维护一份 |
| 模板复制 | 新建业务仓 = cp -r template/ new-domain/,改 domain 名即可 |
| 人员轮岗 | 工程师从 domain-a 调到 domain-b,无需重新学习仓库结构 |
| 工具通用 | 排障脚本、变更检测脚本跨仓通用 |
表 23-2 为什么刻意保持结构同构
Trade-off
同构的代价是"不够灵活"——某些业务域可能有特殊需求,同构结构可能不完全适配。应对策略是"同构为主、特例标注"——绝大多数仓严格同构,极少数特例在 README 中明确标注差异。同构的收益(可维护性)远大于特例的收益(灵活性)。
这四大价值里,"CI 复用"是我在同构后感受最深的。异构时每个仓的 CI 要单独写——三个仓三份 CI 脚本,改一个通用步骤(如加安全扫描)要改三处。同构后所有仓调用同一个 reusable workflow(见 Ch 27),改一处全局生效。到第二年业务仓从 3 个涨到 6 个时,这个"一处改全局生效"的价值兑现了——加一个新的安全检查步骤,6 个仓同时受益,零额外工作。同构的本质是用"结构一致性"换"运维规模化"——仓越多,同构的复利越大(M4 同构仓库模式)。
23.3 始祖仓的遗产:新旧标准混合的治理债与拆分决策¶
始祖仓的演化¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
subgraph 演化历程["仓库演化历程"]
T1@{ icon: "logos:aws-step-functions", form: "rounded", label: 初期:单一始祖仓<br/>所有业务线混在一起<br/>100+ Step Functions, pos: "b", h: 40 }
T2[中期:标准演进<br/>新框架/新标准出现<br/>始祖仓混入新旧代码]
T3[后期:拆分<br/>新业务线独立建仓<br/>始祖仓只承载历史任务]
end
T1 --> T2 --> T3
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class T1,T2,T3 bpProcess
linkStyle default stroke:#697077,stroke-width:2px
图 23-3 始祖仓的演化
治理债的形成¶
| 问题 | 原因 | 影响 |
|---|---|---|
| 新旧标准混合 | 始祖仓承载了 v1 和 v2 两代框架 | 新人困惑,不知道该遵循哪套标准 |
| 旧代码不再维护 | 大量旧 connector/state machine 仍在跑 | 不敢改、不敢删,维护成本高 |
| 仓库过大 | 100+ 资源定义在一个仓 | CI 慢、 PR review 困难 |
表 23-3 治理债的形成
拆分决策¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
ANCESTOR[始祖仓<br/>100+ SF / 混合新旧标准] --> SPLIT[拆分决策]
SPLIT --> NEW1[business-domain-a<br/>新标准]
SPLIT --> NEW2[business-domain-b<br/>新标准]
SPLIT --> NEW3[business-domain-c<br/>新标准]
SPLIT --> KEEP[始祖仓<br/>冻结新增,仅维护历史任务]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class ANCESTOR,KEEP,NEW1,NEW2,NEW3,SPLIT bpProcess
linkStyle default stroke:#697077,stroke-width:2px
图 23-4 拆分决策
引申
始祖仓的治理债是"快速迭代"的代价——初期为了快速上线,所有东西放一个仓;随着业务增长,这个仓变成了"垃圾场"。拆分是正确的决策,但代价是"历史任务留在始祖仓,长期需要维护两套标准"。更好的做法是"从第一天就按业务域拆仓"——哪怕初期只有 2-3 个任务,也建独立仓。这就是同构模式的预防价值。
23.4 引申:monorepo vs polyrepo 的 IaC 治理对比¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
subgraph 两种模式["两种 IaC 仓库模式"]
MONO[Monorepo<br/>所有 IaC 在一个仓<br/>目录划分业务域]
POLY[Polyrepo<br/>每个业务域独立仓<br/>本书方案]
end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
class MONO,POLY bpProcess
linkStyle default stroke:#697077,stroke-width:2px
图 23-5 引申:monorepo vs polyrepo 的 IaC 治理对比
| 维度 | Monorepo | Polyrepo(本书) |
|---|---|---|
| CI | 一次 CI 覆盖所有(慢但全面) | 每仓独立 CI(快但需复用) |
| 权限 | 粗粒度(全有或全无) | 细粒度(按仓授权) |
| 变更协调 | 跨域变更一个 PR | 跨域变更需多仓协调 |
| 发布节奏 | 统一节奏 | 各域独立节奏 |
| 新人上手 | 需理解整体结构 | 只需理解一个仓 |
| 适合规模 | 中小型、域间耦合强 | 中大型、域间解耦 |
表 23-4 引申:monorepo vs polyrepo 的 IaC 治理对比
Trade-off
本书选 polyrepo 的核心理由是"IaC 与运行时代码发布节奏不同"—— Terraform 需 plan/apply 审批,Glue 脚本只需 S3 上传。混在 monorepo 会让 CI 极度复杂。但如果团队小、域间耦合强,monorepo 的协调成本更低。没有银弹——按团队规模和耦合度选择。
我在选 polyrepo 时也有过犹豫——monorepo 的"一个 PR 改多仓"确实方便跨域变更。但最终选 polyrepo 的决定性因素是权限隔离——Aurora 有 6 个业务域团队,每个团队只该改自己的 IaC。monorepo 的权限是"全有或全无"——要么所有人都能改所有域(安全风险),要么用 CODEOWNERS 做目录级控制(复杂且易配错)。polyrepo 天然按仓授权——domain-a 团队只有 domain-a 仓的写权限,碰不到 domain-b。在医药合规场景下,这个权限隔离是刚需(M10 合规要求"变更可归属"——谁的改动必须可追溯,polyrepo 的按仓授权让追溯天然清晰)。polyrepo 的权限隔离在合规场景下比 monorepo 的便利性更重要。
本章小结¶
- 业务 IaC 仓采用同构目录结构:etl/orchestration/dynamodb + environments/{dev,qa,prod} + 统一 CI
- 同构四大价值:CI 复用 / 模板复制 / 人员轮岗 / 工具通用——收益远大于特例灵活性
- 始祖仓的治理债:新旧标准混合、旧代码不维护、仓库过大——拆分是正确决策但遗留两套标准
- monorepo vs polyrepo:按团队规模和耦合度选择,本书选 polyrepo 因 IaC 与运行时代码发布节奏不同
下一章
Ch 24 通用 Terraform 模块设计 —— 接下来看通用模块库如何设计,让业务仓能"搭积木"式组装资源。