数环通iPaaS架构设计的结构化与模块化方法论——从高内聚低耦合到工程落地的完整指南

🔔 本文 7000+ 字深度原创,含 10+ 张架构图和三大真实案例场景拆解。创作不易,如果对你有帮助,请点赞 👍 收藏 ⭐ 关注 🔥 三连支持,你的认可是我持续输出的最大动力!


写在前面

做过几年架构的人都会发现一个现象:

系统的复杂度不是被"加"出来的,而是被"搅"出来的。

功能 A 和功能 B 本来是独立的,但因为"方便"被写在同一个类里;模块 C 和模块 D 本来没有依赖,但因为"复用一段代码"产生了隐式耦合;分层本来很清晰,但因为"赶进度"出现了 Controller 直接调 DAO 的捷径。

三个月后回头看,系统变成了一团意大利面------改一处动全身,加一个功能要改八个文件,删一行代码不知道哪里会崩。

这不是技术能力的问题,而是缺乏结构化设计的方法论

本文从"什么是好的模块化"出发,系统拆解结构化设计的核心原则、实操技术和避坑指南,结合 iPaaS 平台的真实案例,讲清楚如何把一个大系统拆得清楚、合得回来、改得动


一、先搞清楚:什么是好的模块化?

1.1 一个直觉测试

给你两个系统,同样 10 万行代码:

系统 A(大泥球) 系统 B(模块化)
1 个项目,300 个类平铺 5 个模块,每个模块 60 个类
任意类可以引用任意类 模块间只通过接口通信
改一行代码影响范围未知 改一个模块只影响自己
新人上手要读全部代码 新人只需理解负责模块

你选哪个?所有人都选 B。但现实中,80% 的系统长成了 A。

1.2 模块化的本质定义

模块化 = 把系统拆成多个相对独立的"积木",每个积木有明确的边界和接口,积木之间通过约定好的方式组合。

三个关键词:

  • 边界(Boundary):每个模块管什么、不管什么,必须明确
  • 接口(Interface):模块对外暴露什么能力,必须稳定
  • 组合(Composition):模块之间如何协作,必须有规则

1.3 模块化的度量:耦合度 + 内聚度

这是评价模块化好坏的两把尺子:
#mermaid-svg-62EwbsJDYeRaUArV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-62EwbsJDYeRaUArV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-62EwbsJDYeRaUArV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-62EwbsJDYeRaUArV .error-icon{fill:#552222;}#mermaid-svg-62EwbsJDYeRaUArV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-62EwbsJDYeRaUArV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-62EwbsJDYeRaUArV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-62EwbsJDYeRaUArV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-62EwbsJDYeRaUArV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-62EwbsJDYeRaUArV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-62EwbsJDYeRaUArV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-62EwbsJDYeRaUArV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-62EwbsJDYeRaUArV .marker.cross{stroke:#333333;}#mermaid-svg-62EwbsJDYeRaUArV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-62EwbsJDYeRaUArV p{margin:0;}#mermaid-svg-62EwbsJDYeRaUArV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-62EwbsJDYeRaUArV .cluster-label text{fill:#333;}#mermaid-svg-62EwbsJDYeRaUArV .cluster-label span{color:#333;}#mermaid-svg-62EwbsJDYeRaUArV .cluster-label span p{background-color:transparent;}#mermaid-svg-62EwbsJDYeRaUArV .label text,#mermaid-svg-62EwbsJDYeRaUArV span{fill:#333;color:#333;}#mermaid-svg-62EwbsJDYeRaUArV .node rect,#mermaid-svg-62EwbsJDYeRaUArV .node circle,#mermaid-svg-62EwbsJDYeRaUArV .node ellipse,#mermaid-svg-62EwbsJDYeRaUArV .node polygon,#mermaid-svg-62EwbsJDYeRaUArV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-62EwbsJDYeRaUArV .rough-node .label text,#mermaid-svg-62EwbsJDYeRaUArV .node .label text,#mermaid-svg-62EwbsJDYeRaUArV .image-shape .label,#mermaid-svg-62EwbsJDYeRaUArV .icon-shape .label{text-anchor:middle;}#mermaid-svg-62EwbsJDYeRaUArV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-62EwbsJDYeRaUArV .rough-node .label,#mermaid-svg-62EwbsJDYeRaUArV .node .label,#mermaid-svg-62EwbsJDYeRaUArV .image-shape .label,#mermaid-svg-62EwbsJDYeRaUArV .icon-shape .label{text-align:center;}#mermaid-svg-62EwbsJDYeRaUArV .node.clickable{cursor:pointer;}#mermaid-svg-62EwbsJDYeRaUArV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-62EwbsJDYeRaUArV .arrowheadPath{fill:#333333;}#mermaid-svg-62EwbsJDYeRaUArV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-62EwbsJDYeRaUArV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-62EwbsJDYeRaUArV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-62EwbsJDYeRaUArV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-62EwbsJDYeRaUArV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-62EwbsJDYeRaUArV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-62EwbsJDYeRaUArV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-62EwbsJDYeRaUArV .cluster text{fill:#333;}#mermaid-svg-62EwbsJDYeRaUArV .cluster span{color:#333;}#mermaid-svg-62EwbsJDYeRaUArV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-62EwbsJDYeRaUArV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-62EwbsJDYeRaUArV rect.text{fill:none;stroke-width:0;}#mermaid-svg-62EwbsJDYeRaUArV .icon-shape,#mermaid-svg-62EwbsJDYeRaUArV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-62EwbsJDYeRaUArV .icon-shape p,#mermaid-svg-62EwbsJDYeRaUArV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-62EwbsJDYeRaUArV .icon-shape .label rect,#mermaid-svg-62EwbsJDYeRaUArV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-62EwbsJDYeRaUArV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-62EwbsJDYeRaUArV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-62EwbsJDYeRaUArV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 坏设计
低内聚

一个模块干了很多不相关的事
高耦合

模块之间互相依赖、互相引用
好设计
高内聚

模块内部紧密协作
低耦合

模块之间松散依赖

维度 判断标准
内聚度 高内聚:模块内部元素紧密相关 低内聚:模块是"工具箱",啥都往里塞 模块改名会不会觉得别扭?
耦合度 低耦合:模块间只通过接口通信 高耦合:模块间直接引用内部类 删掉一个模块,其他模块编译能不能过?

二、结构化设计的六大原则

2.1 原则全景图

#mermaid-svg-wy9qk4h7ot9vDN1H{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wy9qk4h7ot9vDN1H .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wy9qk4h7ot9vDN1H .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wy9qk4h7ot9vDN1H .error-icon{fill:#552222;}#mermaid-svg-wy9qk4h7ot9vDN1H .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wy9qk4h7ot9vDN1H .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wy9qk4h7ot9vDN1H .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wy9qk4h7ot9vDN1H .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wy9qk4h7ot9vDN1H .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wy9qk4h7ot9vDN1H .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wy9qk4h7ot9vDN1H .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wy9qk4h7ot9vDN1H .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wy9qk4h7ot9vDN1H .marker.cross{stroke:#333333;}#mermaid-svg-wy9qk4h7ot9vDN1H svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wy9qk4h7ot9vDN1H p{margin:0;}#mermaid-svg-wy9qk4h7ot9vDN1H .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wy9qk4h7ot9vDN1H .cluster-label text{fill:#333;}#mermaid-svg-wy9qk4h7ot9vDN1H .cluster-label span{color:#333;}#mermaid-svg-wy9qk4h7ot9vDN1H .cluster-label span p{background-color:transparent;}#mermaid-svg-wy9qk4h7ot9vDN1H .label text,#mermaid-svg-wy9qk4h7ot9vDN1H span{fill:#333;color:#333;}#mermaid-svg-wy9qk4h7ot9vDN1H .node rect,#mermaid-svg-wy9qk4h7ot9vDN1H .node circle,#mermaid-svg-wy9qk4h7ot9vDN1H .node ellipse,#mermaid-svg-wy9qk4h7ot9vDN1H .node polygon,#mermaid-svg-wy9qk4h7ot9vDN1H .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wy9qk4h7ot9vDN1H .rough-node .label text,#mermaid-svg-wy9qk4h7ot9vDN1H .node .label text,#mermaid-svg-wy9qk4h7ot9vDN1H .image-shape .label,#mermaid-svg-wy9qk4h7ot9vDN1H .icon-shape .label{text-anchor:middle;}#mermaid-svg-wy9qk4h7ot9vDN1H .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wy9qk4h7ot9vDN1H .rough-node .label,#mermaid-svg-wy9qk4h7ot9vDN1H .node .label,#mermaid-svg-wy9qk4h7ot9vDN1H .image-shape .label,#mermaid-svg-wy9qk4h7ot9vDN1H .icon-shape .label{text-align:center;}#mermaid-svg-wy9qk4h7ot9vDN1H .node.clickable{cursor:pointer;}#mermaid-svg-wy9qk4h7ot9vDN1H .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wy9qk4h7ot9vDN1H .arrowheadPath{fill:#333333;}#mermaid-svg-wy9qk4h7ot9vDN1H .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wy9qk4h7ot9vDN1H .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wy9qk4h7ot9vDN1H .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wy9qk4h7ot9vDN1H .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wy9qk4h7ot9vDN1H .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wy9qk4h7ot9vDN1H .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wy9qk4h7ot9vDN1H .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wy9qk4h7ot9vDN1H .cluster text{fill:#333;}#mermaid-svg-wy9qk4h7ot9vDN1H .cluster span{color:#333;}#mermaid-svg-wy9qk4h7ot9vDN1H div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wy9qk4h7ot9vDN1H .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wy9qk4h7ot9vDN1H rect.text{fill:none;stroke-width:0;}#mermaid-svg-wy9qk4h7ot9vDN1H .icon-shape,#mermaid-svg-wy9qk4h7ot9vDN1H .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wy9qk4h7ot9vDN1H .icon-shape p,#mermaid-svg-wy9qk4h7ot9vDN1H .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wy9qk4h7ot9vDN1H .icon-shape .label rect,#mermaid-svg-wy9qk4h7ot9vDN1H .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wy9qk4h7ot9vDN1H .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wy9qk4h7ot9vDN1H .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wy9qk4h7ot9vDN1H :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 结构化设计六大原则
单一职责

一个模块只做一件事
高内聚低耦合

内部紧密 外部松散
信息隐藏

只暴露必要的接口
依赖倒置

依赖抽象 不依赖实现
接口隔离

接口小而精 不做大而全
分层架构

自上而下 禁止穿透

2.2 原则一:单一职责(SRP at 模块级)

一个模块只负责一个明确的业务领域,只有一个理由让它改变。

反例:

复制代码
module: user-service
  ├── UserController       // 用户管理 ✓
  ├── OrderController      // 订单管理 ✗ 该去 order-service
  ├── NotificationService  // 通知发送 ✗ 该去 notification-service
  └── FileUploadService    // 文件上传 ✗ 该去 file-service

这个"用户服务"有 4 个改变理由(用户变更、订单变更、通知变更、文件变更)------它不是单一职责,是"大杂烩"。

