能源管理系统多设备对接时,业务层如何做到无感调用?ems4j 的实现思路

很多系统在接入设备时,最开始都不会觉得问题有多大。

先接一家厂商,写一个对接实现;再接第二家厂商,再补一个实现;业务层先把接口调通,后面再慢慢收敛。短期看,这样推进很快。

但设备一多,问题很快就会暴露出来。

不同厂商协议不同,不同型号命令不同,网关转发和直连设备的处理方式也不同。如果这些差异直接暴露给业务层,最后最容易出现的情况就是:业务代码里到处都是厂商判断、型号判断、协议判断,设备接入越多,业务层越难维护。

这篇文章就只讲一件事:在多设备对接场景下,ems4j 是怎么让业务层做到"无感调用"的。

1. 业务层真正怕的,不是接设备,而是设备差异进入业务代码

比如业务层如果直接知道:

  • 这个园区用的是 A 厂商电表
  • 那个园区用的是 B 厂商网关
  • 某个型号的 CT 命令格式和标准型号不一样
  • 网关子设备下发时要先转到网关协议

那业务代码就很容易从"调用设备能力"变成"处理设备差异"。

一开始可能只是几个 if else,后面就会变成:

  • 某个业务服务里混入设备厂商判断
  • 同一个开关闸动作在不同模块里重复适配
  • 新增一个厂商时,要同时改业务层和接入层
  • 某个型号有特殊处理时,影响一大片已有逻辑

从工程角度看,这类问题的根源其实不是"协议复杂",而是"边界没守住"。

如果业务层的职责是开户、计费、订单、预警这些领域逻辑,那它就不应该知道当前对接的是哪家设备,也不应该关心设备到底走的是直连还是网关。

ems4j 的处理方式,就是尽量把这些差异挡在业务层外面。

要做到这一点,光说"做一层抽象"是不够的,还得先把这套抽象到底拆成了哪几层讲清楚。

2. 先把 integration 和 iot 的两层分工说清楚

ems4j 里,业务层并不是直接面向 ems-iot 编程,而是先经过 integration 这一层。

这里分成了前后衔接的两层:

  • integration:站在业务层和设备平台之间,负责做平台级集成,对上暴露统一设备能力,对下屏蔽不同平台的接入差异
  • iot:作为一种自研设备平台实现,继续往下处理更底层的协议识别、命令路由和设备上下行

如果把它落到实际调用链上,业务层的"无感"主要来自下面这条路径:

  1. 业务层只调用 integration 暴露的统一接口,比如 EnergyService
  2. integration 根据配置选择当前区域对应的设备平台的EnergyService实现,这个实现可以对接第三方平台,也可以对接 ems-iot
  3. 当底层接的是 ems-iot 时,再由 iot 层继续处理协议、命令翻译和设备通信细节

这两层拆开之后,业务层处理的是"我要什么设备能力",而不是"底层到底接了哪家设备、用了哪种协议"。

3. 再看 integration 内部是怎么分层的

只把 integration 理解成"中间层"其实还不够。

它之所以能稳定地向上提供统一入口,很大程度上也和它内部继续做了分层有关。在 ems4j 里,integration 自身又拆成了 core / concrete / biz 三层:

  • core:核心流程层,负责定义统一抽象和核心选择逻辑。比如统一模块接口、配置读取、DeviceModuleContext 这类"该选哪个实现"的基础能力,都在这一层解决。
  • concrete:具体设备能力层,按不同业务类型提供实际对接接口和落地实现。当前文章讨论的是能耗这一支,对应 EnergyService;以后也可以继续扩展成其他类型设备的实现。
  • biz:附加能力层,在核心设备操作之外补上更完整的业务处理。比如直接通过核心接口就可以对设备做开关闸,但如果通过 biz 层调用,还可以获得操作记录、执行结果、失败重试这类更完整的能力。

用一个简化的 UML 看,integration 里的主要 service 关系大致是这样:

text 复制代码
+------------------------------------------------------+
|            ems-foundation-integration                |
+------------------------------------------------------+
| core                                                 |
|   +----------------------------+                     |
|   | DeviceModuleContext        |                     |
|   +----------------------------+                     |
|   +----------------------------+                     |
|   | DeviceModuleConfigService  |                     |
|   +----------------------------+                     |
|                                                      |
| concrete                                             |
|   +----------------------------+                     |
|   | EnergyService              |<-----------------+  |
|   +----------------------------+                  |  |
|   +----------------------------+                  |  |
|   | DefaultEnergyServiceImpl   |------------------+  |
|   +----------------------------+                     |
|                                                      |
| biz                                                  |
|   +----------------------------+                     |
|   | DeviceCommandService       |<-----------------+  |
|   +----------------------------+                  |  |
|   +----------------------------+                  |  |
|   | DeviceCommandServiceImpl   |------------------+  |
|   +----------------------------+                     |
+------------------------------------------------------+

