很多系统在接入设备时,最开始都不会觉得问题有多大。
先接一家厂商,写一个对接实现;再接第二家厂商,再补一个实现;业务层先把接口调通,后面再慢慢收敛。短期看,这样推进很快。
但设备一多,问题很快就会暴露出来。
不同厂商协议不同,不同型号命令不同,网关转发和直连设备的处理方式也不同。如果这些差异直接暴露给业务层,最后最容易出现的情况就是:业务代码里到处都是厂商判断、型号判断、协议判断,设备接入越多,业务层越难维护。
这篇文章就只讲一件事:在多设备对接场景下,ems4j 是怎么让业务层做到“无感调用”的。
1. 业务层真正怕的,不是接设备,而是设备差异进入业务代码
比如业务层如果直接知道:
- 这个园区用的是 A 厂商电表
- 那个园区用的是 B 厂商网关
- 某个型号的 CT 命令格式和标准型号不一样
- 网关子设备下发时要先转到网关协议
那业务代码就很容易从“调用设备能力”变成“处理设备差异”。
一开始可能只是几个 if else,后面就会变成:
- 某个业务服务里混入设备厂商判断
- 同一个开关闸动作在不同模块里重复适配
- 新增一个厂商时,要同时改业务层和接入层
- 某个型号有特殊处理时,影响一大片已有逻辑
从工程角度看,这类问题的根源其实不是“协议复杂”,而是“边界没守住”。
如果业务层的职责是开户、计费、订单、预警这些领域逻辑,那它就不应该知道当前对接的是哪家设备,也不应该关心设备到底走的是直连还是网关。
ems4j 的处理方式,就是尽量把这些差异挡在业务层外面。
要做到这一点,光说“做一层抽象”是不够的,还得先把这套抽象到底拆成了哪几层讲清楚。
2. 先把 integration 和 iot 的两层分工说清楚
在 ems4j 里,业务层并不是直接面向 ems-iot 编程,而是先经过 integration 这一层。
这里分成了前后衔接的两层:
integration:站在业务层和设备平台之间,负责做平台级集成,对上暴露统一设备能力,对下屏蔽不同平台的接入差异iot:作为一种自研设备平台实现,继续往下处理更底层的协议识别、命令路由和设备上下行
如果把它落到实际调用链上,业务层的“无感”主要来自下面这条路径:
- 业务层只调用
integration暴露的统一接口,比如EnergyService integration根据配置选择当前区域对应的设备平台的EnergyService实现,这个实现可以对接第三方平台,也可以对接ems-iot- 当底层接的是
ems-iot时,再由iot层继续处理协议、命令翻译和设备通信细节
这两层拆开之后,业务层处理的是“我要什么设备能力”,而不是“底层到底接了哪家设备、用了哪种协议”。
3. 再看 integration 内部是怎么分层的
只把 integration 理解成“中间层”其实还不够。
它之所以能稳定地向上提供统一入口,很大程度上也和它内部继续做了分层有关。在 ems4j 里,integration 自身又拆成了 core / concrete / biz 三层:
core:核心流程层,负责定义统一抽象和核心选择逻辑。比如统一模块接口、配置读取、DeviceModuleContext这类“该选哪个实现”的基础能力,都在这一层解决。concrete:具体设备能力层,按不同业务类型提供实际对接接口和落地实现。当前文章讨论的是能耗这一支,对应EnergyService;以后也可以继续扩展成其他类型设备的实现。biz:附加能力层,在核心设备操作之外补上更完整的业务处理。比如直接通过核心接口就可以对设备做开关闸,但如果通过biz层调用,还可以获得操作记录、执行结果、失败重试这类更完整的能力。
用一个简化的 UML 看,integration 里的主要 service 关系大致是这样:
+------------------------------------------------------+
| ems-foundation-integration |
+------------------------------------------------------+
| core |
| +----------------------------+ |
| | DeviceModuleContext | |
| +----------------------------+ |
| +----------------------------+ |
| | DeviceModuleConfigService | |
| +----------------------------+ |
| |
| concrete |
| +----------------------------+ |
| | EnergyService |<-----------------+ |
| +----------------------------+ | |
| +----------------------------+ | |
| | DefaultEnergyServiceImpl |------------------+ |
| +----------------------------+ |
| |
| biz |
| +----------------------------+ |
| | DeviceCommandService |<-----------------+ |
| +----------------------------+ | |
| +----------------------------+ | |
| | DeviceCommandServiceImpl |------------------+ |
| +----------------------------+ |
+------------------------------------------------------+
4. 第一层统一:业务层只看到稳定的设备能力接口
在 ems-foundation-integration 里,concrete 层的 EnergyService 定义了一组面向业务的标准设备能力接口,例如:
cutOffrecoverisOnlinegetMeterEnergysetDurationgetDurationsetElectricCtgetElectricCt
从业务层视角看,这些接口表达的不是“某厂商协议怎么发”,而是“系统需要设备提供什么能力”。
这点非常重要。
因为一旦接口定义的是业务语义,而不是协议语义,上层代码就可以稳定下来。业务层只需要决定什么时候断闸、什么时候合闸、什么时候读取电量,而不用知道底层是哪个厂商、哪种报文、哪种命令格式。
EnergyService 这一层,本质上是在做第一轮收敛:
把设备能力抽象成统一接口,先让业务层和设备平台实现解耦。
这里就需要对业务的深刻理解和抽象的能力。
5. 第二层统一:通过 core 层的 DeviceModuleContext 按区域拿到对应实现
只有统一接口还不够。
因为同样是 EnergyService,不同园区、不同区域下,实际接入的设备实现可能并不一样。A 园区接的是一套厂商实现,B 园区接的是另一套实现。如果业务层还要自己判断“当前区域该用哪个实现类”,那无感调用依然做不起来。
ems4j 这里用了一个很关键的核心上下文:DeviceModuleContext。
它做的事情可以概括成一句话:
业务层只告诉系统“我要某种设备能力”和“当前是哪个区域”,至于应该选哪一个具体实现,由上下文和配置来决定。
也就是说,业务层调用时更接近这种形式:
- 指定自己要拿的是
EnergyService - 传入当前业务所属的
areaId - 由
DeviceModuleContext根据配置返回这个区域对应的真实实现
这样一来,业务层就不需要写下面这些东西:
- 这个区域是不是 A 厂商
- 这个区域是不是 B 型号
- 现在应该注入哪一个具体实现类
这就是“无感调用”的第二个关键点:
统一接口之外,还要有统一的实现选择入口。
否则接口虽然统一了,业务层仍然会被迫关心设备来源。
6. 真正处理设备差异的地方,被收敛在 integration 和 iot 两层内部
到这里,业务层已经基本不需要关心设备差异了。
但差异并没有消失,它只是被收敛到了更合适的位置。
它先被限制在“平台集成”这一层,再被继续下沉到 iot 这一层,而不是直接暴露给业务代码。
这也是为什么新增设备时,变化会更多地留在接入层和协议层,而不是持续污染开户、计费、订单、预警这些业务服务。
7. 总结
我觉得多设备对接最容易被低估的点,不是“怎么接一个新协议”,而是“怎么在设备越来越多之后,业务层还能保持干净”。
如果业务层一开始就和厂商协议绑死,后面每新增一个设备,系统复杂度都会进一步向上扩散。
反过来,如果从一开始就把统一接口、统一上下文、统一命令入口和协议差异吸收机制放好,后续接入新设备时,变化就会更多地留在接入层和协议层,不会持续污染业务代码。
这也是 ems4j 这套实现最值得看的地方。
它要解决的不是“怎么把设备接进来”这么简单,而是“接进来之后,业务层还能不能继续无感调用”。
8. 项目地址
- Gitee:gitee.com/jerryxiaosa…
- GitHub:github.com/jerryxiaosa…
如果你也在做多设备对接,或者正在思考怎么把设备差异挡在业务层外面,可以看看 ems4j。
建议先顺着 integration -> iot 这条链路读一遍,再回头看业务层的调用方式,基本就能判断这套思路是不是适合你的系统。
如果觉得有参考价值,欢迎 Star,也欢迎交流。