正例(iPaaS 的服务划分):
#mermaid-svg-UkNKZrz2V4xvBfcS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UkNKZrz2V4xvBfcS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UkNKZrz2V4xvBfcS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UkNKZrz2V4xvBfcS .error-icon{fill:#552222;}#mermaid-svg-UkNKZrz2V4xvBfcS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UkNKZrz2V4xvBfcS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UkNKZrz2V4xvBfcS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UkNKZrz2V4xvBfcS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UkNKZrz2V4xvBfcS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UkNKZrz2V4xvBfcS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UkNKZrz2V4xvBfcS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UkNKZrz2V4xvBfcS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UkNKZrz2V4xvBfcS .marker.cross{stroke:#333333;}#mermaid-svg-UkNKZrz2V4xvBfcS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UkNKZrz2V4xvBfcS p{margin:0;}#mermaid-svg-UkNKZrz2V4xvBfcS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UkNKZrz2V4xvBfcS .cluster-label text{fill:#333;}#mermaid-svg-UkNKZrz2V4xvBfcS .cluster-label span{color:#333;}#mermaid-svg-UkNKZrz2V4xvBfcS .cluster-label span p{background-color:transparent;}#mermaid-svg-UkNKZrz2V4xvBfcS .label text,#mermaid-svg-UkNKZrz2V4xvBfcS span{fill:#333;color:#333;}#mermaid-svg-UkNKZrz2V4xvBfcS .node rect,#mermaid-svg-UkNKZrz2V4xvBfcS .node circle,#mermaid-svg-UkNKZrz2V4xvBfcS .node ellipse,#mermaid-svg-UkNKZrz2V4xvBfcS .node polygon,#mermaid-svg-UkNKZrz2V4xvBfcS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UkNKZrz2V4xvBfcS .rough-node .label text,#mermaid-svg-UkNKZrz2V4xvBfcS .node .label text,#mermaid-svg-UkNKZrz2V4xvBfcS .image-shape .label,#mermaid-svg-UkNKZrz2V4xvBfcS .icon-shape .label{text-anchor:middle;}#mermaid-svg-UkNKZrz2V4xvBfcS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UkNKZrz2V4xvBfcS .rough-node .label,#mermaid-svg-UkNKZrz2V4xvBfcS .node .label,#mermaid-svg-UkNKZrz2V4xvBfcS .image-shape .label,#mermaid-svg-UkNKZrz2V4xvBfcS .icon-shape .label{text-align:center;}#mermaid-svg-UkNKZrz2V4xvBfcS .node.clickable{cursor:pointer;}#mermaid-svg-UkNKZrz2V4xvBfcS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UkNKZrz2V4xvBfcS .arrowheadPath{fill:#333333;}#mermaid-svg-UkNKZrz2V4xvBfcS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UkNKZrz2V4xvBfcS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UkNKZrz2V4xvBfcS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UkNKZrz2V4xvBfcS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UkNKZrz2V4xvBfcS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UkNKZrz2V4xvBfcS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UkNKZrz2V4xvBfcS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UkNKZrz2V4xvBfcS .cluster text{fill:#333;}#mermaid-svg-UkNKZrz2V4xvBfcS .cluster span{color:#333;}#mermaid-svg-UkNKZrz2V4xvBfcS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UkNKZrz2V4xvBfcS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UkNKZrz2V4xvBfcS rect.text{fill:none;stroke-width:0;}#mermaid-svg-UkNKZrz2V4xvBfcS .icon-shape,#mermaid-svg-UkNKZrz2V4xvBfcS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UkNKZrz2V4xvBfcS .icon-shape p,#mermaid-svg-UkNKZrz2V4xvBfcS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UkNKZrz2V4xvBfcS .icon-shape .label rect,#mermaid-svg-UkNKZrz2V4xvBfcS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UkNKZrz2V4xvBfcS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UkNKZrz2V4xvBfcS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UkNKZrz2V4xvBfcS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} iPaaS核心服务
流程引擎

ipaas-engine

唯一职责:执行流程
管理网关

ipaas-gateway

唯一职责:流程配置管理
事件中心

ipaas-event-center

唯一职责:Webhook接收与分发
应用库

ipaas-app-libs

唯一职责:连接器定义与Schema
JS引擎

ipaas-js-engine

唯一职责:脚本执行

每个服务只有一个改变理由。流程执行逻辑变了,改 engine;Webhook 处理逻辑变了,改 event-center。互不干扰。

2.3 原则二:高内聚低耦合

模块内部的东西应该紧密协作,模块之间应该尽量减少依赖。

耦合度从低到高:

复制代码
无直接耦合 → 消息耦合 → 接口耦合 → 类耦合 → 代码级耦合 → 内容耦合
(最好)                                              (最差)
耦合类型 说明 示例
无直接耦合 两个模块互不引用 engine 和 event-center 通过 MQ 通信
消息耦合 通过消息队列异步通信 engine 发 MQ 消息,middleware 消费
接口耦合 通过定义好的接口调用 gateway 调用 engine 的 REST API
类耦合 直接引用另一个模块的内部类 ❌ engine 直接引用 gateway 的 DAO
内容耦合 直接修改另一个模块的内部数据 ❌ 直接写另一个模块的数据库表

2.4 原则三:信息隐藏

模块内部实现对外不可见,外部只能通过暴露的接口操作。
#mermaid-svg-LULTXfkXPxjhIEvV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LULTXfkXPxjhIEvV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LULTXfkXPxjhIEvV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LULTXfkXPxjhIEvV .error-icon{fill:#552222;}#mermaid-svg-LULTXfkXPxjhIEvV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LULTXfkXPxjhIEvV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LULTXfkXPxjhIEvV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LULTXfkXPxjhIEvV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LULTXfkXPxjhIEvV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LULTXfkXPxjhIEvV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LULTXfkXPxjhIEvV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LULTXfkXPxjhIEvV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LULTXfkXPxjhIEvV .marker.cross{stroke:#333333;}#mermaid-svg-LULTXfkXPxjhIEvV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LULTXfkXPxjhIEvV p{margin:0;}#mermaid-svg-LULTXfkXPxjhIEvV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LULTXfkXPxjhIEvV .cluster-label text{fill:#333;}#mermaid-svg-LULTXfkXPxjhIEvV .cluster-label span{color:#333;}#mermaid-svg-LULTXfkXPxjhIEvV .cluster-label span p{background-color:transparent;}#mermaid-svg-LULTXfkXPxjhIEvV .label text,#mermaid-svg-LULTXfkXPxjhIEvV span{fill:#333;color:#333;}#mermaid-svg-LULTXfkXPxjhIEvV .node rect,#mermaid-svg-LULTXfkXPxjhIEvV .node circle,#mermaid-svg-LULTXfkXPxjhIEvV .node ellipse,#mermaid-svg-LULTXfkXPxjhIEvV .node polygon,#mermaid-svg-LULTXfkXPxjhIEvV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LULTXfkXPxjhIEvV .rough-node .label text,#mermaid-svg-LULTXfkXPxjhIEvV .node .label text,#mermaid-svg-LULTXfkXPxjhIEvV .image-shape .label,#mermaid-svg-LULTXfkXPxjhIEvV .icon-shape .label{text-anchor:middle;}#mermaid-svg-LULTXfkXPxjhIEvV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LULTXfkXPxjhIEvV .rough-node .label,#mermaid-svg-LULTXfkXPxjhIEvV .node .label,#mermaid-svg-LULTXfkXPxjhIEvV .image-shape .label,#mermaid-svg-LULTXfkXPxjhIEvV .icon-shape .label{text-align:center;}#mermaid-svg-LULTXfkXPxjhIEvV .node.clickable{cursor:pointer;}#mermaid-svg-LULTXfkXPxjhIEvV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LULTXfkXPxjhIEvV .arrowheadPath{fill:#333333;}#mermaid-svg-LULTXfkXPxjhIEvV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LULTXfkXPxjhIEvV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LULTXfkXPxjhIEvV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LULTXfkXPxjhIEvV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LULTXfkXPxjhIEvV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LULTXfkXPxjhIEvV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LULTXfkXPxjhIEvV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LULTXfkXPxjhIEvV .cluster text{fill:#333;}#mermaid-svg-LULTXfkXPxjhIEvV .cluster span{color:#333;}#mermaid-svg-LULTXfkXPxjhIEvV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LULTXfkXPxjhIEvV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LULTXfkXPxjhIEvV rect.text{fill:none;stroke-width:0;}#mermaid-svg-LULTXfkXPxjhIEvV .icon-shape,#mermaid-svg-LULTXfkXPxjhIEvV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LULTXfkXPxjhIEvV .icon-shape p,#mermaid-svg-LULTXfkXPxjhIEvV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LULTXfkXPxjhIEvV .icon-shape .label rect,#mermaid-svg-LULTXfkXPxjhIEvV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LULTXfkXPxjhIEvV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LULTXfkXPxjhIEvV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LULTXfkXPxjhIEvV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 连接器模块
禁止直接引用
禁止直接引用
公开接口

ConnectorExecutor
内部实现

OAuth2TokenRefresher
内部实现

RequestBuilder
内部实现

ResponseParser
其他模块

Java 项目中信息隐藏的实践:

java 复制代码
// ❌ 暴露内部实现
public class ConnectorService {
    public OAuth2TokenRefresher tokenRefresher;  // public!外部直接访问
    public RequestBuilder requestBuilder;

    public Response execute(Request req) { ... }
}

// ✓ 隐藏内部实现,只暴露接口
public class ConnectorService {
    private final OAuth2TokenRefresher tokenRefresher;  // private
    private final RequestBuilder requestBuilder;        // private

    // 只暴露这一个方法
    public Response execute(Request req) { ... }
}

2.5 原则四:依赖倒置(DIP)

高层模块不依赖低层模块,两者都依赖抽象。
#mermaid-svg-Nl5cqC3YQY6zgf60{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Nl5cqC3YQY6zgf60 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Nl5cqC3YQY6zgf60 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Nl5cqC3YQY6zgf60 .error-icon{fill:#552222;}#mermaid-svg-Nl5cqC3YQY6zgf60 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Nl5cqC3YQY6zgf60 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Nl5cqC3YQY6zgf60 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Nl5cqC3YQY6zgf60 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Nl5cqC3YQY6zgf60 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Nl5cqC3YQY6zgf60 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Nl5cqC3YQY6zgf60 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Nl5cqC3YQY6zgf60 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Nl5cqC3YQY6zgf60 .marker.cross{stroke:#333333;}#mermaid-svg-Nl5cqC3YQY6zgf60 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Nl5cqC3YQY6zgf60 p{margin:0;}#mermaid-svg-Nl5cqC3YQY6zgf60 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Nl5cqC3YQY6zgf60 .cluster-label text{fill:#333;}#mermaid-svg-Nl5cqC3YQY6zgf60 .cluster-label span{color:#333;}#mermaid-svg-Nl5cqC3YQY6zgf60 .cluster-label span p{background-color:transparent;}#mermaid-svg-Nl5cqC3YQY6zgf60 .label text,#mermaid-svg-Nl5cqC3YQY6zgf60 span{fill:#333;color:#333;}#mermaid-svg-Nl5cqC3YQY6zgf60 .node rect,#mermaid-svg-Nl5cqC3YQY6zgf60 .node circle,#mermaid-svg-Nl5cqC3YQY6zgf60 .node ellipse,#mermaid-svg-Nl5cqC3YQY6zgf60 .node polygon,#mermaid-svg-Nl5cqC3YQY6zgf60 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Nl5cqC3YQY6zgf60 .rough-node .label text,#mermaid-svg-Nl5cqC3YQY6zgf60 .node .label text,#mermaid-svg-Nl5cqC3YQY6zgf60 .image-shape .label,#mermaid-svg-Nl5cqC3YQY6zgf60 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Nl5cqC3YQY6zgf60 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Nl5cqC3YQY6zgf60 .rough-node .label,#mermaid-svg-Nl5cqC3YQY6zgf60 .node .label,#mermaid-svg-Nl5cqC3YQY6zgf60 .image-shape .label,#mermaid-svg-Nl5cqC3YQY6zgf60 .icon-shape .label{text-align:center;}#mermaid-svg-Nl5cqC3YQY6zgf60 .node.clickable{cursor:pointer;}#mermaid-svg-Nl5cqC3YQY6zgf60 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Nl5cqC3YQY6zgf60 .arrowheadPath{fill:#333333;}#mermaid-svg-Nl5cqC3YQY6zgf60 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Nl5cqC3YQY6zgf60 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Nl5cqC3YQY6zgf60 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Nl5cqC3YQY6zgf60 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Nl5cqC3YQY6zgf60 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Nl5cqC3YQY6zgf60 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Nl5cqC3YQY6zgf60 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Nl5cqC3YQY6zgf60 .cluster text{fill:#333;}#mermaid-svg-Nl5cqC3YQY6zgf60 .cluster span{color:#333;}#mermaid-svg-Nl5cqC3YQY6zgf60 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Nl5cqC3YQY6zgf60 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Nl5cqC3YQY6zgf60 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Nl5cqC3YQY6zgf60 .icon-shape,#mermaid-svg-Nl5cqC3YQY6zgf60 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Nl5cqC3YQY6zgf60 .icon-shape p,#mermaid-svg-Nl5cqC3YQY6zgf60 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Nl5cqC3YQY6zgf60 .icon-shape .label rect,#mermaid-svg-Nl5cqC3YQY6zgf60 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Nl5cqC3YQY6zgf60 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Nl5cqC3YQY6zgf60 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Nl5cqC3YQY6zgf60 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 好设计:都依赖抽象
依赖
实现
实现
实现
流程引擎
ConnectorExecutor接口
钉钉适配器
企微适配器
飞书适配器
坏设计:高层依赖低层实现
直接依赖
直接依赖
直接依赖
流程引擎
钉钉SDK
企微SDK
飞书SDK

