能源管理系统多设备对接时,业务层如何做到无感调用?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,也欢迎交流。

相关推荐
李白的粉2 小时前
基于springboot的论坛网站
java·spring boot·毕业设计·课程设计·论坛网站
Hvitur2 小时前
eclipse新建SpringBoot项目
java·spring boot·eclipse
最初的↘那颗心2 小时前
Spring AI Alibaba 多模态全家桶:图片理解、图片生成与语音合成实战
spring boot·大模型·多模态·通义千问·spring ai
码喽7号3 小时前
Springboot学习五:MybatisPlus的快速上手
spring boot·学习·spring
Knight_AL3 小时前
为什么要用 ApplicationReadyEvent 来初始化 RabbitTemplate 回调?
spring boot
熙胤3 小时前
springboot与springcloud对应版本
java·spring boot·spring cloud
J2虾虾3 小时前
SpringBoot 中给 @Autowired 搭配 @Lazy
java·spring boot·后端
智_永无止境4 小时前
Spring Boot 动态多数据源:核心思路与关键考量
spring boot
sheji34164 小时前
【开题答辩全过程】以 基于springboot的健身预约系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端