0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

SIMBOSS:物联网业务如何应用领域驱动设计

电子设计 来源:电子设计 作者:电子设计 2020-12-25 18:31 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

在这个万物互联的时代,物联网业务蓬勃发展,但也瞬息万变,对于开发人员来说,这是一种挑战,但也是一种“折磨”。

在业务发展初期,因为时间有限,我们一般会遵循“小步快跑,迭代试错”的原则进行业务开发,用通俗的话来说就是“no bb,先上了再说”,对于开发人员的具体实现,就是“脚本式”的开发方式,或者说是数据的 CURD,这样的开发方式,在项目早期没什么问题,但随着新业务的不断加入,业务迭代的频繁,我们会发现,现在的业务系统变得越来越冗杂,新加一个需求或者改一个业务,变得无比困难,因为业务实现彼此之间模糊不清,业务规则在代码中无处不在,开发人员也就无从下手。

那怎么解决上面的问题呢?可能很多人会说“你这代码不行,重构呀”,是的,我们发现了项目中的“坏代码”,比如一个类上千行,一个方法几百行,于是我们把一些代码抽离出来,做一些内聚的实现,代码规范做一些调整,但这样只是解决现在项目代码中的问题,下次项目迭代的时候,你并不能保证写的新代码是符合规范的,而且最重要的是,重构并不能在业务代码上给一个定义,什么意思呢?比如你重构一个方法,你只能从技术的角度去重构它,并不能从业务的角度去重构,因为在整个业务系统“混乱”的情况下,你无法保证自己的“清白”。另外还有一点,即使你重构了它,但对于新加入的开发人员来说,他并不能理解你重构的目的,换句话说,就是如果他要使用或改这个方法,他完全不知道能不能使用或者使用了会不会影响其他业务,说白了就是,业务的边界不明确。

那如何定义业务的边界呢?答案就是运用 Eric Evans 提出的领域驱动设计(Domain Driven Design,简称 DDD),关于 DDD 的相关概念,这边就不叙述了,网上有很多资料,需要注意的是,DDD 关注的是业务设计,并非技术实现。

物联网业务如何应用领域驱动设计?这其实是个大命题,该怎么实现?如何下手呢?我找了我之前做的一个业务需求,来做示例,看看“脚本式”的实现,和 DDD 的实现,前后有什么不太一样的地方。

脚本式的开发

业务需求:针对物联网卡的当前套餐使用量,根据一定的规则,进行特定的限速设置。

需求看起来很简单,下面要具体实现了,首先,我创建了三张表:

speed_limit:限速表,包含用户 ID、套餐 ID 等。

speed_limit_config:限速配置表,包含限速档位,也就是套餐使用量在什么区间,限速多少的配置。

speed_limit_level:限速级别表,包含限速的单位和具体值,主要界面选择使用。

然后再创建对应“贫血”的模型对象(没有任何行为,并且属性和数据库字段一一对应):