iPaaS 的依赖倒置实践:

java 复制代码
// 引擎层定义抽象接口
public interface ConnectorExecutor {
    ExecuteResult execute(ConnectorRequest request);
}

// 引擎层只依赖接口,不依赖任何具体实现
public class FlowEngine {
    private final Map<String, ConnectorExecutor> executors;

    public ExecuteResult executeNode(FlowNode node) {
        ConnectorExecutor executor = executors.get(node.getConnectorType());
        return executor.execute(buildRequest(node));
    }
}

// 具体连接器实现接口------新增连接器不改引擎
@Component
public class DingTalkConnectorExecutor implements ConnectorExecutor { ... }

@Component
public class WeComConnectorExecutor implements ConnectorExecutor { ... }

2.6 原则五:接口隔离

接口要小而精,不要大而全。客户端不应该被迫依赖它不需要的方法。

java 复制代码
// ❌ 大接口:不是所有连接器都需要这些方法
public interface Connector {
    void connect();
    void disconnect();
    void execute(Request req);
    void subscribe(Event event);      // 只有事件类连接器需要
    void unsubscribe(Event event);    // 只有事件类连接器需要
    void batchExecute(List<Request> reqs); // 只有批量类连接器需要
}

// ✓ 小接口:按需组合
public interface Connector {
    void execute(Request req);
}

public interface EventDrivenConnector {
    void subscribe(Event event);
    void unsubscribe(Event event);
}

public interface BatchConnector {
    void batchExecute(List<Request> reqs);
}

2.7 原则六:分层架构------禁止穿透

每一层只能调用下一层的接口,禁止跳层调用。
#mermaid-svg-4KliyWqs3v2pYgfN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4KliyWqs3v2pYgfN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4KliyWqs3v2pYgfN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4KliyWqs3v2pYgfN .error-icon{fill:#552222;}#mermaid-svg-4KliyWqs3v2pYgfN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4KliyWqs3v2pYgfN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4KliyWqs3v2pYgfN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4KliyWqs3v2pYgfN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4KliyWqs3v2pYgfN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4KliyWqs3v2pYgfN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4KliyWqs3v2pYgfN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4KliyWqs3v2pYgfN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4KliyWqs3v2pYgfN .marker.cross{stroke:#333333;}#mermaid-svg-4KliyWqs3v2pYgfN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4KliyWqs3v2pYgfN p{margin:0;}#mermaid-svg-4KliyWqs3v2pYgfN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4KliyWqs3v2pYgfN .cluster-label text{fill:#333;}#mermaid-svg-4KliyWqs3v2pYgfN .cluster-label span{color:#333;}#mermaid-svg-4KliyWqs3v2pYgfN .cluster-label span p{background-color:transparent;}#mermaid-svg-4KliyWqs3v2pYgfN .label text,#mermaid-svg-4KliyWqs3v2pYgfN span{fill:#333;color:#333;}#mermaid-svg-4KliyWqs3v2pYgfN .node rect,#mermaid-svg-4KliyWqs3v2pYgfN .node circle,#mermaid-svg-4KliyWqs3v2pYgfN .node ellipse,#mermaid-svg-4KliyWqs3v2pYgfN .node polygon,#mermaid-svg-4KliyWqs3v2pYgfN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4KliyWqs3v2pYgfN .rough-node .label text,#mermaid-svg-4KliyWqs3v2pYgfN .node .label text,#mermaid-svg-4KliyWqs3v2pYgfN .image-shape .label,#mermaid-svg-4KliyWqs3v2pYgfN .icon-shape .label{text-anchor:middle;}#mermaid-svg-4KliyWqs3v2pYgfN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4KliyWqs3v2pYgfN .rough-node .label,#mermaid-svg-4KliyWqs3v2pYgfN .node .label,#mermaid-svg-4KliyWqs3v2pYgfN .image-shape .label,#mermaid-svg-4KliyWqs3v2pYgfN .icon-shape .label{text-align:center;}#mermaid-svg-4KliyWqs3v2pYgfN .node.clickable{cursor:pointer;}#mermaid-svg-4KliyWqs3v2pYgfN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4KliyWqs3v2pYgfN .arrowheadPath{fill:#333333;}#mermaid-svg-4KliyWqs3v2pYgfN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4KliyWqs3v2pYgfN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4KliyWqs3v2pYgfN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4KliyWqs3v2pYgfN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4KliyWqs3v2pYgfN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4KliyWqs3v2pYgfN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4KliyWqs3v2pYgfN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4KliyWqs3v2pYgfN .cluster text{fill:#333;}#mermaid-svg-4KliyWqs3v2pYgfN .cluster span{color:#333;}#mermaid-svg-4KliyWqs3v2pYgfN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4KliyWqs3v2pYgfN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4KliyWqs3v2pYgfN rect.text{fill:none;stroke-width:0;}#mermaid-svg-4KliyWqs3v2pYgfN .icon-shape,#mermaid-svg-4KliyWqs3v2pYgfN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4KliyWqs3v2pYgfN .icon-shape p,#mermaid-svg-4KliyWqs3v2pYgfN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4KliyWqs3v2pYgfN .icon-shape .label rect,#mermaid-svg-4KliyWqs3v2pYgfN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4KliyWqs3v2pYgfN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4KliyWqs3v2pYgfN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4KliyWqs3v2pYgfN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 禁止穿透
❌ 跳过Service
❌ 跳过Service和Domain
Controller
Repository
Controller
SQL
正确分层
Controller层

接收请求 返回响应
Service层

业务编排
Domain层

核心业务逻辑
Repository层

数据访问

穿透的危害

java 复制代码
// ❌ Controller 直接调 DAO------跳过了 Service 和 Domain
@Controller
public class FlowController {
    @Autowired
    private FlowMapper flowMapper; // DAO

    @GetMapping("/flows/{id}")
    public FlowDO getFlow(@PathVariable Long id) {
        return flowMapper.selectById(id); // 直接查库!
        // 没有鉴权、没有业务校验、没有 DTO 转换
    }
}

三、模块拆分的实操方法

3.1 方法一:按业务域拆分(DDD 限界上下文)

#mermaid-svg-HGGTXdGvQeUTiuYv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HGGTXdGvQeUTiuYv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HGGTXdGvQeUTiuYv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HGGTXdGvQeUTiuYv .error-icon{fill:#552222;}#mermaid-svg-HGGTXdGvQeUTiuYv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HGGTXdGvQeUTiuYv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HGGTXdGvQeUTiuYv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HGGTXdGvQeUTiuYv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HGGTXdGvQeUTiuYv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HGGTXdGvQeUTiuYv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HGGTXdGvQeUTiuYv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HGGTXdGvQeUTiuYv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HGGTXdGvQeUTiuYv .marker.cross{stroke:#333333;}#mermaid-svg-HGGTXdGvQeUTiuYv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HGGTXdGvQeUTiuYv p{margin:0;}#mermaid-svg-HGGTXdGvQeUTiuYv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HGGTXdGvQeUTiuYv .cluster-label text{fill:#333;}#mermaid-svg-HGGTXdGvQeUTiuYv .cluster-label span{color:#333;}#mermaid-svg-HGGTXdGvQeUTiuYv .cluster-label span p{background-color:transparent;}#mermaid-svg-HGGTXdGvQeUTiuYv .label text,#mermaid-svg-HGGTXdGvQeUTiuYv span{fill:#333;color:#333;}#mermaid-svg-HGGTXdGvQeUTiuYv .node rect,#mermaid-svg-HGGTXdGvQeUTiuYv .node circle,#mermaid-svg-HGGTXdGvQeUTiuYv .node ellipse,#mermaid-svg-HGGTXdGvQeUTiuYv .node polygon,#mermaid-svg-HGGTXdGvQeUTiuYv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HGGTXdGvQeUTiuYv .rough-node .label text,#mermaid-svg-HGGTXdGvQeUTiuYv .node .label text,#mermaid-svg-HGGTXdGvQeUTiuYv .image-shape .label,#mermaid-svg-HGGTXdGvQeUTiuYv .icon-shape .label{text-anchor:middle;}#mermaid-svg-HGGTXdGvQeUTiuYv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HGGTXdGvQeUTiuYv .rough-node .label,#mermaid-svg-HGGTXdGvQeUTiuYv .node .label,#mermaid-svg-HGGTXdGvQeUTiuYv .image-shape .label,#mermaid-svg-HGGTXdGvQeUTiuYv .icon-shape .label{text-align:center;}#mermaid-svg-HGGTXdGvQeUTiuYv .node.clickable{cursor:pointer;}#mermaid-svg-HGGTXdGvQeUTiuYv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HGGTXdGvQeUTiuYv .arrowheadPath{fill:#333333;}#mermaid-svg-HGGTXdGvQeUTiuYv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HGGTXdGvQeUTiuYv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HGGTXdGvQeUTiuYv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HGGTXdGvQeUTiuYv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HGGTXdGvQeUTiuYv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HGGTXdGvQeUTiuYv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HGGTXdGvQeUTiuYv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HGGTXdGvQeUTiuYv .cluster text{fill:#333;}#mermaid-svg-HGGTXdGvQeUTiuYv .cluster span{color:#333;}#mermaid-svg-HGGTXdGvQeUTiuYv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HGGTXdGvQeUTiuYv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HGGTXdGvQeUTiuYv rect.text{fill:none;stroke-width:0;}#mermaid-svg-HGGTXdGvQeUTiuYv .icon-shape,#mermaid-svg-HGGTXdGvQeUTiuYv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HGGTXdGvQeUTiuYv .icon-shape p,#mermaid-svg-HGGTXdGvQeUTiuYv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HGGTXdGvQeUTiuYv .icon-shape .label rect,#mermaid-svg-HGGTXdGvQeUTiuYv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HGGTXdGvQeUTiuYv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HGGTXdGvQeUTiuYv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HGGTXdGvQeUTiuYv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} iPaaS限界上下文
流程执行上下文

执行/暂停/恢复/终止
连接器上下文

定义/鉴权/调用/错误处理
调度上下文

触发器/定时/Webhook/事件
用户上下文

注册/登录/权限/组织
运营上下文

计费/统计/审计/告警

拆分判断标准:同一个名词在不同上下文中的含义不同,就应该拆。

名词 流程执行上下文 运营上下文 用户上下文
"流程" 可执行的 DAG 定义 计费的资源单元 用户创建的内容
"用户" 流程的执行者 付费的客户 登录的账号
"执行" 节点遍历 + 状态流转 消耗配额的事件 操作历史记录

同一个"流程"在三个上下文中有不同的属性和行为------这就是拆分的信号。

3.2 方法二:按流量特征拆分

