Monolith Modularization (Deep Workflow)
Modularize before microservice splintering when possible: clear internal APIs and data boundaries lower risk than distributed rewrites.
When to Offer This Workflow
Trigger conditions:
- - Tangled code; long build/test times; fear of changes
- Preparing for extract services later
- Team parallelism blocked by merge conflicts
Initial offer:
Use six stages: (1) map current state, (2) define target modules, (3) enforce boundaries, (4) migrate data & calls incrementally, (5) extract candidates, (6) validate & iterate. Confirm language ecosystem (Java modules, packages, etc.).
Stage 1: Map Current State
Goal: Dependency graph and pain hotspots.
Activities
- - Categorize by domain (feature areas)
- Identify shared DB tables across features
- Build ownership per directory package
Exit condition: Simple diagram or list of coupling edges.
Stage 2: Define Target Modules
Goal: Bounded contexts as packages or layers with explicit APIs.
Practices
- - Public API surface per module; internal packages hidden
- Dependency rule: feature → core allowed; not feature ↔ feature (eventually)
Exit condition: Rule document (lint or arch tests when possible).
Stage 3: Enforce Boundaries
Goal: Tooling backs intent (ArchUnit, dependency-cruiser, eslint boundaries).
Practices
- - CI fails on new violations (ratchet)
- Gradual allowlist until legacy cleaned
Stage 4: Incremental Migration
Goal: Strangler inside the monolith: new code behind facades.
Patterns
- - Extract interface + impl swap later
- Outbox table for events before Kafka exists
Exit condition: No big-bang rewrite without feature flags.
Stage 5: Extract Candidates
Goal: Choose first service by clear data ownership and low coupling.
Heuristics
- - Stable API already emerged internally
- Different scaling needs or release cadence
Stage 6: Validate & Iterate
Goal: Metrics: build time, test time, defect rate per module.
Practices
- - Retros on boundary pain; adjust modules
Final Review Checklist
- - [ ] Domain map and coupling understood
- [ ] Target module boundaries and public APIs defined
- [ ] Enforcement (lint/tests) in CI where feasible
- [ ] Incremental migration path without big-bang
- [ ] Extraction candidates prioritized with rationale
Tips for Effective Guidance
- - Shared DB rows are the hardest coupling—plan migrations early.
- “Anti-corruption” layer when integrating legacy subdomains.
- Modular monolith often beats premature microservices.
Handling Deviations
- - Rails / Django / Spring: map to packaging and engines / modules specifically.
- Tiny codebase: folder structure + import rules only.
单体模块化(深度工作流)
尽可能在微服务拆分之前进行模块化:清晰的内部API和数据边界比分布式重写的风险更低。
何时提供此工作流
触发条件:
- - 代码纠缠不清;构建/测试时间长;害怕变更
- 为后续服务提取做准备
- 团队并行开发被合并冲突阻塞
初始建议:
使用六个阶段:(1) 映射当前状态,(2) 定义目标模块,(3) 强制边界,(4) 增量迁移数据与调用,(5) 提取候选模块,(6) 验证与迭代。确认语言生态(Java模块、包等)。
阶段1:映射当前状态
目标: 依赖图和痛点分析。
活动
- - 按领域(功能区域)进行分类
- 识别跨功能的共享数据库表
- 为每个目录包建立所有权
退出条件: 简单的耦合关系图或列表。
阶段2:定义目标模块
目标: 将限界上下文作为具有显式API的包或层。
实践
- - 每个模块有公共API表面;内部包隐藏
- 依赖规则:允许功能→核心依赖;不允许功能↔功能(最终目标)
退出条件: 规则文档(尽可能包含lint或架构测试)。
阶段3:强制边界
目标: 工具支持意图(ArchUnit、dependency-cruiser、eslint边界)。
实践
- - CI对新违规失败(棘轮机制)
- 逐步建立允许列表直到遗留代码清理完毕
阶段4:增量迁移
目标: 单体内部的绞杀者模式:新代码放在外观模式之后。
模式
- - 提取接口,后续交换实现
- 在Kafka存在之前使用发件箱表处理事件
退出条件: 没有特性开关就不进行大爆炸式重写。
阶段5:提取候选模块
目标: 根据清晰的数据所有权和低耦合度选择第一个服务。
启发式方法
- - 内部已形成稳定的API
- 存在不同的扩展需求或发布节奏
阶段6:验证与迭代
目标: 指标:每个模块的构建时间、测试时间、缺陷率。
实践
最终审查清单
- - [ ] 理解领域映射和耦合关系
- [ ] 定义目标模块边界和公共API
- [ ] 在可行的情况下在CI中实施强制检查(lint/测试)
- [ ] 增量迁移路径,避免大爆炸式重写
- [ ] 基于理由对提取候选模块进行优先级排序
有效指导技巧
- - 共享数据库行是最难处理的耦合——尽早规划迁移。
- 集成遗留子域时使用“防腐层”。
- 模块化单体通常优于过早的微服务。
处理偏差
- - Rails / Django / Spring:具体映射到打包、引擎/模块。
- 小型代码库:仅使用文件夹结构 + 导入规则。