模块化与系统边界
约 1193 字大约 4 分钟
布欧-Lewyon
2026-05-16
首页 › Spring Boot › 架构师进阶 › 模块化与系统边界
本篇服务 L4 架构师。假设已具备 Boot 多模块 Maven/Gradle 与业务分层基础(见推荐工程结构)。
单体应用在团队膨胀后往往走向"大泥球"——没有边界、没有 API 模块、Controller 直接调用 Repository。模块化是在不拆微服务的前提下,通过代码组织与编译期约束来治理复杂度。
多模块 Maven 结构(推荐)
my-app/
├── pom.xml # 父 POM
├── my-app-api/ # API 模块:接口 + DTO
│ └── src/main/java/com/example/api/
│ ├── dto/
│ ├── service/
│ └── feign/ # 如果是微服务,可作为 Feign 依赖
├── my-app-domain/ # 领域模块:实体 + 领域服务
│ └── src/main/java/com/example/domain/
│ ├── model/
│ └── repository/ # Repository 接口(非实现)
├── my-app-infrastructure/ # 基础设施:数据源、消息、外部客户端
│ └── src/main/java/com/example/infra/
│ ├── persistence/ # JPA / MyBatis 实现
│ ├── mq/
│ └── client/
├── my-app-web/ # Web 层:Controller + 安全配置
│ └── src/main/java/com/example/web/
│ ├── controller/
│ └── config/
└── my-app-bootstrap/ # 启动模块:@SpringBootApplication
└── src/main/java/com/example/
└── Application.java<!-- 依赖方向:bootstrap → web → infrastructure → domain ← api(被所有模块依赖) -->依赖方向原则:api 模块不依赖任何其他内部模块;domain 只依赖 api;infrastructure 依赖 domain;web 依赖 infrastructure;bootstrap 依赖 web。这种单向依赖保证内层(domain/api)不感知外层实现细节。
限界上下文(Bounded Context)
当业务复杂度升高时,仅靠技术分层不够——需要按业务领域拆分模块:
order-app/
├── order-api/ # 订单相关接口
├── order-domain/ # 订单领域逻辑
├── order-infra/ # 订单数据访问
│
├── payment-api/
├── payment-domain/
├── payment-infra/
│
└── order-bootstrap/ # 启动模块,聚合所有上下文模块间通信
- 同进程:
order-domain通过payment-api定义的接口调用payment-domain——依赖接口而非实现。 - 跨上下文禁止:
order-domain不能直接注入payment-infra中的 Repository。 - 使用事件解耦:上下文之间的状态变更通过
ApplicationEventPublisher发布域事件;接收方在自身模块内处理。
// order-domain 中发布事件
@Service
public class OrderDomainService {
private final ApplicationEventPublisher publisher;
public void completeOrder(Long orderId) {
// 订单领域逻辑...
publisher.publishEvent(new OrderCompletedEvent(orderId));
}
}
// payment-domain 中监听事件
@Component
public class PaymentEventListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderCompleted(OrderCompletedEvent event) {
// 发起支付处理——不直接依赖 order-domain
}
}架构视角:什么时候拆微服务
模块化单体可以很好地服务于大多数团队,以下条件累积出现时才需要考虑拆微服务:
| 条件 | 单体应对 | 微服务优势 |
|---|---|---|
| 团队规模 > 15 人,频繁代码冲突 | 模块化 + 代码所有权(OWNERS) | 独立部署,降低协调成本 |
| 子模块资源需求差异大(IO 密集 vs CPU 密集) | 统一部署,资源浪费 | 独立扩缩容 |
| 子模块发布节奏不同(月级 vs 天级) | 每次发版全量回归 | 独立 CI/CD |
| 需要异构技术栈 | 只能 Java | 各服务可用不同语言 |
关键警示:微服务不是银弹。网络不可靠、分布式事务、调试复杂度、运维成本——这些在拆开之前很难估算。"先模块化单体,确认边界后再拆" 是比"一上来就拆"更稳妥的演进路径。
架构视角:API 模块与版本管理
api 模块的职责是定义契约:
// my-app-api 模块中的接口
public interface UserService {
Page<UserDTO> searchUsers(UserSearchRequest request);
UserDetailDTO getUserDetail(Long userId);
}// my-app-infrastructure 模块中的实现
@Service
public class UserServiceImpl implements UserService {
// 实现细节不泄漏到 api 模块
}这种做法允许在不部署应用的情况下,让其他服务(或同一服务的不同模块)只依赖 api 模块的 Jar——实现契约优先的开发模式。
当接口需要向后兼容时,永不移除但不推荐:
// v1 接口(标记为废弃但保留)
@Deprecated
public interface UserServiceV1 {
Page<UserDTO> listUsers(int page, int size);
}
// v2 接口(新契约)
public interface UserServiceV2 {
Page<UserDTO> searchUsers(UserSearchRequest request);
}小结
- 多模块 Maven 项目的依赖方向:
bootstrap → web → infrastructure → domain ← api。 - 限界上下文:按业务领域拆分模块,模块间通过接口 + 事件解耦。
- 模块化单体是微服务的前置步骤——先明确边界再决定是否拆。
- API 模块的契约定义可以不依赖任何实现框架,纯 POJO 接口。
- 架构视角:拆微服务的决策应基于团队规模、发布频率、资源扩缩容需求,而非技术潮流。模块化单体对 80% 的团队已经足够。
- 思考任务:审视当前项目是否可以从"包内分层"演进到"模块级分层"——将
controller/service/repository按模块拆分,验证编译期依赖方向是否正确,并观察对开发效率的影响。
上一节:韧性与容错
下一节:性能与容量