这是我做 iPaaS 时最实用的拆分方法------不按功能拆,按流量拆
#mermaid-svg-IZNce75y9SfcajY9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IZNce75y9SfcajY9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IZNce75y9SfcajY9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IZNce75y9SfcajY9 .error-icon{fill:#552222;}#mermaid-svg-IZNce75y9SfcajY9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IZNce75y9SfcajY9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IZNce75y9SfcajY9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IZNce75y9SfcajY9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IZNce75y9SfcajY9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IZNce75y9SfcajY9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IZNce75y9SfcajY9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IZNce75y9SfcajY9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IZNce75y9SfcajY9 .marker.cross{stroke:#333333;}#mermaid-svg-IZNce75y9SfcajY9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IZNce75y9SfcajY9 p{margin:0;}#mermaid-svg-IZNce75y9SfcajY9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IZNce75y9SfcajY9 .cluster-label text{fill:#333;}#mermaid-svg-IZNce75y9SfcajY9 .cluster-label span{color:#333;}#mermaid-svg-IZNce75y9SfcajY9 .cluster-label span p{background-color:transparent;}#mermaid-svg-IZNce75y9SfcajY9 .label text,#mermaid-svg-IZNce75y9SfcajY9 span{fill:#333;color:#333;}#mermaid-svg-IZNce75y9SfcajY9 .node rect,#mermaid-svg-IZNce75y9SfcajY9 .node circle,#mermaid-svg-IZNce75y9SfcajY9 .node ellipse,#mermaid-svg-IZNce75y9SfcajY9 .node polygon,#mermaid-svg-IZNce75y9SfcajY9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IZNce75y9SfcajY9 .rough-node .label text,#mermaid-svg-IZNce75y9SfcajY9 .node .label text,#mermaid-svg-IZNce75y9SfcajY9 .image-shape .label,#mermaid-svg-IZNce75y9SfcajY9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-IZNce75y9SfcajY9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IZNce75y9SfcajY9 .rough-node .label,#mermaid-svg-IZNce75y9SfcajY9 .node .label,#mermaid-svg-IZNce75y9SfcajY9 .image-shape .label,#mermaid-svg-IZNce75y9SfcajY9 .icon-shape .label{text-align:center;}#mermaid-svg-IZNce75y9SfcajY9 .node.clickable{cursor:pointer;}#mermaid-svg-IZNce75y9SfcajY9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IZNce75y9SfcajY9 .arrowheadPath{fill:#333333;}#mermaid-svg-IZNce75y9SfcajY9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IZNce75y9SfcajY9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IZNce75y9SfcajY9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IZNce75y9SfcajY9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IZNce75y9SfcajY9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IZNce75y9SfcajY9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IZNce75y9SfcajY9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IZNce75y9SfcajY9 .cluster text{fill:#333;}#mermaid-svg-IZNce75y9SfcajY9 .cluster span{color:#333;}#mermaid-svg-IZNce75y9SfcajY9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IZNce75y9SfcajY9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IZNce75y9SfcajY9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-IZNce75y9SfcajY9 .icon-shape,#mermaid-svg-IZNce75y9SfcajY9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IZNce75y9SfcajY9 .icon-shape p,#mermaid-svg-IZNce75y9SfcajY9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IZNce75y9SfcajY9 .icon-shape .label rect,#mermaid-svg-IZNce75y9SfcajY9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IZNce75y9SfcajY9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IZNce75y9SfcajY9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IZNce75y9SfcajY9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 流量特征分析
高频持续

流程执行

CPU密集
低频可预测

流程管理

内存型
突发不可控

Webhook

IO密集
定时批量

数据同步

磁盘IO
engine

独立部署+HPA
gateway

独立部署
event-center

独立部署+弹性
middleware

独立部署+调度

为什么按流量拆比按功能拆好?

维度 按功能拆 按流量拆
资源隔离 ❌ 高频和突发抢资源 ✅ 各自独立资源池
弹性伸缩 ❌ 全量扩缩容 ✅ 按需扩缩容
故障隔离 ❌ Webhook 拖垮全系统 ✅ 只影响自己的服务
运维成本 略高(但值得)

3.3 方法三:按变更频率拆分

变化快的和变化慢的不要放在一起。
#mermaid-svg-RRsl9MzVFVe1GKU4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RRsl9MzVFVe1GKU4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RRsl9MzVFVe1GKU4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RRsl9MzVFVe1GKU4 .error-icon{fill:#552222;}#mermaid-svg-RRsl9MzVFVe1GKU4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RRsl9MzVFVe1GKU4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RRsl9MzVFVe1GKU4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RRsl9MzVFVe1GKU4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RRsl9MzVFVe1GKU4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RRsl9MzVFVe1GKU4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RRsl9MzVFVe1GKU4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RRsl9MzVFVe1GKU4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RRsl9MzVFVe1GKU4 .marker.cross{stroke:#333333;}#mermaid-svg-RRsl9MzVFVe1GKU4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RRsl9MzVFVe1GKU4 p{margin:0;}#mermaid-svg-RRsl9MzVFVe1GKU4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RRsl9MzVFVe1GKU4 .cluster-label text{fill:#333;}#mermaid-svg-RRsl9MzVFVe1GKU4 .cluster-label span{color:#333;}#mermaid-svg-RRsl9MzVFVe1GKU4 .cluster-label span p{background-color:transparent;}#mermaid-svg-RRsl9MzVFVe1GKU4 .label text,#mermaid-svg-RRsl9MzVFVe1GKU4 span{fill:#333;color:#333;}#mermaid-svg-RRsl9MzVFVe1GKU4 .node rect,#mermaid-svg-RRsl9MzVFVe1GKU4 .node circle,#mermaid-svg-RRsl9MzVFVe1GKU4 .node ellipse,#mermaid-svg-RRsl9MzVFVe1GKU4 .node polygon,#mermaid-svg-RRsl9MzVFVe1GKU4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RRsl9MzVFVe1GKU4 .rough-node .label text,#mermaid-svg-RRsl9MzVFVe1GKU4 .node .label text,#mermaid-svg-RRsl9MzVFVe1GKU4 .image-shape .label,#mermaid-svg-RRsl9MzVFVe1GKU4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-RRsl9MzVFVe1GKU4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RRsl9MzVFVe1GKU4 .rough-node .label,#mermaid-svg-RRsl9MzVFVe1GKU4 .node .label,#mermaid-svg-RRsl9MzVFVe1GKU4 .image-shape .label,#mermaid-svg-RRsl9MzVFVe1GKU4 .icon-shape .label{text-align:center;}#mermaid-svg-RRsl9MzVFVe1GKU4 .node.clickable{cursor:pointer;}#mermaid-svg-RRsl9MzVFVe1GKU4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RRsl9MzVFVe1GKU4 .arrowheadPath{fill:#333333;}#mermaid-svg-RRsl9MzVFVe1GKU4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RRsl9MzVFVe1GKU4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RRsl9MzVFVe1GKU4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RRsl9MzVFVe1GKU4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RRsl9MzVFVe1GKU4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RRsl9MzVFVe1GKU4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RRsl9MzVFVe1GKU4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RRsl9MzVFVe1GKU4 .cluster text{fill:#333;}#mermaid-svg-RRsl9MzVFVe1GKU4 .cluster span{color:#333;}#mermaid-svg-RRsl9MzVFVe1GKU4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RRsl9MzVFVe1GKU4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RRsl9MzVFVe1GKU4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-RRsl9MzVFVe1GKU4 .icon-shape,#mermaid-svg-RRsl9MzVFVe1GKU4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RRsl9MzVFVe1GKU4 .icon-shape p,#mermaid-svg-RRsl9MzVFVe1GKU4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RRsl9MzVFVe1GKU4 .icon-shape .label rect,#mermaid-svg-RRsl9MzVFVe1GKU4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RRsl9MzVFVe1GKU4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RRsl9MzVFVe1GKU4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RRsl9MzVFVe1GKU4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 低频变更-每季度1次
核心框架

基础库/中间件封装
数据模型

表结构/索引
中频变更-每月1到2次
流程引擎

执行逻辑优化
API网关

路由/鉴权策略
高频变更-每周1到2次
连接器实现

新API适配/Bug修复
流程模板

新增场景模板

把高频变更的模块和低频变更的模块分开:

  • 高频模块发版不影响低频模块
  • 低频模块的稳定性保障了系统基座

四、模块间通信的设计

模块拆好后,关键问题:模块之间怎么说话?

4.1 通信方式选型矩阵

#mermaid-svg-an1xWyqXNwKv7Xlv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-an1xWyqXNwKv7Xlv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-an1xWyqXNwKv7Xlv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-an1xWyqXNwKv7Xlv .error-icon{fill:#552222;}#mermaid-svg-an1xWyqXNwKv7Xlv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-an1xWyqXNwKv7Xlv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-an1xWyqXNwKv7Xlv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-an1xWyqXNwKv7Xlv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-an1xWyqXNwKv7Xlv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-an1xWyqXNwKv7Xlv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-an1xWyqXNwKv7Xlv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-an1xWyqXNwKv7Xlv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-an1xWyqXNwKv7Xlv .marker.cross{stroke:#333333;}#mermaid-svg-an1xWyqXNwKv7Xlv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-an1xWyqXNwKv7Xlv p{margin:0;}#mermaid-svg-an1xWyqXNwKv7Xlv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-an1xWyqXNwKv7Xlv .cluster-label text{fill:#333;}#mermaid-svg-an1xWyqXNwKv7Xlv .cluster-label span{color:#333;}#mermaid-svg-an1xWyqXNwKv7Xlv .cluster-label span p{background-color:transparent;}#mermaid-svg-an1xWyqXNwKv7Xlv .label text,#mermaid-svg-an1xWyqXNwKv7Xlv span{fill:#333;color:#333;}#mermaid-svg-an1xWyqXNwKv7Xlv .node rect,#mermaid-svg-an1xWyqXNwKv7Xlv .node circle,#mermaid-svg-an1xWyqXNwKv7Xlv .node ellipse,#mermaid-svg-an1xWyqXNwKv7Xlv .node polygon,#mermaid-svg-an1xWyqXNwKv7Xlv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-an1xWyqXNwKv7Xlv .rough-node .label text,#mermaid-svg-an1xWyqXNwKv7Xlv .node .label text,#mermaid-svg-an1xWyqXNwKv7Xlv .image-shape .label,#mermaid-svg-an1xWyqXNwKv7Xlv .icon-shape .label{text-anchor:middle;}#mermaid-svg-an1xWyqXNwKv7Xlv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-an1xWyqXNwKv7Xlv .rough-node .label,#mermaid-svg-an1xWyqXNwKv7Xlv .node .label,#mermaid-svg-an1xWyqXNwKv7Xlv .image-shape .label,#mermaid-svg-an1xWyqXNwKv7Xlv .icon-shape .label{text-align:center;}#mermaid-svg-an1xWyqXNwKv7Xlv .node.clickable{cursor:pointer;}#mermaid-svg-an1xWyqXNwKv7Xlv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-an1xWyqXNwKv7Xlv .arrowheadPath{fill:#333333;}#mermaid-svg-an1xWyqXNwKv7Xlv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-an1xWyqXNwKv7Xlv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-an1xWyqXNwKv7Xlv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-an1xWyqXNwKv7Xlv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-an1xWyqXNwKv7Xlv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-an1xWyqXNwKv7Xlv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-an1xWyqXNwKv7Xlv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-an1xWyqXNwKv7Xlv .cluster text{fill:#333;}#mermaid-svg-an1xWyqXNwKv7Xlv .cluster span{color:#333;}#mermaid-svg-an1xWyqXNwKv7Xlv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-an1xWyqXNwKv7Xlv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-an1xWyqXNwKv7Xlv rect.text{fill:none;stroke-width:0;}#mermaid-svg-an1xWyqXNwKv7Xlv .icon-shape,#mermaid-svg-an1xWyqXNwKv7Xlv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-an1xWyqXNwKv7Xlv .icon-shape p,#mermaid-svg-an1xWyqXNwKv7Xlv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-an1xWyqXNwKv7Xlv .icon-shape .label rect,#mermaid-svg-an1xWyqXNwKv7Xlv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-an1xWyqXNwKv7Xlv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-an1xWyqXNwKv7Xlv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-an1xWyqXNwKv7Xlv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 选型依据
异步通信
同步通信






RPC/gRPC

强类型 高性能
REST API

通用性强 调试方便
消息队列

解耦 削峰 可靠
事件驱动

发布-订阅 广播
需要立即返回?
调用方关心结果?
需要解耦?

通信方式 适用场景 iPaaS 实践
同步 RPC 需要立即返回结果、强一致性 gateway → engine(查询流程状态)
REST API 跨语言、跨团队、对外开放 对外 API、Webhook 回调
消息队列 异步、削峰、可靠性保证 engine → middleware(日志异步写入)
事件驱动 一对多通知、发布订阅 event-center → engine(触发流程执行)

