跳转至

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 模块设计 —— 接下来看通用模块库如何设计,让业务仓能"搭积木"式组装资源。

评论