做 Spring Boot 项目时,很多人都会做模块拆分。
但模块拆分这件事,真正难的从来不是"拆几个模块",而是"为什么这样拆"。
有些项目看起来模块很多,继续往下看却会发现:controller 里写业务,service 里拼协议,公共能力散落在各处,最后虽然工程被拆开了,边界却并没有真正清楚。
ems4j 在项目级分层上想解决的,不是"把模块拆得更多",而是另一件更实际的事情:
把变化和复杂度关在合适的位置。
这篇文章不展开单个模块内部实现,只从项目级架构出发,聊聊 ems4j 为什么要拆成 bootstrap / web / business / foundation / components / iot 这几层。
1. 为什么很多项目分了层,最后还是会乱
很多项目的问题,并不是没有分层,而是分层停留在目录层面。
比如一个常见情况是:
web层不只是接接口,还开始承载业务判断- 业务模块里混入用户、组织、空间这类基础能力
- 通用组件里又写进了业务语义
- 设备接入和业务处理搅在一起
这样一来,工程虽然有了多个模块,但模块之间的职责并没有真正收住。最后的结果通常是:
- 某个功能一改,要同时改好几层
- 公共能力无法稳定复用
- 业务和技术实现互相污染
- 读代码时很难快速判断"这段逻辑应该属于哪一层"
所以我理解的分层设计,不是"多建几个 Maven 模块",而是先回答一个问题:
这段职责,究竟应该放在哪一层,才不会把后续变化扩散出去?
ems4j 的项目级拆分,就是围绕这个问题做的。
2. ems4j 的项目级分层,不是横向切包,而是纵向划边界
从整体看,ems4j 的主干大致可以理解成这样:
ems-bootstrap:应用启动入口ems-web:HTTP 接口层ems-business:核心业务层ems-foundation:跨业务共享的基础能力层ems-components:可复用的通用技术组件层ems-iot:独立的设备接入与协议平台
用一张简化的 ASCII 图看,整体结构大致是这样:
sql
+----------------------+
| ems-bootstrap |
| 应用启动 / 装配 |
+----------+-----------+
|
v
+----------------------+
| ems-web |
| HTTP 接口 / 视图 |
+----------+-----------+
|
v
+--------------------------------------------------+
| ems-business |
| device / account / billing / order / lease ... |
+----------+----------------------+----------------+
| |
v v
+----------------------+ +----------------------+
| ems-foundation | | ems-components |
| 用户/组织/空间/集成 | | 锁/数据源/上下文等 |
+----------------------+ +----------------------+
如果只看名字,这几个模块很普通。
但这套拆分真正重要的地方,不在于"模块名称是什么",而在于每一层都在刻意回答一个问题:
- 哪些逻辑属于应用入口
- 哪些逻辑属于接口表达
- 哪些逻辑属于核心业务
- 哪些逻辑属于基础域能力
- 哪些逻辑属于技术基础设施
- 哪些逻辑应该被隔离在设备平台内部
3. bootstrap:只负责启动和装配,不承载具体业务
ems-bootstrap 在这套结构里是最上层入口。
它的职责很克制:负责应用启动、模块装配、配置加载和整体运行入口,而不是去承载某个具体业务模块的实现。
这样做的价值很直接。
如果启动入口里直接堆满业务逻辑,那么应用装配和业务处理就会耦合在一起。后面你想拆服务、换部署方式、补外部依赖时,入口层就会越来越重。
把 bootstrap 单独拿出来,至少可以保证一件事:
应用怎么启动是一回事,业务怎么实现是另一回事。
4. web:只表达接口,不承载核心业务
ems-web 的定位也很明确,就是 HTTP 接口层。
它负责的事情包括:
- 接收前端请求
- 做接口层参数组织
- 返回前端需要的视图对象
- 处理权限、路由和接口层表达
但它不应该直接承载核心业务。
这一点在很多项目里最容易失控。因为接口层最靠近需求,一旦赶进度,很多业务判断会自然地写进 controller 或者 biz 编排里。短期看好像更快,长期看却会把业务边界打穿。
ems4j 这里强调的是:
接口层的价值,在于把请求翻译成系统能处理的输入,而不是替代业务层做判断。
所以 web 的存在,不是为了多加一层,而是为了把"接口表达"这件事从"业务实现"里剥离出来。
5. business:把账户、设备、计费、订单这些核心逻辑放在一起
如果说哪一层是系统主体,那一定是 ems-business。
在 ems4j 里,核心业务被放在这里,主要包括:
device:设备档案与设备管理account:账户信息、开户、销户billing:计费、消费、账务处理order:订单创建、支付回调、订单状态流转lease:主体与空间租赁关系plan:电价方案、尖峰平谷、预警方案aggregation:跨业务读聚合与应用编排
把这些模块集中在 business 层的意义,是把"系统真正关心的业务问题"沉淀下来。
因为一个业务系统里,变化最频繁、最值得认真建模的,通常不是接口格式,也不是框架配置,而是账户、订单、计费、设备这些领域逻辑本身。
所以 business 这一层的存在,不只是为了分类,更是为了明确:
系统的重心到底在哪里。
6. foundation:基础能力不属于某个业务模块
很多项目刚开始规模不大时,用户、组织、空间、通知、系统配置这些能力,常常会被顺手放进某个业务模块里。
但当业务越来越多时,这种放法会很快变成问题。
因为这些能力本身并不属于某个单独业务域,它们更像是整个系统都要共享的基础服务。比如:
- 用户认证、角色、权限
- 组织与多租户能力
- 空间与区域管理
- 系统配置
- 平台集成能力
- 通知能力
也就是说,foundation 解决的是:
哪些能力应该被多个业务域共同依赖,但又不该反过来依赖具体业务。
7. components:通用技术能力应该被抽出来,但不要带业务语义
ems-components 和 ems-foundation 很容易被混淆。
看起来两者都像"公共模块",但它们解决的问题并不一样。
foundation 偏业务共享能力,components 偏纯技术组件。
在 ems4j 里,components 里放的是这类内容:
- 数据源能力
- 锁能力
- 上下文能力
- Redis 等基础组件
- 翻译组件
这些能力的共同点是:它们可以被很多地方复用,但本身不应该携带具体业务语义。
如果把这类技术组件直接和账户、设备、订单这些业务概念混在一起,后面复用边界就会很难看清。
反过来,如果把业务判断写进组件层,又会让组件失去"通用"的意义。
所以 components 这一层最核心的约束其实是:
只做通用技术能力,不承载具体业务含义。
8. iot:设备接入平台应该独立,而不是塞进业务层
ems4j 里还有一个很关键的拆分,就是把 ems-iot 单独拉出来。
这是很多类似系统和普通后台项目真正不一样的地方。
如果设备接入、协议解析、命令路由这些内容直接塞进业务层,会发生什么?
很简单,业务模块会一边处理账户、订单、计费,一边还要理解设备协议、网络通信、命令格式、上下行事件,最后整个系统会同时承载两套完全不同层次的复杂度。
而 ems-iot 单独存在之后,边界就清楚很多了:
- 业务系统负责表达"我要什么设备能力"
- IoT 平台负责处理"设备到底怎么通信、怎么解析、怎么下发"
这类拆分的价值,不只是模块更清楚,更重要的是:
设备侧复杂度不会直接污染业务层。
9. 这套分层真正解决了什么问题
如果只从模块图看,这套结构看起来像是常规的多模块拆分。
但它真正解决的问题,不在图上,而在后续演进里。
我觉得至少解决了 4 件事:
- 接口变化不会直接冲击业务实现
web负责接口表达,业务层保持稳定。 - 基础能力不会散落到各个业务模块
foundation收住共享能力,避免重复建设。 - 通用技术组件不会和业务语义混在一起
components保持技术中立,复用边界更清楚。 - 设备接入复杂度不会直接扩散到业务层
iot独立存在,业务和协议分开演进。
所以这套分层的核心价值,不是"工程更好看",而是:
系统在继续增长时,复杂度不会无序扩散。
10. 最后
我一直觉得,架构分层设计最容易被误解成"模块越多越高级"。
但真正有价值的分层,不是多,而是准。
不是把东西拆碎,而是把不同性质的职责放到合适的位置。
ems4j 这套项目级分层,未必是唯一答案,但它至少在试图解决一个很现实的问题:
当系统同时面对后台管理、业务建模、计费结算、设备接入和平台集成时,怎么让每一层都只处理自己该处理的事。
如果你也在做 Spring Boot 多模块项目,或者正在思考业务系统和设备平台该怎么拆,ems4j 这套结构应该会有一些参考价值。