4.2 iPaaS 服务间通信架构

#mermaid-svg-STnpQ31o7XuHKOR6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-STnpQ31o7XuHKOR6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-STnpQ31o7XuHKOR6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-STnpQ31o7XuHKOR6 .error-icon{fill:#552222;}#mermaid-svg-STnpQ31o7XuHKOR6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-STnpQ31o7XuHKOR6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-STnpQ31o7XuHKOR6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-STnpQ31o7XuHKOR6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-STnpQ31o7XuHKOR6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-STnpQ31o7XuHKOR6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-STnpQ31o7XuHKOR6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-STnpQ31o7XuHKOR6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-STnpQ31o7XuHKOR6 .marker.cross{stroke:#333333;}#mermaid-svg-STnpQ31o7XuHKOR6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-STnpQ31o7XuHKOR6 p{margin:0;}#mermaid-svg-STnpQ31o7XuHKOR6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-STnpQ31o7XuHKOR6 .cluster-label text{fill:#333;}#mermaid-svg-STnpQ31o7XuHKOR6 .cluster-label span{color:#333;}#mermaid-svg-STnpQ31o7XuHKOR6 .cluster-label span p{background-color:transparent;}#mermaid-svg-STnpQ31o7XuHKOR6 .label text,#mermaid-svg-STnpQ31o7XuHKOR6 span{fill:#333;color:#333;}#mermaid-svg-STnpQ31o7XuHKOR6 .node rect,#mermaid-svg-STnpQ31o7XuHKOR6 .node circle,#mermaid-svg-STnpQ31o7XuHKOR6 .node ellipse,#mermaid-svg-STnpQ31o7XuHKOR6 .node polygon,#mermaid-svg-STnpQ31o7XuHKOR6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-STnpQ31o7XuHKOR6 .rough-node .label text,#mermaid-svg-STnpQ31o7XuHKOR6 .node .label text,#mermaid-svg-STnpQ31o7XuHKOR6 .image-shape .label,#mermaid-svg-STnpQ31o7XuHKOR6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-STnpQ31o7XuHKOR6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-STnpQ31o7XuHKOR6 .rough-node .label,#mermaid-svg-STnpQ31o7XuHKOR6 .node .label,#mermaid-svg-STnpQ31o7XuHKOR6 .image-shape .label,#mermaid-svg-STnpQ31o7XuHKOR6 .icon-shape .label{text-align:center;}#mermaid-svg-STnpQ31o7XuHKOR6 .node.clickable{cursor:pointer;}#mermaid-svg-STnpQ31o7XuHKOR6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-STnpQ31o7XuHKOR6 .arrowheadPath{fill:#333333;}#mermaid-svg-STnpQ31o7XuHKOR6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-STnpQ31o7XuHKOR6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-STnpQ31o7XuHKOR6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-STnpQ31o7XuHKOR6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-STnpQ31o7XuHKOR6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-STnpQ31o7XuHKOR6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-STnpQ31o7XuHKOR6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-STnpQ31o7XuHKOR6 .cluster text{fill:#333;}#mermaid-svg-STnpQ31o7XuHKOR6 .cluster span{color:#333;}#mermaid-svg-STnpQ31o7XuHKOR6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-STnpQ31o7XuHKOR6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-STnpQ31o7XuHKOR6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-STnpQ31o7XuHKOR6 .icon-shape,#mermaid-svg-STnpQ31o7XuHKOR6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-STnpQ31o7XuHKOR6 .icon-shape p,#mermaid-svg-STnpQ31o7XuHKOR6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-STnpQ31o7XuHKOR6 .icon-shape .label rect,#mermaid-svg-STnpQ31o7XuHKOR6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-STnpQ31o7XuHKOR6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-STnpQ31o7XuHKOR6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-STnpQ31o7XuHKOR6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 异步层
核心层
接入层
REST
HTTP
同步RPC
MQ异步
RPC
RPC
MQ异步
用户/外部系统
gateway

管理API
event-center

Webhook接收
engine

流程执行
app-libs

连接器库
js-engine

脚本执行
RocketMQ
middleware

中间件服务

关键设计决策

  • gateway → engine:同步(用户点了"执行",需要立即知道结果)
  • webhook → engine:异步(Webhook 来了先入库,引擎自己取,防止突发流量打垮引擎)
  • engine → middleware:异步(日志、统计不影响主流程,异步写)

4.3 防腐层(Anti-Corruption Layer)

当两个模块的"语言"不同时,在中间加一层翻译。

java 复制代码
// ❌ 引擎直接使用连接器的内部模型
public class FlowEngine {
    public void execute() {
        DingTalkApiRequest req = new DingTalkApiRequest(); // 引擎直接依赖钉钉模型
        req.setAction("send_message");
        dingTalkClient.call(req);
    }
}

// ✓ 通过防腐层隔离
public class FlowEngine {
    public void execute() {
        // 引擎只使用统一的 ConnectorRequest
        ConnectorRequest req = ConnectorRequest.builder()
            .connectorType("dingtalk")
            .action("send_message")
            .params(Map.of("content", "hello"))
            .build();
        connectorExecutor.execute(req);
    }
}

// 防腐层:在连接器适配器中做翻译
@Component
public class DingTalkConnectorAdapter implements ConnectorExecutor {
    @Override
    public ExecuteResult execute(ConnectorRequest request) {
        // 把统一的 ConnectorRequest 翻译成钉钉的 DingTalkApiRequest
        DingTalkApiRequest dingReq = translate(request);
        DingTalkApiResponse resp = dingTalkClient.call(dingReq);
        // 把钉钉响应翻译回统一的 ExecuteResult
        return translate(resp);
    }
}

五、模块内部的代码结构

模块拆好了,每个模块内部怎么组织?

5.1 标准四层架构

#mermaid-svg-GPXLBdSHyTyMEiUG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GPXLBdSHyTyMEiUG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GPXLBdSHyTyMEiUG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GPXLBdSHyTyMEiUG .error-icon{fill:#552222;}#mermaid-svg-GPXLBdSHyTyMEiUG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GPXLBdSHyTyMEiUG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GPXLBdSHyTyMEiUG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GPXLBdSHyTyMEiUG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GPXLBdSHyTyMEiUG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GPXLBdSHyTyMEiUG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GPXLBdSHyTyMEiUG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GPXLBdSHyTyMEiUG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GPXLBdSHyTyMEiUG .marker.cross{stroke:#333333;}#mermaid-svg-GPXLBdSHyTyMEiUG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GPXLBdSHyTyMEiUG p{margin:0;}#mermaid-svg-GPXLBdSHyTyMEiUG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GPXLBdSHyTyMEiUG .cluster-label text{fill:#333;}#mermaid-svg-GPXLBdSHyTyMEiUG .cluster-label span{color:#333;}#mermaid-svg-GPXLBdSHyTyMEiUG .cluster-label span p{background-color:transparent;}#mermaid-svg-GPXLBdSHyTyMEiUG .label text,#mermaid-svg-GPXLBdSHyTyMEiUG span{fill:#333;color:#333;}#mermaid-svg-GPXLBdSHyTyMEiUG .node rect,#mermaid-svg-GPXLBdSHyTyMEiUG .node circle,#mermaid-svg-GPXLBdSHyTyMEiUG .node ellipse,#mermaid-svg-GPXLBdSHyTyMEiUG .node polygon,#mermaid-svg-GPXLBdSHyTyMEiUG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GPXLBdSHyTyMEiUG .rough-node .label text,#mermaid-svg-GPXLBdSHyTyMEiUG .node .label text,#mermaid-svg-GPXLBdSHyTyMEiUG .image-shape .label,#mermaid-svg-GPXLBdSHyTyMEiUG .icon-shape .label{text-anchor:middle;}#mermaid-svg-GPXLBdSHyTyMEiUG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GPXLBdSHyTyMEiUG .rough-node .label,#mermaid-svg-GPXLBdSHyTyMEiUG .node .label,#mermaid-svg-GPXLBdSHyTyMEiUG .image-shape .label,#mermaid-svg-GPXLBdSHyTyMEiUG .icon-shape .label{text-align:center;}#mermaid-svg-GPXLBdSHyTyMEiUG .node.clickable{cursor:pointer;}#mermaid-svg-GPXLBdSHyTyMEiUG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GPXLBdSHyTyMEiUG .arrowheadPath{fill:#333333;}#mermaid-svg-GPXLBdSHyTyMEiUG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GPXLBdSHyTyMEiUG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GPXLBdSHyTyMEiUG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GPXLBdSHyTyMEiUG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GPXLBdSHyTyMEiUG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GPXLBdSHyTyMEiUG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GPXLBdSHyTyMEiUG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GPXLBdSHyTyMEiUG .cluster text{fill:#333;}#mermaid-svg-GPXLBdSHyTyMEiUG .cluster span{color:#333;}#mermaid-svg-GPXLBdSHyTyMEiUG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GPXLBdSHyTyMEiUG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GPXLBdSHyTyMEiUG rect.text{fill:none;stroke-width:0;}#mermaid-svg-GPXLBdSHyTyMEiUG .icon-shape,#mermaid-svg-GPXLBdSHyTyMEiUG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GPXLBdSHyTyMEiUG .icon-shape p,#mermaid-svg-GPXLBdSHyTyMEiUG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GPXLBdSHyTyMEiUG .icon-shape .label rect,#mermaid-svg-GPXLBdSHyTyMEiUG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GPXLBdSHyTyMEiUG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GPXLBdSHyTyMEiUG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GPXLBdSHyTyMEiUG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 一个Service模块内部
接口
API层

Controller / Feign接口

职责:接收请求 转换参数 返回响应
业务层

Service

职责:业务编排 事务管理 权限校验
领域层

Domain / Entity

职责:核心业务规则 不可变对象
基础设施层

Repository / Client

职责:数据访问 外部调用 中间件封装

关键规则

  1. 领域层不依赖基础设施层------领域模型不知道数据库的存在
  2. 基础设施层实现领域层定义的接口------依赖倒置
  3. API 层不包含业务逻辑------只做参数转换和响应封装

5.2 代码组织示例

复制代码
ipaas-engine/
├── src/main/java/com/shuhuan/ipaas/engine/
│   ├── api/                        # API层
│   │   ├── FlowExecuteController.java
│   │   └── dto/
│   │       ├── ExecuteRequest.java
│   │       └── ExecuteResponse.java
│   ├── service/                    # 业务层
│   │   ├── FlowExecutionService.java
│   │   └── FlowResumeService.java
│   ├── domain/                     # 领域层
│   │   ├── FlowInstance.java        # 流程实例(聚合根)
│   │   ├── FlowNode.java            # 流程节点
│   │   ├── NodeResult.java          # 节点执行结果
│   │   └── FlowStatus.java          # 状态枚举
│   └── infrastructure/             # 基础设施层
│       ├── repository/
│       │   ├── FlowInstanceRepository.java    # 接口
│       │   └── FlowInstanceRepositoryImpl.java # 实现
│       ├── client/
│       │   └── ConnectorClient.java           # 调用连接器服务
│       └── mq/
│           └── FlowMessageProducer.java       # MQ发送

5.3 Maven 多模块的物理隔离

xml 复制代码
<!-- 父 POM:统一管理版本和依赖 -->
<modules>
    <module>ipaas-engine-core</module>      <!-- 领域层 + 业务层 -->
    <module>ipaas-engine-api</module>       <!-- API层(Controller) -->
    <module>ipaas-engine-infrastructure</module> <!-- 基础设施层 -->
    <module>ipaas-engine-starter</module>   <!-- 启动模块(组装) -->
</modules>

