背景与痛点
在一家互联网公司中,最初团队为了代码复用,将所有业务通用逻辑打包成一个共享 JAR(公共库),并被多个业务线 A、B、C 引用。
随着业务的发展,不同线上的需求出现差异:A 线需要的特性被硬塞进公共库,同样的改动也带给了 B、C。虽然 A 线的 RD 修改后,A 的 QA 进行了验证并上线,但 QA 只关注 A 线场景,无视对 B、C 的影响,最终导致"一个业务上线,多个业务宕机"的连锁故障。
核心问题是:公共库承载了过多个性化逻辑,反而毁掉了最初的通用性,造成多业务深度耦合,测试覆盖不完整,风险高且事故频发。
现代解耦思路
总体目标:将稳定、核心 的共性逻辑与易变、个性逻辑彻底分离,做到低耦合、高内聚,安全可控。下面引入三种更现代化且可视化的方案。
方案一:模块化单体(Modular Monolith)
-
思路:在同一个仓库与部署单元内,通过分域(Domain)与模块(Module)划分------模块内代码可自由改动,模块间仅通过明确接口交互。
-
实现方式:
- 基于 领域驱动设计 (DDD),为 A、B、C 各自建立
Domain Module
。 - 公共基础设施(工具、公共 DTO、通用工具类)放在
Core Module
。 - Release 时,通过构建工具(Maven/Gradle)按模块聚合产出单体应用。
- 基于 领域驱动设计 (DDD),为 A、B、C 各自建立
bash
modular-monolith/
├── pom.xml ← 父工程(Aggregator POM)
├── core/ ← Core Module(公共协议、DTO、工具)
│ └── pom.xml
│ └── src/main/java/...
│ └── com/example/core/
│ ├── dto/ ← 通用 DTO
│ ├── util/ ← 通用工具类
│ └── config/ ← 公共配置(如 Swagger、拦截器等)
├── module-a/ ← A-Module(领域 A 逻辑)
│ └── pom.xml
│ └── src/main/java/...
│ └── com/example/a/
│ ├── api/ ← A 模块对外接口(Service 接口)
│ ├── application/ ← 用例层(Use Case)
│ ├── domain/ ← 领域层(Entity / VO / Repository 接口)
│ └── infrastructure/ ← 基础设施层(Repository 实现、FeignClient 等)
├── module-b/ ... ← B-Module(同理)
└── module-c/ ... ← C-Module(同理)
%% 模块化单体示意
flowchart LR
subgraph 单体应用
Core[Core Module 工具公共协议]
A[A-Module 业务A逻辑]
B[B-Module 业务B逻辑]
C[C-Module 业务C逻辑]
end
Core --> A
Core --> B
Core --> C
A --> DB1((DB-A))
B --> DB2((DB-B))
C --> DB3((DB-C))
style Core fill:#f0f8ff,stroke:#0066cc,stroke-width:2px
style A fill:#e6ffe6,stroke:#009933
style B fill:#fff5e6,stroke:#ff9933
style C fill:#ffe6f2,stroke:#cc0066
- 优点:部署统一、维护简单;模块边界清晰;易于在单体基础上渐进拆分。
- 适用场景:团队规模中等,对服务化拆分成本较高时。常用作从公共库走向微服务的过渡。
方案二:插件化架构(Plug-in Architecture)
-
思路 :核心框架提供可扩展的 插件加载机制,每个业务个性化逻辑通过插件动态加载,无需改动核心框架。
-
实现方式:
- 定义插件接口(
Plugin API
),约定初始化、执行、销毁等生命周期方法。 - 各业务将定制逻辑封装为独立插件包,按需部署到插件目录。
- 启动时,核心框架扫描并加载所有插件,实现运行时隔离。
- 定义插件接口(
%% 插件化架构示意
flowchart TD
subgraph 核心框架
Core[Core Service]
Loader(Plugin Loader)
end
subgraph 插件目录
P1[Plugin A]
P2[Plugin B]
P3[Plugin C]
end
Core --> Loader
Loader --> P1 & P2 & P3
Core --> DB((数据库))
style Core fill:#e8f4ff,stroke:#2a6fdb
style P1 fill:#d9f7d9,stroke:#33aa33
style P2 fill:#fff0d9,stroke:#ff9e33
style P3 fill:#f9d9ff,stroke:#cc33aa
- 优点:核心库稳定不变,业务个性可独立迭代;插件隔离减少互相影响。
- 适用场景:需要支持多租户、可插拔特性,或者框架级项目(如支付、消息中间件)。
- 下面给出一个基于 Maven 的 插件化架构(Plug‑in Architecture)示例项目,展示如何在核心框架中定义插件接口并动态加载各业务插件。
bash
plugin-architecture/
├── pom.xml ← 父工程(Aggregator POM)
├── core/ ← Core 模块(包含 Plugin API 与加载器)
│ └── pom.xml
│ └── src/main/java/...
│ └── com/example/core/
│ ├── plugin/ ← 插件接口定义
│ ├── loader/ ← 插件加载器实现
│ └── app/ ← 核心应用入口
└── plugins/ ← 插件项目(可独立部署、打包为 JAR)
├── plugin-a/
│ └── pom.xml
│ └── src/main/java/...
│ └── com/example/plugin/a/
│ └── PluginAImpl.java
├── plugin-b/... ← Plugin B 同理
└── plugin-c/... ← Plugin C 同理
1. 父工程 pom.xml
xml
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>plugin-architecture</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>core</module>
<module>plugins/plugin-a</module>
<module>plugins/plugin-b</module>
<module>plugins/plugin-c</module>
</modules>
<properties>
<java.version>11</java.version>
</properties>
</project>
2. Core 模块(core/pom.xml
)
xml
<project ...>
<parent>
<groupId>com.example</groupId>
<artifactId>plugin-architecture</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>core</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- 用于扫描插件目录的 Apache Commons IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
</project>
2.1 插件接口定义(Plugin API
)
java
// src/main/java/com/example/core/plugin/Plugin.java
package com.example.core.plugin;
/**
* 插件基础接口,定义生命周期方法
*/
public interface Plugin {
/**
* 插件初始化(例如读取配置)
*/
void init();
/**
* 执行业务逻辑,返回执行结果
*/
String execute(String input);
/**
* 插件卸载/销毁
*/
void destroy();
}
2.2 插件加载器(PluginLoader
)
java
// src/main/java/com/example/core/loader/PluginLoader.java
package com.example.core.loader;
import com.example.core.plugin.Plugin;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
/**
* 简单的插件加载器:扫描指定目录下所有 JAR 并实例化 Plugin
*/
public class PluginLoader {
private final File pluginsDir;
public PluginLoader(String pluginsDirPath) {
this.pluginsDir = new File(pluginsDirPath);
}
public void loadAndRunAll() throws Exception {
if (!pluginsDir.exists() || !pluginsDir.isDirectory()) {
throw new IllegalArgumentException("插件目录不存在: " + pluginsDir.getAbsolutePath());
}
// 扫描所有 .jar 文件
Collection<File> jars = FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false);
for (File jar : jars) {
// 每个 JAR 用单独的 ClassLoader 加载
URL jarUrl = jar.toURI().toURL();
try (URLClassLoader cl = new URLClassLoader(new URL[]{jarUrl}, this.getClass().getClassLoader())) {
// 约定:插件 JAR 内含 resources/plugin.properties,声明插件主类
String propsPath = "plugin.properties";
URL propUrl = cl.findResource(propsPath);
if (propUrl == null) continue;
java.util.Properties props = new java.util.Properties();
props.load(propUrl.openStream());
String pluginClass = props.getProperty("plugin.class");
Class<?> cls = cl.loadClass(pluginClass);
Plugin plugin = (Plugin) cls.getDeclaredConstructor().newInstance();
plugin.init();
String result = plugin.execute("TestInput");
System.out.println("[" + jar.getName() + "] execute result: " + result);
plugin.destroy();
}
}
}
}
2.3 核心应用入口
java
// src/main/java/com/example/core/app/CoreApp.java
package com.example.core.app;
import com.example.core.loader.PluginLoader;
public class CoreApp {
public static void main(String[] args) throws Exception {
String pluginsDir = args.length > 0 ? args[0] : "./plugins";
System.out.println("Loading plugins from: " + pluginsDir);
new PluginLoader(pluginsDir).loadAndRunAll();
}
}
3. 插件示例:Plugin A(plugins/plugin-a/pom.xml
)
xml
<project ...>
<parent>
<groupId>com.example</groupId>
<artifactId>plugin-architecture</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>plugin-a</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- 依赖 Plugin 接口 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
3.1 插件实现
java
// src/main/java/com/example/plugin/a/PluginAImpl.java
package com.example.plugin.a;
import com.example.core.plugin.Plugin;
public class PluginAImpl implements Plugin {
@Override
public void init() {
System.out.println("Plugin A 初始化");
}
@Override
public String execute(String input) {
return "Plugin A processed: " + input.toUpperCase();
}
@Override
public void destroy() {
System.out.println("Plugin A 销毁");
}
}
3.2 插件描述文件
在 src/main/resources/plugin.properties
中添加:
properties
# 声明此插件的主类
plugin.class=com.example.plugin.a.PluginAImpl
4. Plugin B / Plugin C
B、C 插件项目结构、plugin.properties
、实现类都与 A 类似,只需改包名和业务逻辑。
5. 构建与运行
-
构建所有模块
bashmvn clean package
- 会在
core/target
生成核心 JAR - 在各
plugins/plugin-*/target
生成各自的插件 JAR
- 会在
-
准备插件目录
bashmkdir app-plugins cp plugins/plugin-a/target/plugin-a-1.0.0-SNAPSHOT.jar app-plugins/ cp plugins/plugin-b/target/plugin-b-1.0.0-SNAPSHOT.jar app-plugins/
-
启动核心应用
bashjava -jar core/target/core-1.0.0-SNAPSHOT.jar ./app-plugins
控制台会依次输出:
bashLoading plugins from: ./app-plugins Plugin A 初始化 [plugin-a-1.0.0-SNAPSHOT.jar] execute result: Plugin A processed: TestInput Plugin A 销毁 Plugin B 初始化 ...
这样就实现了一个 插件化架构:
- 核心框架(
core
)只负责定义标准 API 和加载机制, - 具体业务以插件 JAR 的形式部署到指定目录,
- 启动时动态扫描、隔离加载并执行,互不干扰。
方案三:微服务化(Serviceization)
-
思路:对于真正共性的、可复用的业务能力,下沉为独立微服务,暴露契约清晰的 HTTP/RPC 接口。
-
实现方式:
- 将公共逻辑部署为
biz-service
。接口需遵守 兼容性策略(向后兼容、接口版本管理)。 - 使用 API Gateway 承载路由、限流、鉴权等横切关注点。
- 各业务侧通过 SDK 或 API 调用方式集成。
- 将公共逻辑部署为
%% 微服务化示意
flowchart LR
subgraph 业务侧
A[Service A]
B[Service B]
C[Service C]
end
D[API Gateway]
E[biz-service]
subgraph 数据存储
DB[(数据库)]
Cache((缓存))
end
A & B & C --> D
D --> E
E --> DB & Cache
style A fill:#e6ffe6,stroke:#009933
style B fill:#fff5e6,stroke:#ff9933
style C fill:#ffe6f2,stroke:#cc0066
style D fill:#f0f0f0,stroke:#666666,stroke-dasharray:4 2
style E fill:#eef7ff,stroke:#2a6fdb
- 优点:真正解耦;可独立伸缩、独立发布;不同团队可并行开发;框架升级不影响业务侧。
- 适用场景:团队成熟、服务调用频繁、对稳定性、扩展性要求高的项目。
总结:分离关注点
- 垂直拆分(模块化/插件化) :将业务个性化逻辑从公共库剥离,放到模块或插件中,核心只保留稳定基础。
- 水平拆分(微服务化) :真正的共性能力下沉为服务,以接口契约维系耦合。
- 物理隔离(过渡手段) :在拆分初期,模块化单体或插件化能快速止血,待成熟后再逐步演进。