public class SpeedLimit {

private Long id;

private Integer orgId;

private Long priceOfferId;

//getter setter....

public class SpeedLimitConfig {

private Long id;

private Long speedLimitId;

private Double usageStart;

private Double usageEnd;

//getter setter....

public class SpeedLimitLevel {

private Long id;

private String unit;

private Double value;

//getter setter....

好,数据库表和模型对象都创建好了,接下来做什么呢?CURD 啊,界面需要对这些数据进行查看和维护,所以,我创建了:

SpeedLimitMapper.xml:数据库访问 SQL。

SpeedLimitService.java:调用 Mapper,并返回数据。

SpeedLimitController.java:接受前端传递参数,并调用 Service,封装返回数据。

简单看下SpeedLimitService.java中的代码:

public interface SpeedLimitService {

List<SpeedLimit> listAll();

SpeedLimitVO getById(Long id);

Boolean insert(Integer orgId, Long priceOfferId, List<SpeedLimitConfig> speedLimitConfigs);

//...

CURD 流程没啥问题吧,数据维护好了,接下来要进行限速检查了,我们目前的实现方式是:有一个定时任务,每间隔一段时间批量执行,查询所有的限速配置(上面的speed_limit),然后根据用户 ID 和套餐 ID,查询出符合条件的物联网卡,然后将卡号丢到 MQ 中异步处理,MQ 接受到卡号,再查询对应的限速配置(speed_limit以及speed_limit_config),然后再查询此卡的套餐使用量,最后根据规则匹配,进行限速设置等操作。

MQ 中的处理代码(阿里插件都已经提醒我,这个方法代码太长了):

为什么代码不贴出来?因为里面的代码惨不忍睹啊,if..else..的各种嵌套,所以,还是眼不看为净。

好,到此为止,这个需求已经“脚本式”的开发完了,我们来总结一把:

条理清晰,开发效率贼高,完全符合“先上了再说”的开发原则。

数据的 CURD 和业务逻辑处理隔离开,用到的地方_x001D_“单独处理”,似乎没啥问题。

没啥问题对吧?好,现在业务迭代来了,产品经理发话了,说除了批量限速检查,还需要对单卡的限速同步处理(瞎掰的),因为是同步处理,所以我没办法发消息到 MQ 处理,只能对 MQ 中的那一坨代码进行重构,代码抽离的过程中发现,并不能兼容新的需求,怎么搞呢?只能又重载了一个方法,把里面能抽离的抽离出来,改好之后,需求完美上线。

过了一天,产品经理又发话了。

然后,我把产品经理打死了。

领域驱动设计

为了避免我和产品经理打架,我需要做一些改变,就事论事,毕竟问题出在开发这边,面对“一锅乱粥”的代码,我决定用 DDD 这把“武器”进行改造它。

我们知道,DDD 分为战略设计和战术设计,战略设计就是把限界上下文和核心领域搞出来,然后针对某个限界上下文,再利用战术设计进行具体的实现,这个过程一般是针对一个完整复杂的业务系统,涉及的东西很多,你可能需要和领域专家进行深入沟通,如有必要还需画出业务领域图、限界上下文图、限界上下文映射图等等,以便理解。

针对限速设置的业务需求,我简单画了下所涉及的上下文映射图:

可以看到,我们关注的只有一个限速上下文,物联网卡上下文、套餐流量上下文和运营商 API 上下文,我们并不需要关心,ACL 的意思是防腐层(Anticorruption Layer),它的作用就是隔离各个上下文,以及协调上下文之间的通信

限速上下文内部的实现(如聚合根和实体等),其实就是战术设计的具体实现,关于概念这边就不多说了,这里说下具体的设计:

SpeedLimit聚合根:毫无疑问,限速上下文的聚合根是限速聚合根,也可以称之为聚合根实体,这里的SpeedLimit并不是上面贫血的模型对象,而是包含限速业务逻辑的聚合对象。

SpeedLimitConfig实体:限速配置实体,在生命周期内有唯一的标识,并且依附于限速聚合根。

SpeedLimitLevel实体:其实限速级别应该设计成值对象,因为它并没有生命周期和唯一标识的概念,只是一个具体的值。

SpeedLimitContext值对象:限速上下文,只包含具体的值,作用就是从应用层发起调用到领域层,可以看做是传输对象。

SpeedLimitService领域服务:因为涉及到多个上下文的协调和交互,限速聚合根并不能独立完成,所以这些聚合根完成不了的操作,可以放到领域服务中去处理。

SpeedLimitRepository仓储:限速聚合对象的管理中心,可以数据库存储,也可以其他方式存储,不要把Mapper接口定义为Repository接口。

以上因为不好在现有项目中做改造,我就用 Spring Boot 做了一个项目示例(Spring Boot 用起来真的很爽,简洁高效,做微服务非常好),大致的项目结构:

├── src

│ ├── main

│ │ ├── java

│ │ │ └── com

│ │ │ └── qipeng

│ │ │ └── simboss

│ │ │ └── speedlimit

│ │ │ ├── SpeedLimitApplication.java

│ │ │ ├── application

│ │ │ │ ├── dto

│ │ │ │ └── service

│ │ │ │ ├── SpeedLimitApplicationService.java

│ │ │ │ └── impl

│ │ │ │ └── SpeedLimitApplicationServiceImpl.java

│ │ │ ├── domain

│ │ │ │ ├── aggregate

│ │ │ │ │ └── SpeedLimit.java

│ │ │ │ ├── entity

│ │ │ │ │ ├── SpeedLimitConfig.java

│ │ │ │ │ └── SpeedLimitLevel.java

│ │ │ │ ├── service

│ │ │ │ │ ├── SpeedLimitService.java

│ │ │ │ │ └── impl

│ │ │ │ │ └── SpeedLimitServiceImpl.java

│ │ │ │ └── valobj

│ │ │ │ └── SpeedLimitCheckContext.java

│ │ │ ├── facade

│ │ │ │ ├── CarrierApiFacade.java

│ │ │ │ ├── DeviceRatePlanFacade.java

│ │ │ │ ├── IotCardFacade.java

│ │ │ │ └── model

│ │ │ │ ├── CarrierConstants.java

│ │ │ │ ├── DeviceRatePlan.java

│ │ │ │ ├── EnumTemplate.java

│ │ │ │ ├── IotCard.java

│ │ │ │ └── SpeedLimitAction.java

│ │ │ └── repo

│ │ │ ├── dao

│ │ │ │ └── SpeedLimitDao.java

│ │ │ └── repository

│ │ │ └── SpeedLimitRepository.java

│ │ └── resources

│ │ ├── application.yml

│ │ ├── mybatis

│ │ │ ├── mapper

│ │ │ │ └── SpeedLimitMapper.xml

│ │ │ └── mybatis-config.xml

│ └── test

│ └── java

│ └── com

│ └── qipeng

│ └── simboss

│ └── speedlimit

│ ├── SpeedLimitApplicationTests.java

│ ├── application

│ │ └── SpeedLimitApplicationServiceTest.java

│ └── domain

│ └── SpeedLimitServiceTest.java

包路径:

import com.qipeng.simboss.speedlimit.domain.aggregate.SpeedLimit;//聚合根import com.qipeng.simboss.speedlimit.domain.entity.*;//实体import com.qipeng.simboss.speedlimit.domain.valobj.*;//值对象import com.qipeng.simboss.speedlimit.domain.service.*;//领域服务import com.qipeng.simboss.speedlimit.domain.repo.repository.*;//仓储import com.qipeng.simboss.speedlimit.repo.dao.*;//mapper接口import com.qipeng.simboss.speedlimit.application.service.*;//应用层服务

好,基本上这个项目设计的差不多了,需要注意的是,上面核心是com.qipeng.simboss.speedlimit.domain包,里面包含了最重要的业务逻辑处理,其他都是为此服务的,另外,在领域模型不断完善的过程中,需要持续对领域模型进行单元测试,以保证其健壮性,并且,设计SpeedLimit聚合根的时候,不要先考虑数据库的实现,如果需要数据进行测试,可以在SpeedLimitRepository中 Mock 对应的数据。

看下SpeedLimit聚合根中的代码:

package com.qipeng.simboss.speedlimit.domain.aggregate;

import com.qipeng.simboss.speedlimit.domain.entity.SpeedLimitConfig;import com.qipeng.simboss.speedlimit.facade.model.IotCard;import lombok.Data;import java.util.Date;import java.util.List;

/**

* 限速聚合根

*/@Datapublic class SpeedLimit {

/**

* 限速

*/

private Long id;

/**

* 组织ID

*/

private Integer orgId;

/**

* 套餐ID

*/

private Long priceOfferId;

/**

* 限速配置集合

*/

private List<SpeedLimitConfig> configs;

/**

* 是否删除当前限速,不持久化

*/

private Boolean isDel = false;

/**

* 卡的限速值,不持久化

*/

private Double cardSpeedLimit;

/**

* 获取限速值

*/

public Double chooseSpeedLimit(Double usageDataVolume, Double totalDataVolume, Long cardPoolId,

Boolean isRealnamePassed, Double currentSpeedLimit) {

//todo this...

/**

* 设置是否删除当前限速

*/

private void setIsDelSpeedLimit(Double currentSpeedLimit) {

//判断当前限速是否存在,如果存在,则删除现有的限速配置

//todo this...

上面注释写的比较多(方便理解),SpeedLimit聚合根和之前的SpeedLimit贫血对象相比,主要有以下改动:

SpeedLimit聚合根并不只是包含getter和setter,还包含了业务行为,并且也不和数据库表一一对应。

SpeedLimit聚合根中包含configs对象(限速配置集合),因为限速配置实体依附于SpeedLimit聚合根。

SpeedLimit聚合根中的chooseSpeedLimit方法,意思是根据某种规则从限速配置中,选取当前要限速的值,这是限速的核心业务逻辑。

那为什么不把整个限速设置的逻辑写在SpeedLimit聚合根中?而只是实现选取要限速的值呢?为什么?为什么?为什么?

答案很简单,因为限速设置的整个逻辑需要涉及到多个上下文的协作,SpeedLimit聚合根完全 Hold 不住呀,所以要把这些逻辑写到限速领域服务中,还有最重要的是,SpeedLimit聚合根只关注它边界内的业务逻辑,像限速设置的具体后续操作,它不需要关心,那是业务流程需要关心的,也就是限速_x001D_领域服务需要去协作的。

好,那我们就看下限速领域服务的具体实现:

package com.qipeng.simboss.speedlimit.domain.service.impl;

/**

* 限速领域服务

*/@Servicepublic class SpeedLimitServiceImpl implements SpeedLimitService {

@Autowired

private SpeedLimitRepository speedLimitRepo;

@Autowired

private IotCardFacade iotCardFacade;

@Autowired

private DeviceRatePlanFacade deviceRatePlanFacade;

@Autowired

private CarrierApiFacade carrierApiFacade;

/**

* 批量限速检查

*/

@Override

public void batchSpeedLimitCheck() {

List<SpeedLimit> speedLimits = speedLimitRepo.listAll();

for (SpeedLimit speedLimit : speedLimits) {

List<IotCard> iotCards = iotCardFacade.listByByOrgId(speedLimit.getOrgId(), speedLimit.getPriceOfferId());

for (IotCard iotCard : iotCards) {

doSpeedLimitCheck(iotCard, speedLimit);

/**

* 单个限速检查

*/

@Override

public void doSpeedLimitCheck(SpeedLimitCheckContext context) {

String iccid = context.getIccid();

IotCard iotCard = iotCardFacade.get(iccid);

if (iotCard != null) {

SpeedLimit speedLimit = speedLimitRepo.get(iotCard.getOrgId(), iotCard.getPriceOfferId());

if (speedLimit != null) {

this.doSpeedLimitCheck(iotCard, speedLimit);

/**

* 执行限速逻辑

* @param iotCard

* @param speedLimit

*/

private void doSpeedLimitCheck(IotCard iotCard, SpeedLimit speedLimit) {

//todo this...

notify(iccid, speedLimit.getCardSpeedLimit());

/**

* 修改卡的限速值,并通知用户

*/

private void notify(String iccid, Double speedLimit) {

if (speedLimit != null) {

//todo this...

System.out.println("update iotCard SpeedLimit to: " + speedLimit);

System.out.println("notify...");

上面的代码看起来很多,其实干的事并不复杂,主要是业务流程:

通过SpeedLimitCheckContext上下文获取iccid,然后获取对应的限速对象和套餐流量对象。

通过限速聚合根获取需要设置的限速值(核心业务)。

调用相关接口进行添加/删除限速。

修改卡的限速值,并通知用户。

以上限速领域模型基本上比较丰富了,后面的业务迭代只需要改里面的代码即可。

好,我们再来看下应用服务中的代码:

package com.qipeng.simboss.speedlimit.application.service.impl;

@Servicepublic class SpeedLimitApplicationServiceImpl implements SpeedLimitApplicationService {

@Autowired

private SpeedLimitService speedLimitService;

@Override

public void batchSpeedLimitCheck() {

speedLimitService.batchSpeedLimitCheck();

@Override

public void doSpeedLimitCheck(String iccid) {

SpeedLimitCheckContext context = new SpeedLimitCheckContext();

context.setIccid(iccid);

speedLimitService.doSpeedLimitCheck(context);

应用服务不应包含任何的业务逻辑,只是工作流程的处理,比如接受参数,然后调用相关服务,封装返回等,如果需要持久化聚合根对象,调用仓储服务即可(可能会涉及到 UnitOfWork),另外,像限速聚合根对象的维护,也是实现在应用服务(因为不包含任何业务逻辑),比如创建限速聚合根,过程大概是这样:

应用服务接受参数,然后调用创建限速聚合根工厂(如SpeedLimitFactory),或者通过构造函数创建(包含业务规则,不符合则抛出错误),当然创建还包含聚合根附属的实体。

限速聚合根创建好了,调用仓储服务持久化对象。

返回操作结果。

那如何改善之前 MQ 中处理的一坨代码呢?答案就是一行代码:

@Testpublic void doSpeedLimitCheckTest() {

System.out.println("start....");

speedLimitApplicationService.doSpeedLimitCheck("1111");

System.out.println("end");

没错,调用下应用层的doSpeedLimitCheck服务即可,调用方完全不需要关心里面的业务逻辑,业务隔离。

单元测试执行结果:

结语

关于领域驱动设计的分层架构:

其实,我个人觉得 DDD 的首要核心是确定业务的边界(领域边界),接着把各个边界之间的关系整理清晰(上下文映射图),然后再针对具体的边界具体设计(战术设计),最后就是工作流程的处理,就像上面图中所表达一样。

好,改造完了,又可以和产品经理一起愉快的玩耍了。

作者:岳中新

审核编辑:符乾江
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 物联网
    +关注

    关注

    2939

    文章

    47343

    浏览量

    408252
  • 应用软件
    +关注

    关注

    0

    文章

    53

    浏览量

    9375
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    Lora技术应用领域

    1. 联网应用:Lora技术的远距离传输特性使其在联网应用中得到广泛应用。通过Lora技术,可以实现智能城市、智能家居、智能农业等多种
    发表于 11-26 08:10

    CW32L系列MCU的应用领域

    智能家居设备。 联网:如无线传感器网络、远程数据通信等联网应用场景。 工业控制:如自动化控制系统、工业仪表等。 医疗电子:如便携式医疗设备、健康监测设备等。
    发表于 11-25 07:22

    深耕蓝牙联网十年:北京桂花网 2015-2025 发展大事件全景

    在智慧校园、工业联网、智慧医疗养老、运动健康、蓝牙定位、智慧工地、智慧停车、冷链物流等诸多领域。我们的客户和合作伙伴遍布全球十几个国家和地区。桂花网的目标是为蓝牙插上翅膀,为
    发表于 11-07 13:52

    学习联网怎么入门?

    随着联网技术的不断发展,越来越多的人开始关注学习这一领域。但是对于初学者来说,联网似乎是一个庞杂的概念,学习起来很困难。因此,从哪里开始
    发表于 10-14 10:34

    学习联网可以做什么工作?

    学习联网专业后,你可以从事多种与联网相关的工作。联网是一个非常新颖和实用的
    发表于 10-11 16:40

    联网的应用范围有哪些?

    的应用范围极其广泛,众多行业都在积极引入这项技术,以提升效率、创新业务模式。 物流行业:在仓储环节,借助联网技术,可实现对货物的实时监测,了解货物的位置、状态等信息,优化仓储空间布局。运输过程中,通过
    发表于 06-16 16:01

    联网未来发展趋势如何?

    技术将为人们带来更加安全、便捷和舒适的居住环境。 工业互联网:工业互联网联网行业中的热门领域。通过将传感器、设备和计算机系统相连接,实
    发表于 06-09 15:25

    低轨卫星联网的创新应用领域探索

    作为无线通信技术爱好者和从业者,今天我想与大家探讨低轨卫星联网在实际生活中的多样化应用可能。虽然传统地面网络已覆盖大部分区域,但在偏远地区和特殊场景下,低轨卫星联网展现出独特优势。
    的头像 发表于 04-16 15:29 1367次阅读
    低轨卫星<b class='flag-5'>物</b><b class='flag-5'>联网</b>的创新<b class='flag-5'>应用领域</b>探索

    明远智睿SSD2351核心板在联网领域的应用实践

    联网作为当今科技发展的热门领域,将无数设备连接在一起,实现数据的采集、传输与共享,构建起一个智能化的世界。在这庞大的联网体系中,核心板扮
    发表于 04-11 11:50

    工业联网驱动工业变革的新引擎

    工业联网驱动工业变革的新引擎
    的头像 发表于 03-19 09:57 631次阅读

    蜂窝联网怎么选

    的数据传输速率。有了蜂窝联网技术,您就不必在功耗和数据传输速率之间做出妥协,而是可以两全其美。 终身成本:虽然某些 LPWAN 技术的前期成本可能看起来很吸引人,但评估整个生命周期的成本(部署
    发表于 03-17 11:46

    为什么选择蜂窝联网

    在为您的联网(IoT)应用评估最合适的低功耗广域网(LPWAN)技术时,除了考虑技术指标外,还必须考虑各种因素,因为技术指标本身可能与实际性能大相径庭。与 LoRaWAN、Sigfox
    发表于 03-17 11:42

    宇树科技在联网方面

    宇树科技在联网领域有多方面的涉及和发展,以下是一些具体信息: 传感器技术合作 与传感器公司合作:宇树科技与一些传感器技术公司有合作,例如奥比中光为宇树机器狗提供激光雷达及结构光传感器,这些传感器
    发表于 02-04 06:48

    联网就业有哪些高薪岗位?

    联网就业有哪些高薪岗位? 联网行业迎来了全面爆发式的发展,众多高薪岗位吸引着人们的目光。联网
    发表于 01-10 16:47

    多线示波器的原理和应用领域

    多线示波器是一种电子测量仪器,其原理和应用领域可以归纳如下:一、原理多线示波器在普通示波器原理的基础上,采用了双线(或多线)示波法。这种方法使得示波器能够同时显示多个波形。其基本原理是,示波器利用
    发表于 01-07 15:34