模块间依赖关系
#mermaid-svg-DpklAsge2wnVQN4j{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DpklAsge2wnVQN4j .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DpklAsge2wnVQN4j .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DpklAsge2wnVQN4j .error-icon{fill:#552222;}#mermaid-svg-DpklAsge2wnVQN4j .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DpklAsge2wnVQN4j .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DpklAsge2wnVQN4j .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DpklAsge2wnVQN4j .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DpklAsge2wnVQN4j .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DpklAsge2wnVQN4j .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DpklAsge2wnVQN4j .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DpklAsge2wnVQN4j .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DpklAsge2wnVQN4j .marker.cross{stroke:#333333;}#mermaid-svg-DpklAsge2wnVQN4j svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DpklAsge2wnVQN4j p{margin:0;}#mermaid-svg-DpklAsge2wnVQN4j .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DpklAsge2wnVQN4j .cluster-label text{fill:#333;}#mermaid-svg-DpklAsge2wnVQN4j .cluster-label span{color:#333;}#mermaid-svg-DpklAsge2wnVQN4j .cluster-label span p{background-color:transparent;}#mermaid-svg-DpklAsge2wnVQN4j .label text,#mermaid-svg-DpklAsge2wnVQN4j span{fill:#333;color:#333;}#mermaid-svg-DpklAsge2wnVQN4j .node rect,#mermaid-svg-DpklAsge2wnVQN4j .node circle,#mermaid-svg-DpklAsge2wnVQN4j .node ellipse,#mermaid-svg-DpklAsge2wnVQN4j .node polygon,#mermaid-svg-DpklAsge2wnVQN4j .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DpklAsge2wnVQN4j .rough-node .label text,#mermaid-svg-DpklAsge2wnVQN4j .node .label text,#mermaid-svg-DpklAsge2wnVQN4j .image-shape .label,#mermaid-svg-DpklAsge2wnVQN4j .icon-shape .label{text-anchor:middle;}#mermaid-svg-DpklAsge2wnVQN4j .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DpklAsge2wnVQN4j .rough-node .label,#mermaid-svg-DpklAsge2wnVQN4j .node .label,#mermaid-svg-DpklAsge2wnVQN4j .image-shape .label,#mermaid-svg-DpklAsge2wnVQN4j .icon-shape .label{text-align:center;}#mermaid-svg-DpklAsge2wnVQN4j .node.clickable{cursor:pointer;}#mermaid-svg-DpklAsge2wnVQN4j .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DpklAsge2wnVQN4j .arrowheadPath{fill:#333333;}#mermaid-svg-DpklAsge2wnVQN4j .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DpklAsge2wnVQN4j .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DpklAsge2wnVQN4j .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DpklAsge2wnVQN4j .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DpklAsge2wnVQN4j .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DpklAsge2wnVQN4j .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DpklAsge2wnVQN4j .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DpklAsge2wnVQN4j .cluster text{fill:#333;}#mermaid-svg-DpklAsge2wnVQN4j .cluster span{color:#333;}#mermaid-svg-DpklAsge2wnVQN4j div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DpklAsge2wnVQN4j .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DpklAsge2wnVQN4j rect.text{fill:none;stroke-width:0;}#mermaid-svg-DpklAsge2wnVQN4j .icon-shape,#mermaid-svg-DpklAsge2wnVQN4j .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DpklAsge2wnVQN4j .icon-shape p,#mermaid-svg-DpklAsge2wnVQN4j .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DpklAsge2wnVQN4j .icon-shape .label rect,#mermaid-svg-DpklAsge2wnVQN4j .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DpklAsge2wnVQN4j .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DpklAsge2wnVQN4j .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DpklAsge2wnVQN4j :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不依赖任何上层
engine-starter

启动模块
engine-api

API层
engine-infrastructure

基础设施层
engine-core

领域层+业务层

关键点engine-core 不依赖 apiinfrastructure------它不知道 HTTP 和数据库的存在。这是信息隐藏的物理保障。


六、三大案例场景深度拆解

案例一:从单体到模块化------iPaaS 连接器体系

背景:早期 iPaaS 的连接器代码全部写在 engine 内部,随着连接器数量从 50 增长到 600+,engine 变成了一个 5 万行的巨型模块。

问题
#mermaid-svg-o4aIKiJEZRoHxt8X{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-o4aIKiJEZRoHxt8X .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-o4aIKiJEZRoHxt8X .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-o4aIKiJEZRoHxt8X .error-icon{fill:#552222;}#mermaid-svg-o4aIKiJEZRoHxt8X .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-o4aIKiJEZRoHxt8X .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-o4aIKiJEZRoHxt8X .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-o4aIKiJEZRoHxt8X .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-o4aIKiJEZRoHxt8X .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-o4aIKiJEZRoHxt8X .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-o4aIKiJEZRoHxt8X .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-o4aIKiJEZRoHxt8X .marker{fill:#333333;stroke:#333333;}#mermaid-svg-o4aIKiJEZRoHxt8X .marker.cross{stroke:#333333;}#mermaid-svg-o4aIKiJEZRoHxt8X svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-o4aIKiJEZRoHxt8X p{margin:0;}#mermaid-svg-o4aIKiJEZRoHxt8X .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-o4aIKiJEZRoHxt8X .cluster-label text{fill:#333;}#mermaid-svg-o4aIKiJEZRoHxt8X .cluster-label span{color:#333;}#mermaid-svg-o4aIKiJEZRoHxt8X .cluster-label span p{background-color:transparent;}#mermaid-svg-o4aIKiJEZRoHxt8X .label text,#mermaid-svg-o4aIKiJEZRoHxt8X span{fill:#333;color:#333;}#mermaid-svg-o4aIKiJEZRoHxt8X .node rect,#mermaid-svg-o4aIKiJEZRoHxt8X .node circle,#mermaid-svg-o4aIKiJEZRoHxt8X .node ellipse,#mermaid-svg-o4aIKiJEZRoHxt8X .node polygon,#mermaid-svg-o4aIKiJEZRoHxt8X .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-o4aIKiJEZRoHxt8X .rough-node .label text,#mermaid-svg-o4aIKiJEZRoHxt8X .node .label text,#mermaid-svg-o4aIKiJEZRoHxt8X .image-shape .label,#mermaid-svg-o4aIKiJEZRoHxt8X .icon-shape .label{text-anchor:middle;}#mermaid-svg-o4aIKiJEZRoHxt8X .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-o4aIKiJEZRoHxt8X .rough-node .label,#mermaid-svg-o4aIKiJEZRoHxt8X .node .label,#mermaid-svg-o4aIKiJEZRoHxt8X .image-shape .label,#mermaid-svg-o4aIKiJEZRoHxt8X .icon-shape .label{text-align:center;}#mermaid-svg-o4aIKiJEZRoHxt8X .node.clickable{cursor:pointer;}#mermaid-svg-o4aIKiJEZRoHxt8X .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-o4aIKiJEZRoHxt8X .arrowheadPath{fill:#333333;}#mermaid-svg-o4aIKiJEZRoHxt8X .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-o4aIKiJEZRoHxt8X .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-o4aIKiJEZRoHxt8X .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o4aIKiJEZRoHxt8X .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-o4aIKiJEZRoHxt8X .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o4aIKiJEZRoHxt8X .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-o4aIKiJEZRoHxt8X .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-o4aIKiJEZRoHxt8X .cluster text{fill:#333;}#mermaid-svg-o4aIKiJEZRoHxt8X .cluster span{color:#333;}#mermaid-svg-o4aIKiJEZRoHxt8X div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-o4aIKiJEZRoHxt8X .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-o4aIKiJEZRoHxt8X rect.text{fill:none;stroke-width:0;}#mermaid-svg-o4aIKiJEZRoHxt8X .icon-shape,#mermaid-svg-o4aIKiJEZRoHxt8X .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o4aIKiJEZRoHxt8X .icon-shape p,#mermaid-svg-o4aIKiJEZRoHxt8X .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-o4aIKiJEZRoHxt8X .icon-shape .label rect,#mermaid-svg-o4aIKiJEZRoHxt8X .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o4aIKiJEZRoHxt8X .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-o4aIKiJEZRoHxt8X .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-o4aIKiJEZRoHxt8X :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 改造前-大泥球
engine

50000行

流程引擎+所有连接器混在一起
MySQL

连接器配置

流程数据

用户数据

  • 新增一个连接器要改 engine 的代码 → 发布 engine
  • 一个连接器的 bug 可能导致 engine 崩溃
  • 无法单独测试一个连接器

改造:拆分为三层模块化架构
#mermaid-svg-NbzzDh5vtxSgSsIc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NbzzDh5vtxSgSsIc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NbzzDh5vtxSgSsIc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NbzzDh5vtxSgSsIc .error-icon{fill:#552222;}#mermaid-svg-NbzzDh5vtxSgSsIc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NbzzDh5vtxSgSsIc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NbzzDh5vtxSgSsIc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NbzzDh5vtxSgSsIc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NbzzDh5vtxSgSsIc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NbzzDh5vtxSgSsIc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NbzzDh5vtxSgSsIc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NbzzDh5vtxSgSsIc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NbzzDh5vtxSgSsIc .marker.cross{stroke:#333333;}#mermaid-svg-NbzzDh5vtxSgSsIc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NbzzDh5vtxSgSsIc p{margin:0;}#mermaid-svg-NbzzDh5vtxSgSsIc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NbzzDh5vtxSgSsIc .cluster-label text{fill:#333;}#mermaid-svg-NbzzDh5vtxSgSsIc .cluster-label span{color:#333;}#mermaid-svg-NbzzDh5vtxSgSsIc .cluster-label span p{background-color:transparent;}#mermaid-svg-NbzzDh5vtxSgSsIc .label text,#mermaid-svg-NbzzDh5vtxSgSsIc span{fill:#333;color:#333;}#mermaid-svg-NbzzDh5vtxSgSsIc .node rect,#mermaid-svg-NbzzDh5vtxSgSsIc .node circle,#mermaid-svg-NbzzDh5vtxSgSsIc .node ellipse,#mermaid-svg-NbzzDh5vtxSgSsIc .node polygon,#mermaid-svg-NbzzDh5vtxSgSsIc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NbzzDh5vtxSgSsIc .rough-node .label text,#mermaid-svg-NbzzDh5vtxSgSsIc .node .label text,#mermaid-svg-NbzzDh5vtxSgSsIc .image-shape .label,#mermaid-svg-NbzzDh5vtxSgSsIc .icon-shape .label{text-anchor:middle;}#mermaid-svg-NbzzDh5vtxSgSsIc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NbzzDh5vtxSgSsIc .rough-node .label,#mermaid-svg-NbzzDh5vtxSgSsIc .node .label,#mermaid-svg-NbzzDh5vtxSgSsIc .image-shape .label,#mermaid-svg-NbzzDh5vtxSgSsIc .icon-shape .label{text-align:center;}#mermaid-svg-NbzzDh5vtxSgSsIc .node.clickable{cursor:pointer;}#mermaid-svg-NbzzDh5vtxSgSsIc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NbzzDh5vtxSgSsIc .arrowheadPath{fill:#333333;}#mermaid-svg-NbzzDh5vtxSgSsIc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NbzzDh5vtxSgSsIc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NbzzDh5vtxSgSsIc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NbzzDh5vtxSgSsIc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NbzzDh5vtxSgSsIc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NbzzDh5vtxSgSsIc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NbzzDh5vtxSgSsIc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NbzzDh5vtxSgSsIc .cluster text{fill:#333;}#mermaid-svg-NbzzDh5vtxSgSsIc .cluster span{color:#333;}#mermaid-svg-NbzzDh5vtxSgSsIc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NbzzDh5vtxSgSsIc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NbzzDh5vtxSgSsIc rect.text{fill:none;stroke-width:0;}#mermaid-svg-NbzzDh5vtxSgSsIc .icon-shape,#mermaid-svg-NbzzDh5vtxSgSsIc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NbzzDh5vtxSgSsIc .icon-shape p,#mermaid-svg-NbzzDh5vtxSgSsIc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NbzzDh5vtxSgSsIc .icon-shape .label rect,#mermaid-svg-NbzzDh5vtxSgSsIc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NbzzDh5vtxSgSsIc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NbzzDh5vtxSgSsIc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NbzzDh5vtxSgSsIc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 改造后-三层模块化
依赖接口
RPC调用
提供Schema
engine

流程引擎核心

只有执行逻辑
app-libs

连接器定义库

Schema + 元数据
连接器执行器

每个连接器一个Adapter

模块化收益

