在这个万物网络的时代,物联网业务蓬勃发展,但也瞬息万变,对于开发人员来说,这是一种挑战,但也是一种“虐待”。在业务发展初期,因为时间受限,我们一般不会遵循“小步快跑,递归试错”的原则展开业务研发,用通俗的话来说就是“nobb,先上了再说”,对于开发人员的明确构建,就是“脚本式”的开发方式,或者说是数据的CURD,这样的开发方式,在项目早期没什么问题,但随着新的业务的大大重新加入,业务递归的频密,我们不会找到,现在的业务系统显得更加冗杂,新加一个市场需求或者改为一个业务,显得无比艰难,因为业务构建彼此之间模糊不清,业务规则在代码中无处不在,开发人员也就无从下手。
那怎么解决问题上面的问题呢?有可能很多人会说道“你这代码敢,重构呀”,是的,我们找到了项目中的“怕代码”,比如一个类上千行,一个方法几百行,于是我们把一些代码抽离出来,做到一些内聚的构建,代码规范做到一些调整,但这样只是解决问题现在项目代码中的问题,下次项目递归的时候,你并无法确保写出的新代码是符合规范的,而且最重要的是,重构并无法在业务代码上给一个定义,什么意思呢?比如你重构一个方法,你不能从技术的角度去重构它,并无法从业务的角度去重构,因为在整个业务系统“恐慌”的情况下,你无法确保自己的“无罪”。另外还有一点,即使你重构了它,但对于新的重新加入的开发人员来说,他并无法解读你重构的目的,换句话说,就是如果他要用于或改为这个方法,他几乎不告诉能无法用于或者用于了会会影响其他业务,说白了就是,业务的边界不具体。那如何定义业务的边界呢?答案就是运用EricEvans明确提出的领域驱动设计(DomainDrivenDesign,全称DDD),关于DDD的涉及概念,这边就不描述了,网上有很多资料,必须留意的是,DDD注目的是业务设计,并非技术构建。
物联网业务如何应用领域驱动设计?这只不过是个大命题,该怎么构建?如何杀掉呢?我去找了我之前做到的一个业务市场需求,来做到示例,想到“脚本式”的构建,和DDD的构建,前后有什么不过于一样的地方。脚本式的研发业务市场需求:针对物联网卡的当前套餐使用量,根据一定的规则,展开特定的车速设置。市场需求看上去很非常简单,下面要明确构建了,首先,我创立了三张表格:speed_limit:车速表格,包括用户ID、套餐ID等。speed_limit_config:车速配备表格,包括车速档位,也就是套餐使用量在什么区间,车速多少的配备。
speed_limit_level:车速级别表格,包括车速的单位和明确值,主要界面自由选择用于。然后再行创立对应“贫血”的模型对象(没任何不道德,并且属性和数据库字段一一对应):publicclassSpeedLimit{privateLongid;privateIntegerorgId;privateLongpriceOfferId;//gettersetter....}publicclassSpeedLimitConfig{privateLongid;privateLongspeedLimitId;privateDoubleusageStart;privateDoubleusageEnd;//gettersetter....}publicclassSpeedLimitLevel{privateLongid;privateStringunit;privateDoublevalue;//gettersetter....}好,数据库表和模型对象都创立好了,接下来做到什么呢?CURD啊,界面必须对这些数据展开查阅和确保,所以,我创立了:SpeedLimitMapper.xml:数据库采访SQL。
SpeedLimitService.java:调用Mapper,并回到数据。SpeedLimitController.java:拒绝接受前端传送参数,并调用Service,PCB回到数据。非常简单看下SpeedLimitService.java中的代码:publicinterfaceSpeedLimitService{List<SpeedLimit>listAll();SpeedLimitVOgetById(Longid);Booleaninsert(IntegerorgId,LongpriceOfferId,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的意思是防腐层(AnticorruptionLayer),它的起到就是隔绝各个上下文,以及协商上下文之间的通信。车速上下文内部的构建(如单体根和实体等),只不过就是战术设计的明确构建,关于概念这边就不多说道了,这里说道下明确的设计:SpeedLimit单体根:毫无疑问,车速上下文的单体根是车速单体根,也可以称作单体根实体,这里的SpeedLimit并不是上面贫血的模型对象,而是包括车速业务逻辑的单体对象。SpeedLimitConfig实体:车速配备实体,在生命周期内有唯一的标识,并且依附于车速单体根。
SpeedLimitLevel实体:只不过车速级别应当设计成值对象,因为它并没生命周期和唯一标识的概念,只是一个明确的值。SpeedLimitContext值对象:车速上下文,只包括明确的值,起到就就是指应用层发动调用到领域层,可以看作是传输对象。SpeedLimitService领域服务:因为牵涉到到多个上下文的协商和交互,车速单体根并无法独立国家已完成,所以这些单体根已完成没法的操作者,可以放在领域服务中去处置。
SpeedLimitRepository仓储:车速单体对象的管理中心,可以数据库存储,也可以其他方式存储,不要把Mapper接口定义为Repository模块。以上因为很差在现有项目中做到改建,我就用SpringBoot做到了一个项目示例(SpringBoot用一起知道很爽,简练高效,做到微服务十分好),大体的项目结构:├──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包在路径:importcom.qipeng.simboss.speedlimit.domain.aggregate.SpeedLimit;//单体根importcom.qipeng.simboss.speedlimit.domain.entity.*;//实体importcom.qipeng.simboss.speedlimit.domain.valobj.*;//值对象importcom.qipeng.simboss.speedlimit.domain.service.*;//领域服务importcom.qipeng.simboss.speedlimit.domain.repo.repository.*;//仓储importcom.qipeng.simboss.speedlimit.repo.dao.*;//mapper模块importcom.qipeng.simboss.speedlimit.application.service.*;//应用层服务好,基本上这个项目设计的差不多了,必须留意的是,上面核心是com.qipeng.simboss.speedlimit.domain包在,里面包括了最重要的业务逻辑处置,其他都是为此服务的,另外,在领域模型不断完善的过程中,必须持续对领域模型展开单元测试,以确保其健壮性,并且,设计SpeedLimit单体根的时候,不要再行考虑到数据库的构建,如果必须数据展开测试,可以在SpeedLimitRepository中Mock对应的数据。看下SpeedLimit单体根中的代码:packagecom.qipeng.simboss.speedlimit.domain.aggregate;importcom.qipeng.simboss.speedlimit.domain.entity.SpeedLimitConfig;importcom.qipeng.simboss.speedlimit.facade.model.IotCard;importlombok.Data;importjava.util.Date;importjava.util.List;/***车速单体根*/@DatapublicclassSpeedLimit{/***车速*/privateLongid;/***的组织ID*/privateIntegerorgId;/***套餐ID*/privateLongpriceOfferId;/***车速配备子集*/privateList<SpeedLimitConfig>configs;/***否移除当前车速,不长久化*/privateBooleanisDel=false;/***卡的车速值,不长久化*/privateDoublecardSpeedLimit;/***提供车速值*/publicDoublechooseSpeedLimit(DoubleusageDataVolume,DoubletotalDataVolume,LongcardPoolId,BooleanisRealnamePassed,DoublecurrentSpeedLimit){//todothis...}/***设置否移除当前车速*/privatevoidsetIsDelSpeedLimit(DoublecurrentSpeedLimit){//辨别当前车速否不存在,如果不存在,则移除现有的车速配备//todothis...}}上面注解写出的较为多(便利解读),SpeedLimit单体根和之前的SpeedLimit贫血对象比起,主要有以下改动:SpeedLimit单体根并不只是包括getter和setter,还包括了业务不道德,并且也不和数据库表格一一对应。
SpeedLimit单体根中包括configs对象(车速配备子集),因为车速配备实体依附于SpeedLimit单体根。SpeedLimit单体根中的chooseSpeedLimit方法,意思是根据某种规则从车速配备中,挑选当前要车速的值,这是车速的核心业务逻辑。
那为什么不把整个车速设置的逻辑写出在SpeedLimit单体根中?而只是构建挑选要车速的值呢?为什么?为什么?为什么?答案很非常简单,因为车速设置的整个逻辑必须牵涉到到多个上下文的协作,SpeedLimit单体根几乎Hold不了呀,所以要把这些逻辑写出到车速领域服务中,还有最重要的是,SpeedLimit单体根只注目它边界内的业务逻辑,像车速设置的明确先前操作者,它不必须关心,那是业务流程必须关心的,也就是车速_x001D_领域服务必须去协作的。好,那我们就看下车速领域服务的明确构建:packagecom.qipeng.simboss.speedlimit.domain.service.impl;/***车速领域服务*/@ServicepublicclassSpeedLimitServiceImplimplementsSpeedLimitService{@AutowiredprivateSpeedLimitRepositoryspeedLimitRepo;@AutowiredprivateIotCardFacadeiotCardFacade;@AutowiredprivateDeviceRatePlanFacadedeviceRatePlanFacade;@AutowiredprivateCarrierApiFacadecarrierApiFacade;/***批量车速检查*/@OverridepublicvoidbatchSpeedLimitCheck(){List<SpeedLimit>speedLimits=speedLimitRepo.listAll();for(SpeedLimitspeedLimit:speedLimits){List<IotCard>iotCards=iotCardFacade.listByByOrgId(speedLimit.getOrgId(),speedLimit.getPriceOfferId());for(IotCardiotCard:iotCards){doSpeedLimitCheck(iotCard,speedLimit);}}}/***单个车速检查*/@OverridepublicvoiddoSpeedLimitCheck(SpeedLimitCheckContextcontext){Stringiccid=context.getIccid();IotCardiotCard=iotCardFacade.get(iccid);if(iotCard!=null){SpeedLimitspeedLimit=speedLimitRepo.get(iotCard.getOrgId(),iotCard.getPriceOfferId());if(speedLimit!=null){this.doSpeedLimitCheck(iotCard,speedLimit);}}}/***继续执行车速逻辑**@paramiotCard*@paramspeedLimit*/privatevoiddoSpeedLimitCheck(IotCardiotCard,SpeedLimitspeedLimit){//todothis...notify(iccid,speedLimit.getCardSpeedLimit());}/***改动卡的车速值,并通报用户*/privatevoidnotify(Stringiccid,DoublespeedLimit){if(speedLimit!=null){//todothis...System.out.println("updateiotCardSpeedLimitto:"+speedLimit);System.out.println("notify...");}}}上面的代码看上去很多,只不过干的事并不简单,主要是业务流程:通过SpeedLimitCheckContext上下文提供iccid,然后提供对应的车速对象和套餐流量对象。通过车速单体根提供必须设置的车速值(核心业务)。调用涉及模块展开加到/移除车速。
改动卡的车速值,并通报用户。以上车速领域模型基本上比较丰富了,后面的业务递归只必须改为里面的代码才可。好,我们再行来看下应用服务中的代码:packagecom.qipeng.simboss.speedlimit.application.service.impl;@ServicepublicclassSpeedLimitApplicationServiceImplimplementsSpeedLimitApplicationService{@AutowiredprivateSpeedLimitServicespeedLimitService;@OverridepublicvoidbatchSpeedLimitCheck(){speedLimitService.batchSpeedLimitCheck();}@OverridepublicvoiddoSpeedLimitCheck(Stringiccid){SpeedLimitCheckContextcontext=newSpeedLimitCheckContext();context.setIccid(iccid);speedLimitService.doSpeedLimitCheck(context);}}应用服务不不应包括任何的业务逻辑,只是工作流程的处置,比如拒绝接受参数,然后调用涉及服务,PCB回到等,如果必须长久化单体根对象,调用仓储服务才可(可能会牵涉到到UnitOfWork),另外,像车速单体根对象的确保,也是构建在应用服务(因为不包括任何业务逻辑),比如创立车速单体根,过程大约是这样:应用服务拒绝接受参数,然后调用创立车速单体根工厂(如SpeedLimitFactory),或者通过构造函数创立(包括业务规则,不合乎则抛错误),当然创立还包括单体根附属的实体。
车速单体根创立好了,调用仓储服务长久化对象。回到操作者结果。那如何提高之前MQ中处置的一坨代码呢?答案就是一行代码:@TestpublicvoiddoSpeedLimitCheckTest(){System.out.println("start....");speedLimitApplicationService.doSpeedLimitCheck("1111");System.out.println("end");}到底,调用下应用层的doSpeedLimitCheck服务才可,调用方几乎不必须关心里面的业务逻辑,业务隔绝。
单元测试继续执行结果:结语关于领域驱动设计的分层架构:只不过,我个人实在DDD的首要核心是确认业务的边界(领域边界),接着把各个边界之间的关系整理明晰(上下文同构图),然后再行针对明确的边界明确设计(战术设计),最后就是工作流程的处置,就像上面图中所传达一样。好,改建完了,又可以和产品经理一起无聊的嬉戏了。
本文来源:皇冠游戏中心官网-www.sriingenieria.com
Copyright © 2003-2023 www.sriingenieria.com. 皇冠游戏中心官网科技 版权所有 ICP备16604452号-7 XML地图 网站模板