4. 第一层统一:业务层只看到稳定的设备能力接口

ems-foundation-integration 里,concrete 层的 EnergyService 定义了一组面向业务的标准设备能力接口,例如:

  • cutOff
  • recover
  • isOnline
  • getMeterEnergy
  • setDuration
  • getDuration
  • setElectricCt
  • getElectricCt

从业务层视角看,这些接口表达的不是"某厂商协议怎么发",而是"系统需要设备提供什么能力"。

这点非常重要。

因为一旦接口定义的是业务语义,而不是协议语义,上层代码就可以稳定下来。业务层只需要决定什么时候断闸、什么时候合闸、什么时候读取电量,而不用知道底层是哪个厂商、哪种报文、哪种命令格式。

EnergyService 这一层,本质上是在做第一轮收敛:

把设备能力抽象成统一接口,先让业务层和设备平台实现解耦。

这里就需要对业务的深刻理解和抽象的能力。

5. 第二层统一:通过 core 层的 DeviceModuleContext 按区域拿到对应实现

只有统一接口还不够。

因为同样是 EnergyService,不同园区、不同区域下,实际接入的设备实现可能并不一样。A 园区接的是一套厂商实现,B 园区接的是另一套实现。如果业务层还要自己判断"当前区域该用哪个实现类",那无感调用依然做不起来。

ems4j 这里用了一个很关键的核心上下文:DeviceModuleContext

它做的事情可以概括成一句话:

业务层只告诉系统"我要某种设备能力"和"当前是哪个区域",至于应该选哪一个具体实现,由上下文和配置来决定。

也就是说,业务层调用时更接近这种形式:

  1. 指定自己要拿的是 EnergyService
  2. 传入当前业务所属的 areaId
  3. DeviceModuleContext 根据配置返回这个区域对应的真实实现

这样一来,业务层就不需要写下面这些东西:

  • 这个区域是不是 A 厂商
  • 这个区域是不是 B 型号
  • 现在应该注入哪一个具体实现类

这就是"无感调用"的第二个关键点:

统一接口之外,还要有统一的实现选择入口。

否则接口虽然统一了,业务层仍然会被迫关心设备来源。

6. 真正处理设备差异的地方,被收敛在 integration 和 iot 两层内部

到这里,业务层已经基本不需要关心设备差异了。

但差异并没有消失,它只是被收敛到了更合适的位置。

它先被限制在"平台集成"这一层,再被继续下沉到 iot 这一层,而不是直接暴露给业务代码。

这也是为什么新增设备时,变化会更多地留在接入层和协议层,而不是持续污染开户、计费、订单、预警这些业务服务。

7. 总结

我觉得多设备对接最容易被低估的点,不是"怎么接一个新协议",而是"怎么在设备越来越多之后,业务层还能保持干净"。

如果业务层一开始就和厂商协议绑死,后面每新增一个设备,系统复杂度都会进一步向上扩散。

反过来,如果从一开始就把统一接口、统一上下文、统一命令入口和协议差异吸收机制放好,后续接入新设备时,变化就会更多地留在接入层和协议层,不会持续污染业务代码。

这也是 ems4j 这套实现最值得看的地方。

它要解决的不是"怎么把设备接进来"这么简单,而是"接进来之后,业务层还能不能继续无感调用"。

8. 项目地址

如果你也在做多设备对接,或者正在思考怎么把设备差异挡在业务层外面,可以看看 ems4j

建议先顺着 integration -> iot 这条链路读一遍,再回头看业务层的调用方式,基本就能判断这套思路是不是适合你的系统。

如果觉得有参考价值,欢迎 Star,也欢迎交流。

相关推荐
星辰徐哥4 小时前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥4 小时前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约4 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee4 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐4 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs4 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
毕设源码_郑学姐4 小时前
计算机毕业设计springboot网络相册设计与实现 基于Spring Boot框架的在线相册管理系统开发与应用 Spring Boot驱动的网络影集设计与实践
spring boot·后端·课程设计
辣机小司4 小时前
【踩坑记录:Spring Boot 配置文件读取值不一致?警惕 YAML 的“八进制陷阱”与 SnakeYAML 版本之谜】
java·spring boot·后端·yaml·踩坑记录
一条小锦吕*4 小时前
基于Spring Boot + 数据可视化 + 协同过滤算法的推荐系统设计与实现(源码+论文+部署全讲解)
spring boot·算法·信息可视化
Jinkxs4 小时前
Prometheus - 监控微服务:Spring Boot 应用指标暴露与监控
spring boot·微服务·prometheus