维度 改造前 改造后
新增连接器 改 engine → 全量发布 加 Adapter → 热加载
连接器 bug 影响 可能拖垮 engine 只影响该连接器
单测 需要 mock 整个 engine 只需 mock ConnectorExecutor 接口
代码量 engine 5 万行 engine 8 千行 + 连接器分散

案例二:模块间通信从同步到异步------日志写入重构

背景:流程执行过程中,每个节点的执行日志需要同步写入数据库。

改造前
MySQL 流程引擎 MySQL 流程引擎 #mermaid-svg-V3JcY4Jnn3deaxJN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V3JcY4Jnn3deaxJN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V3JcY4Jnn3deaxJN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V3JcY4Jnn3deaxJN .error-icon{fill:#552222;}#mermaid-svg-V3JcY4Jnn3deaxJN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V3JcY4Jnn3deaxJN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V3JcY4Jnn3deaxJN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V3JcY4Jnn3deaxJN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V3JcY4Jnn3deaxJN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V3JcY4Jnn3deaxJN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V3JcY4Jnn3deaxJN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V3JcY4Jnn3deaxJN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V3JcY4Jnn3deaxJN .marker.cross{stroke:#333333;}#mermaid-svg-V3JcY4Jnn3deaxJN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V3JcY4Jnn3deaxJN p{margin:0;}#mermaid-svg-V3JcY4Jnn3deaxJN .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-V3JcY4Jnn3deaxJN text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-V3JcY4Jnn3deaxJN .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-V3JcY4Jnn3deaxJN .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-V3JcY4Jnn3deaxJN .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-V3JcY4Jnn3deaxJN .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-V3JcY4Jnn3deaxJN #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-V3JcY4Jnn3deaxJN .sequenceNumber{fill:white;}#mermaid-svg-V3JcY4Jnn3deaxJN #sequencenumber{fill:#333;}#mermaid-svg-V3JcY4Jnn3deaxJN #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-V3JcY4Jnn3deaxJN .messageText{fill:#333;stroke:none;}#mermaid-svg-V3JcY4Jnn3deaxJN .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-V3JcY4Jnn3deaxJN .labelText,#mermaid-svg-V3JcY4Jnn3deaxJN .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-V3JcY4Jnn3deaxJN .loopText,#mermaid-svg-V3JcY4Jnn3deaxJN .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-V3JcY4Jnn3deaxJN .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-V3JcY4Jnn3deaxJN .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-V3JcY4Jnn3deaxJN .noteText,#mermaid-svg-V3JcY4Jnn3deaxJN .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-V3JcY4Jnn3deaxJN .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-V3JcY4Jnn3deaxJN .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-V3JcY4Jnn3deaxJN .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-V3JcY4Jnn3deaxJN .actorPopupMenu{position:absolute;}#mermaid-svg-V3JcY4Jnn3deaxJN .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-V3JcY4Jnn3deaxJN .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-V3JcY4Jnn3deaxJN .actor-man circle,#mermaid-svg-V3JcY4Jnn3deaxJN line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-V3JcY4Jnn3deaxJN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop每个节点执行 INSERT 执行日志OK继续下一个节点

问题:每个节点多一次数据库 IO,100 个节点的流程 = 100 次日志写入 = 性能瓶颈。

改造后:模块化 + 异步通信
Elasticsearch middleware服务 RocketMQ 本地缓冲 流程引擎 Elasticsearch middleware服务 RocketMQ 本地缓冲 流程引擎 #mermaid-svg-4o7DVuw7561TO406{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4o7DVuw7561TO406 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4o7DVuw7561TO406 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4o7DVuw7561TO406 .error-icon{fill:#552222;}#mermaid-svg-4o7DVuw7561TO406 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4o7DVuw7561TO406 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4o7DVuw7561TO406 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4o7DVuw7561TO406 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4o7DVuw7561TO406 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4o7DVuw7561TO406 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4o7DVuw7561TO406 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4o7DVuw7561TO406 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4o7DVuw7561TO406 .marker.cross{stroke:#333333;}#mermaid-svg-4o7DVuw7561TO406 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4o7DVuw7561TO406 p{margin:0;}#mermaid-svg-4o7DVuw7561TO406 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4o7DVuw7561TO406 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-4o7DVuw7561TO406 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-4o7DVuw7561TO406 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-4o7DVuw7561TO406 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-4o7DVuw7561TO406 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-4o7DVuw7561TO406 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-4o7DVuw7561TO406 .sequenceNumber{fill:white;}#mermaid-svg-4o7DVuw7561TO406 #sequencenumber{fill:#333;}#mermaid-svg-4o7DVuw7561TO406 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-4o7DVuw7561TO406 .messageText{fill:#333;stroke:none;}#mermaid-svg-4o7DVuw7561TO406 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4o7DVuw7561TO406 .labelText,#mermaid-svg-4o7DVuw7561TO406 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-4o7DVuw7561TO406 .loopText,#mermaid-svg-4o7DVuw7561TO406 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-4o7DVuw7561TO406 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-4o7DVuw7561TO406 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-4o7DVuw7561TO406 .noteText,#mermaid-svg-4o7DVuw7561TO406 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-4o7DVuw7561TO406 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4o7DVuw7561TO406 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4o7DVuw7561TO406 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4o7DVuw7561TO406 .actorPopupMenu{position:absolute;}#mermaid-svg-4o7DVuw7561TO406 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-4o7DVuw7561TO406 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4o7DVuw7561TO406 .actor-man circle,#mermaid-svg-4o7DVuw7561TO406 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-4o7DVuw7561TO406 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop每个节点执行 写入本地缓冲立即继续下一个节点批量投递(每100条或每3秒)消费消息批量写入ES

模块化设计的关键决策

  1. 日志模块与执行模块解耦:引擎只管写缓冲,不管日志最终存哪
  2. 通信方式选择 MQ:异步、可靠、可削峰
  3. 中间件服务独立:日志的存储策略变化不影响引擎

案例三:模块的可插拔设计------Feature Flag 模块化

背景:新功能上线需要灰度发布,不同客户启用不同功能。

设计:Feature Flag 作为独立的横切模块
#mermaid-svg-K34Db3VxBREScKdh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K34Db3VxBREScKdh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K34Db3VxBREScKdh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K34Db3VxBREScKdh .error-icon{fill:#552222;}#mermaid-svg-K34Db3VxBREScKdh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K34Db3VxBREScKdh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K34Db3VxBREScKdh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K34Db3VxBREScKdh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K34Db3VxBREScKdh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K34Db3VxBREScKdh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K34Db3VxBREScKdh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K34Db3VxBREScKdh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K34Db3VxBREScKdh .marker.cross{stroke:#333333;}#mermaid-svg-K34Db3VxBREScKdh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K34Db3VxBREScKdh p{margin:0;}#mermaid-svg-K34Db3VxBREScKdh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-K34Db3VxBREScKdh .cluster-label text{fill:#333;}#mermaid-svg-K34Db3VxBREScKdh .cluster-label span{color:#333;}#mermaid-svg-K34Db3VxBREScKdh .cluster-label span p{background-color:transparent;}#mermaid-svg-K34Db3VxBREScKdh .label text,#mermaid-svg-K34Db3VxBREScKdh span{fill:#333;color:#333;}#mermaid-svg-K34Db3VxBREScKdh .node rect,#mermaid-svg-K34Db3VxBREScKdh .node circle,#mermaid-svg-K34Db3VxBREScKdh .node ellipse,#mermaid-svg-K34Db3VxBREScKdh .node polygon,#mermaid-svg-K34Db3VxBREScKdh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K34Db3VxBREScKdh .rough-node .label text,#mermaid-svg-K34Db3VxBREScKdh .node .label text,#mermaid-svg-K34Db3VxBREScKdh .image-shape .label,#mermaid-svg-K34Db3VxBREScKdh .icon-shape .label{text-anchor:middle;}#mermaid-svg-K34Db3VxBREScKdh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-K34Db3VxBREScKdh .rough-node .label,#mermaid-svg-K34Db3VxBREScKdh .node .label,#mermaid-svg-K34Db3VxBREScKdh .image-shape .label,#mermaid-svg-K34Db3VxBREScKdh .icon-shape .label{text-align:center;}#mermaid-svg-K34Db3VxBREScKdh .node.clickable{cursor:pointer;}#mermaid-svg-K34Db3VxBREScKdh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-K34Db3VxBREScKdh .arrowheadPath{fill:#333333;}#mermaid-svg-K34Db3VxBREScKdh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K34Db3VxBREScKdh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K34Db3VxBREScKdh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K34Db3VxBREScKdh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-K34Db3VxBREScKdh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K34Db3VxBREScKdh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-K34Db3VxBREScKdh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K34Db3VxBREScKdh .cluster text{fill:#333;}#mermaid-svg-K34Db3VxBREScKdh .cluster span{color:#333;}#mermaid-svg-K34Db3VxBREScKdh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K34Db3VxBREScKdh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-K34Db3VxBREScKdh rect.text{fill:none;stroke-width:0;}#mermaid-svg-K34Db3VxBREScKdh .icon-shape,#mermaid-svg-K34Db3VxBREScKdh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K34Db3VxBREScKdh .icon-shape p,#mermaid-svg-K34Db3VxBREScKdh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-K34Db3VxBREScKdh .icon-shape .label rect,#mermaid-svg-K34Db3VxBREScKdh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K34Db3VxBREScKdh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-K34Db3VxBREScKdh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-K34Db3VxBREScKdh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务模块B
业务模块A
Feature Flag模块
新功能入口
新功能入口
FlagService

判断功能是否开启
Nacos配置中心

存储Flag状态
流程引擎
连接器服务

java 复制代码
// Feature Flag 模块提供的接口
public interface FeatureFlagService {
    boolean isEnabled(String featureKey);
    boolean isEnabledForTenant(String featureKey, String tenantId);
}

// 业务模块使用
@Service
public class FlowExecutionService {
    @Autowired
    private FeatureFlagService flagService;

    public void execute(FlowInstance instance) {
        // 灰度功能:并行节点执行
        if (flagService.isEnabledForTenant("parallel-node-execution", instance.getTenantId())) {
            executeParallel(instance);
        } else {
            executeSequential(instance); // 老逻辑
        }
    }
}

模块化价值 :Feature Flag 是一个独立的、可插拔的横切模块,任何业务模块都可以使用,但 Feature Flag 模块不依赖任何业务模块。


七、模块化设计的反模式清单

7.1 架构级反模式

反模式 症状 根因 解法
大泥球 所有代码在一个项目里 没有模块化意识 按业务域/流量拆分
金锤 所有问题都用同一种技术解决 技术视野窄 根据场景选型
意大利面 模块之间网状依赖 没有分层和接口 引入接口+依赖倒置
微服务微灾难 拆了 30 个服务但只有 5 个人 过度拆分 按团队规模控制服务数
分布式单体 名义上微服务,实际上一起部署一起挂 没有做好独立部署和容错 独立部署 + 熔断降级

7.2 代码级反模式

反模式 症状 解法
Controller 穿透 Controller 直接调 DAO 强制走 Service 层
循环依赖 A 依赖 B,B 依赖 A 抽接口/引入事件/重新划分边界
共享数据库 多个服务直接读写同一张表 每个服务自己的表,通过 API 或事件同步
工具类黑洞 Utils.java 越来越大,啥都往里塞 按职责拆分成多个小工具类
配置散落 同一个配置在多个模块中硬编码 配置中心化(Nacos/Apollo)

7.3 循环依赖的三种解法

#mermaid-svg-gf1B1lBWtRHlxuv6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gf1B1lBWtRHlxuv6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gf1B1lBWtRHlxuv6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gf1B1lBWtRHlxuv6 .error-icon{fill:#552222;}#mermaid-svg-gf1B1lBWtRHlxuv6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gf1B1lBWtRHlxuv6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gf1B1lBWtRHlxuv6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gf1B1lBWtRHlxuv6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gf1B1lBWtRHlxuv6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gf1B1lBWtRHlxuv6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gf1B1lBWtRHlxuv6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gf1B1lBWtRHlxuv6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gf1B1lBWtRHlxuv6 .marker.cross{stroke:#333333;}#mermaid-svg-gf1B1lBWtRHlxuv6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gf1B1lBWtRHlxuv6 p{margin:0;}#mermaid-svg-gf1B1lBWtRHlxuv6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gf1B1lBWtRHlxuv6 .cluster-label text{fill:#333;}#mermaid-svg-gf1B1lBWtRHlxuv6 .cluster-label span{color:#333;}#mermaid-svg-gf1B1lBWtRHlxuv6 .cluster-label span p{background-color:transparent;}#mermaid-svg-gf1B1lBWtRHlxuv6 .label text,#mermaid-svg-gf1B1lBWtRHlxuv6 span{fill:#333;color:#333;}#mermaid-svg-gf1B1lBWtRHlxuv6 .node rect,#mermaid-svg-gf1B1lBWtRHlxuv6 .node circle,#mermaid-svg-gf1B1lBWtRHlxuv6 .node ellipse,#mermaid-svg-gf1B1lBWtRHlxuv6 .node polygon,#mermaid-svg-gf1B1lBWtRHlxuv6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gf1B1lBWtRHlxuv6 .rough-node .label text,#mermaid-svg-gf1B1lBWtRHlxuv6 .node .label text,#mermaid-svg-gf1B1lBWtRHlxuv6 .image-shape .label,#mermaid-svg-gf1B1lBWtRHlxuv6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-gf1B1lBWtRHlxuv6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gf1B1lBWtRHlxuv6 .rough-node .label,#mermaid-svg-gf1B1lBWtRHlxuv6 .node .label,#mermaid-svg-gf1B1lBWtRHlxuv6 .image-shape .label,#mermaid-svg-gf1B1lBWtRHlxuv6 .icon-shape .label{text-align:center;}#mermaid-svg-gf1B1lBWtRHlxuv6 .node.clickable{cursor:pointer;}#mermaid-svg-gf1B1lBWtRHlxuv6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gf1B1lBWtRHlxuv6 .arrowheadPath{fill:#333333;}#mermaid-svg-gf1B1lBWtRHlxuv6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gf1B1lBWtRHlxuv6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gf1B1lBWtRHlxuv6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gf1B1lBWtRHlxuv6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gf1B1lBWtRHlxuv6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gf1B1lBWtRHlxuv6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gf1B1lBWtRHlxuv6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gf1B1lBWtRHlxuv6 .cluster text{fill:#333;}#mermaid-svg-gf1B1lBWtRHlxuv6 .cluster span{color:#333;}#mermaid-svg-gf1B1lBWtRHlxuv6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gf1B1lBWtRHlxuv6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gf1B1lBWtRHlxuv6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-gf1B1lBWtRHlxuv6 .icon-shape,#mermaid-svg-gf1B1lBWtRHlxuv6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gf1B1lBWtRHlxuv6 .icon-shape p,#mermaid-svg-gf1B1lBWtRHlxuv6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gf1B1lBWtRHlxuv6 .icon-shape .label rect,#mermaid-svg-gf1B1lBWtRHlxuv6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gf1B1lBWtRHlxuv6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gf1B1lBWtRHlxuv6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gf1B1lBWtRHlxuv6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 解法3-重新划界
新模块C

合并A和B的共同部分
解法2-事件驱动
发布事件
订阅
模块A
事件总线
模块B
解法1-抽接口
依赖
实现
模块A
接口模块
模块B
问题-循环依赖
模块A
模块B


八、模块化演进的四个阶段

#mermaid-svg-mCF9ajqihi3eUAV0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mCF9ajqihi3eUAV0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mCF9ajqihi3eUAV0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mCF9ajqihi3eUAV0 .error-icon{fill:#552222;}#mermaid-svg-mCF9ajqihi3eUAV0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mCF9ajqihi3eUAV0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mCF9ajqihi3eUAV0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mCF9ajqihi3eUAV0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mCF9ajqihi3eUAV0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mCF9ajqihi3eUAV0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mCF9ajqihi3eUAV0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mCF9ajqihi3eUAV0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mCF9ajqihi3eUAV0 .marker.cross{stroke:#333333;}#mermaid-svg-mCF9ajqihi3eUAV0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mCF9ajqihi3eUAV0 p{margin:0;}#mermaid-svg-mCF9ajqihi3eUAV0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mCF9ajqihi3eUAV0 .cluster-label text{fill:#333;}#mermaid-svg-mCF9ajqihi3eUAV0 .cluster-label span{color:#333;}#mermaid-svg-mCF9ajqihi3eUAV0 .cluster-label span p{background-color:transparent;}#mermaid-svg-mCF9ajqihi3eUAV0 .label text,#mermaid-svg-mCF9ajqihi3eUAV0 span{fill:#333;color:#333;}#mermaid-svg-mCF9ajqihi3eUAV0 .node rect,#mermaid-svg-mCF9ajqihi3eUAV0 .node circle,#mermaid-svg-mCF9ajqihi3eUAV0 .node ellipse,#mermaid-svg-mCF9ajqihi3eUAV0 .node polygon,#mermaid-svg-mCF9ajqihi3eUAV0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mCF9ajqihi3eUAV0 .rough-node .label text,#mermaid-svg-mCF9ajqihi3eUAV0 .node .label text,#mermaid-svg-mCF9ajqihi3eUAV0 .image-shape .label,#mermaid-svg-mCF9ajqihi3eUAV0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-mCF9ajqihi3eUAV0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mCF9ajqihi3eUAV0 .rough-node .label,#mermaid-svg-mCF9ajqihi3eUAV0 .node .label,#mermaid-svg-mCF9ajqihi3eUAV0 .image-shape .label,#mermaid-svg-mCF9ajqihi3eUAV0 .icon-shape .label{text-align:center;}#mermaid-svg-mCF9ajqihi3eUAV0 .node.clickable{cursor:pointer;}#mermaid-svg-mCF9ajqihi3eUAV0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mCF9ajqihi3eUAV0 .arrowheadPath{fill:#333333;}#mermaid-svg-mCF9ajqihi3eUAV0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mCF9ajqihi3eUAV0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mCF9ajqihi3eUAV0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mCF9ajqihi3eUAV0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mCF9ajqihi3eUAV0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mCF9ajqihi3eUAV0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mCF9ajqihi3eUAV0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mCF9ajqihi3eUAV0 .cluster text{fill:#333;}#mermaid-svg-mCF9ajqihi3eUAV0 .cluster span{color:#333;}#mermaid-svg-mCF9ajqihi3eUAV0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mCF9ajqihi3eUAV0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mCF9ajqihi3eUAV0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-mCF9ajqihi3eUAV0 .icon-shape,#mermaid-svg-mCF9ajqihi3eUAV0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mCF9ajqihi3eUAV0 .icon-shape p,#mermaid-svg-mCF9ajqihi3eUAV0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mCF9ajqihi3eUAV0 .icon-shape .label rect,#mermaid-svg-mCF9ajqihi3eUAV0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mCF9ajqihi3eUAV0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mCF9ajqihi3eUAV0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mCF9ajqihi3eUAV0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务增长
流量瓶颈
生态需求
阶段1

单体+分层

1个项目 4层
阶段2

模块化单体

1个项目 N个模块
阶段3

微服务

N个独立服务
阶段4

平台化

服务+中台+开放API

阶段 特征 适用规模 iPaaS 历程
阶段 1 一个项目,内部按包分层 初创期 0~5 人 MVP 验证阶段
阶段 2 一个项目,Maven 多模块物理隔离 成长期 5~15 人 连接器体系拆分
阶段 3 多个独立部署的服务 规模化 15+ 人 engine/gateway/event-center 独立部署
阶段 4 服务 + 开放平台 + 生态 平台化 iPaaS 开放 API + 连接器市场

关键认知:不要跳阶段。团队 5 个人时搞微服务 = 灾难。先在单体里做好模块化(阶段 2),等流量和团队到了规模再拆微服务(阶段 3)。


九、实操 Checklist

每次做模块化设计时,用这份清单自查:

模块边界

  • 每个模块能用一句话说清楚"我是做什么的"
  • 每个模块只有一个改变的理由
  • 模块之间没有循环依赖
  • 模块之间的通信方式已明确(同步/异步)

模块内部

  • 严格四层分层(API → Service → Domain → Infrastructure)
  • 领域层不依赖基础设施层
  • 没有 Controller 穿透调 DAO
  • 没有超过 500 行的"上帝类"

模块接口

  • 接口小而精,没有"上帝接口"
  • 内部实现对外不可见(private/包可见)
  • 接口有版本控制策略

可测试性

  • 每个模块可以独立单测(mock 掉外部依赖)
  • 单测不需要启动整个系统
  • 模块间集成有集成测试保障

可演进性

  • 一个模块可以被独立替换(比如换一个存储实现)
  • 新增功能可以通过"加模块"而非"改模块"实现
  • 模块边界可以随业务演进而调整

十、常见问题(FAQ)

Q:模块化和微服务是什么关系?

A:模块化是思想,微服务是模块化在部署层面的一种实现。微服务是"物理模块化"------每个模块独立部署、独立扩展。但模块化不等于微服务------一个单体项目里做好 Maven 多模块也是模块化。先在代码层面做好模块化,再考虑是否拆微服务。

Q:模块拆得太细怎么办?

A:合并。判断标准:如果两个模块总是一起改、一起发版、一起测试------说明它们应该是一个模块。过度拆分和拆分不足都有害,关键找到"变更一致性"的边界。

Q:循环依赖一定要消除吗?

A:一定要。循环依赖意味着两个模块"互为前提"------任何一个都不能独立理解、独立测试、独立部署。解法三种:抽接口、事件驱动、重新划分边界。

Q:模块化的代价是什么?

A:增加了间接性。原本一个方法调用的事,变成了跨模块的 API 调用 / 消息通信。带来了额外的序列化开销、网络开销、调试复杂度。所以不要过早模块化------先跑通业务,在复杂度到了再做。

Q:康威定律和模块化有什么关系?

A:康威定律说"系统的架构反映组织结构"。如果你的团队按业务域分成 3 个小组,系统自然会长成 3 个模块。反过来也成立------你想让系统长成什么样子,就把团队按那个方式组织。架构设计和组织设计要同步进行。


十一、小结

结构化与模块化设计,本质上是管理复杂度的方法论

三句话总结:

  1. 高内聚低耦合是北极星------所有设计决策都围绕这个目标
  2. 先拆清楚再写代码------模块边界比实现细节更重要
  3. 模块化是演进而非革命------从单体分层开始,逐步走向微服务

好的模块化设计,让系统的每个部分都能独立理解、独立开发、独立测试、独立部署、独立演进。这六个"独立",就是模块化成功与否的终极判断标准。


标签:#架构设计 #模块化设计 #结构化设计 #高内聚低耦合 #微服务 #DDD #分层架构 #依赖倒置 #接口隔离 #Java #Spring #iPaaS #软件工程 #技术架构

相关推荐
mounter6251 小时前
破局与守正:eBPF 在 Linux 内存管理中的应用、演进与重构构想
linux·服务器·网络·mmu·ebpf·linux kernel
STDD1 小时前
Linux Namespace:容器隔离的底层原理,PID、网络、挂载隔离实战
linux·运维·网络
Devin~Y1 小时前
智慧物流+AIGC客服Java大厂面试:Spring Boot、Kafka、Redis、JVM与RAG Agent实战
java·jvm·spring boot·redis·spring cloud·kafka·rag
todoitbo1 小时前
一台 2C2G 服务器上的 KingbaseES 安装记录
运维·服务器·数据库·国产数据库
Demon1_Coder1 小时前
智能体的自定义工具
java·linux·前端
轻帆向远1 小时前
Debian 旧版源配置指南:国内镜像加速与 archive.debian.org 替代方案
网络·debian·apt
原创小甜甜1 小时前
OOM 排查复盘:Hutool 序列化 Request 导致 Java Heap Space
java·开发语言·python
列星随旋1 小时前
矩阵快速幂
java·算法·矩阵
闪电悠米1 小时前
黑马点评-分布式锁-02_simple_redis_lock_setnx
java·数据库·spring boot·redis·分布式·缓存·wpf