安庆大理运城常德铜陵江西
投稿投诉
江西南阳
嘉兴昆明
铜陵滨州
广东西昌
常德梅州
兰州阳江
运城金华
广西萍乡
大理重庆
诸暨泉州
安庆南充
武汉辽宁

领域驱动设计(DDD)实践之路(第二篇)

12月28日 终离去投稿
  在领域驱动里面,infrastructure作为基础设施,是提供技术细节的模块。需要强调的是,很多人会误以为infrastructure就是传统的DAO层,其实infrastructure包括但不限于DAO层,比如文件处理,三方调用,使用缓存,发送异步消息等具体的技术细节实现都存在于infrastructure层。那么技术细节是什么呢。在我们看来,技术细节包含以下特征与业务知识无关与我们常说的技术实现方案相关这里可能说得比较抽象,举几个例子来理解。
  案例1:我们的实体需要持久化(存储),所以我们需要提供存储的实现。领域层的repository。save等方法提供了持久化接口约定,对于infrastructure来说,如何实现这个方法的代码,就是技术细节。那么我们如何实现这个过程呢?自然是选择缓存,OSS存或者数据库存。如果选择数据库,则进而需要选择orm框架,配置。。。,实现repository。save的接口,这些都属于持久化所需的技术细节代码。
  案例2:我们的应用需要导出资产包相关的excel形式数据,那么当导出资产包数据时,文件领域模块提供了导出的统一接口,资产领域模块提供了资产包的适配接口,而导出excel的代码需要使用easyExcel或者POI等第三方框架,属于技术细节代码。
  案例3:接案例2,为了实现导出时所需的excel排版格式,排版本身的格式与业务有关,比如在我们的业务场景下,我们导出调解明细(我们项目特定的一个领域模型)的时候,只需要按照常见的导出方式即可,而导出资产明细(我们项目特定的一个领域模型)则需要解析拼接所有的动态数据列,合并显示每条数据不同的动态列,而这一切是由业务决定的。根据业务不同有不同的排版要求这一点体现了资产域需要提供文件域的导出策略,调解域也需要实现文件域的导出策略。这些都属于描述业务信息的约定,而这些约定的具体实现比如怎么把实体的那一个属性映射到excel的哪一行哪一列,则属于技术细节。这种区分方式显性化了业务的概念,同时又将实现放在了基础设施层,提供了一定的解耦性。
  说完了infrastructure的技术细节的定义,我们接下来聊几个在采用DDD研发模式下,infrastructure层开发过程中经常会遇到的一些问题及我们的解决方案。小数据量系统的save的实现细节Insert还是Update
  为了让业务逻辑和代码实现解耦,在repository的约定中,我们通常用save保存代替我们通常说的insert(插入),update(更新)这样的技术术语,以屏蔽技术细节。这样带来的一个副作用是,在save时就需要根据策略判断调用insert还是update,我们使用的策略是根据id是否是空决定,即我们所有的实体对象都有一个属性,类型为Id类的子类,id对象的属性(数据库里面实际存放的id值)可能为null,但是id对象,本身不会为null,根据这个对象可以判断当前实体id是否为空。对象关系体现数据库关联
  对于聚合场景,子实体是需要知道聚合根的id的,因为在存储到数据库时可能需要以外键的方式存储对象间的映射关系。
  然而,在具体实现中,我们认为,实体之间的对象关系才是标识两个实体之间关系的方式,而不是id,所以生成实体时,先通过对象引用关联对象,表明聚合和实体之间的关系,在保存到数据库的时候,通过实体生成数据库映射类的时候就可以知道当前数据的id是否为空,同时又能知道当前数据之间的关系。
  对象之间的关系在1:1聚合保存的时候可能体现不明显,但是当1:N或者N:N批量保存聚合的时候,作用就比较明显了。在我们的系统中发起调解业务就需要批量保存调解批次。代码如下(欢迎吐槽,拥抱进步)for(MediationBatchmediationBatch:mediationBatchList){得到聚合根MediationBatch的数据库ORM类(DO)MediationBatchDObatchDOMediationTransfer。toMediationBatchDO(mediationBatch);ListMediationRecordDOrecordListMediationTransfer。toMediationRecordDO(mediationBatch)。stream()。peek(recordrecord。setOperatorId(user。getAccountId()))。peek(recordrecord。setOperatorTenantId(user。getTenantId()。getTenantId()))。collect(Collectors。toList());if(mediationBatch。getId()。isNew()){batchDO。setId(idGenerator。getBatchId());recordList。stream()。peek(recordrecord。setBatchId(batchDO。getId()))。forEach(recordrecord。setId(idGenerator。getBatchRecordId()));insertBatchList。add(batchDO);insertRecordDOList。addAll(recordList);}else{updateBatchList。add(batchDO);updateRecordDOList。addAll(recordList);}}
  通过这种方式就解决了批量插入不能返回id,同时又能继续复用id。isNew()判断是否为新数据的方式(这里我们没有创建entity基类,所以判断放在了Id上)。
  以上方法提供了批量保存时如何区分是新增还是更新。下面我们来谈谈我们项目内提供的插入和更新模板代码。批量保存性能优化
  对于领域来说,save是基本的保存代码。方法传入的参数往往是一个存在于内存中的聚合根对象,有时包含全量的子实体,VO和全量的字段,而在插入场景,对批量请求我们希望支持批量插入,减少对数据库的IO频率,在更新场景下,我们希望减少update时的更新字段的数量(只更新需要更新的字段),这有助于减少数据库IO次数、binlog大小和mysql数据库索引变更带来的开销,所以是非常有必要的。因此对于infrastructure来说,可以提供统一的定制化模板方便repository定制化更新字段的方法快速实现。
  由于我们的系统使用的是mybatisplus的ORM方案,所以我们根据api和mysql的批量语句开关提供了一个批量插入和批量更新的Mapper基类,其中insertBatchSomColumn是mybatisplus自带的,updateBatchById则是我们实现的,文档链接如下https:mp。toutiao。comprofilev4graphicpreview?pgcid7062223527654916621通过这种方式可以轻松地提供定制化更新某几列的sql,减轻sql编写负担。业务决定技术方案的策略实现细节
  这一次要讲的其实就是上面提到过的excel导入导出的案例。对于我们的系统来说,具有资产域,文件域,调解域等。其中资产域、调节域等三个域需要导入导出excel。但是我们在设计的时候认为文件的操作属于文件域的概念,所以应当由文件的domain提供功能。但是很明显,具体的导入导出的策略根据数据的不同是可以变化的。所以针对这种情况,我们回归到领域驱动的实现的本质面向对象技术来思考这个问题的优雅解法。以导入为例excel的操作属于文件操作,所以属于文件域,所以文件域提供了一个接口ExcelParser,来表明文件域提供了excel的解析能力各个业务域有自己的具体的操作策略,所以各个域提供了自己的文件域的子接口,来表明自身域在excel的导入这件事上,资产与调解提供了解析能力AssetDetailExcelParser和MediationResultExcelParser。随后在infrastructure层,在deal。excel包(deal代表业务处理技术细节实现,excel代表excel相关业务)下提供了实现类和相关的其他类。
  代码如下packagexxx。domain。解析Excel,得到对象authorversionpublicinterfaceExcelParserTextendsSimpleStrategyT{解析excel对应的文件,得到对象输出paramurlexcel的路径对象,使用{linkFileIO}进行解析得到InputStream再处理return输出解析得到的对象ListTparse(FileRecordUrlurl);key,能够处理的数据类型,同时也是{linkcom。antgroup。antchain。unifyx。base。common。strategy。registrar。StrategyFactory}的路由key。return{linkExcelParserparse(FileRecordUrl)}的元素类型ClassTkey();OverridedefaultvoidputKey(SetObjectkeys){keys。add(key());}OverridedefaultC?extendsStrategystrategyGroup(){returnExcelParser。}}packagexxx。domain。解析资产明细excelauthorversonId:ParaseExcel。javav0。12021年10月28日16:26publicinterfaceAssetDetailExcelParserextendsExcelParserExportAssetDetailExcelVO{OverridedefaultClassExportAssetDetailExcelVOkey(){returnExportAssetDetailExcelVO。}}packagexxx。domain。mediation。调解回填excel解析器authorpublicinterfaceMediationResultExcelParserextendsExcelParserMediationResultVO{OverridedefaultClassMediationResultVOkey(){returnMediationResultVO。}}packagexxx。infrastructure。deal。调解回填excel解析器authorversonId:ParseAssetMediationExcelImpl。javav0。12021年11月02日11:27ComponentpublicclassMediationResultExcelParserImplimplementsMediationResultExcelParser{AutowiredFileIOfileIO;OverridepublicListMediationResultVOparse(FileRecordUrlurl){InputStreaminputStreamfileIO。openFile(url);try{returnEasyExcelFactory。read(inputStream,newSheet(1,3,MediationResultDTO。class))。stream()。map(item(MediationResultDTO)item)。map(itemMediationResultVO。create(newMediationBatchId(Long。parseLong(item。getMediationBatchId())),newMediationDetailId(Long。parseLong(item。getMediationDetailId())),MediationDetailStatus。convert(item。getMediationStatus()),item。getMediationNote()))。collect(Collectors。toList());}finally{fileIO。closeFile(inputStream);}}}
  上面4份代码是domain的,最下面的是infrastructure的,这里我们只讲infrastructure的(但是我个人认为领域分层后还是需要整体考虑的,所以才会贴上domain的代码)。这里的代码实现上与具体的技术有关,使用了EasyExcel,所以在实现上的时候,我们认为这个代码应当是一个技术细节,对于domain层来说不需要感知如何实现。因此放在了infrastructure。当前代码所在的包表明了在infrastructure的概念,这是业务流程中的excel处理的部分。继承关系是为了domain中的子概念接口赋能。
  这是我们对于跨域业务逻辑的处理办法。简单、小巧的领域事件发送功能实现细节
  为了保证各领域模型间的解耦,我们经常通过最轻量级的领域事件的方式实现,而不是类似metaq,msgbroker这样的异步分布式消息中间件。领域事件的发送有很多的实现方案,我们倾向于直接使用spring的功能,因为我们需要同步保证事务。但是spring的event发送需要继承ApplicationEvent而领域事件我们又希望独立于spring的event体系,所以我们通过对spring的了解发现了spring已经提供了PayloadApplicationEvent可以实现这种功能实现上和其他的spring的event一致,获取我们自己定义的event的方法如下OverridepublicvoidonApplicationEvent(PayloadApplicationEventevent){Assert。assertEquals(event。getPayload()。getClass(),TimeoutEvent。class);System。out。println(event);}
  这里的getPayload()可以获取到我们放进去的领域事件TimeoutEvent大数据量聚合加载的解决方案
  在任何系统中都会有批处理的业务。可能是批处理聚合,可能是批处理聚合内的实体类。这里说一下我之前遇到的一个帖子(jdon)上的讨论。帖子上说的是有一个排班业务,一条班表数据作为聚合存在着每日排班子实体,每日排班下又存在着排班明细子实体,当日期逐渐增加时一条排班需要加载好几年的数据用于生成聚合,而实际上则仅仅只需要计算最近几周的数据。这里存在两点问题聚合器是否必须加载完整如何实现这种部分加载
  第一点自然不用多说,技术实现以提供业务功能为核心是我一直以来的主张。所以当数据量可能会不断增大的情况下不用加载完整自然是必须的(哪怕内存存储的下也应当尽可能少的消耗)。第二点来说帖子的一位回复者倾向于DomainService提供专门的适配方法,用于加载几周的数据。
  我们的系统中存在一个有一些类似的业务。我们的系统需要每隔几分钟就运行一次批处理任务,获取所有已经过期的调解明细,并且设置为过期。调解明细属于调解批次的聚合,所以我们有同样的需求。
  我们在此提供一种我们的实现,供参考。
  repository提供iter(LocalDateTime):Iterator方法,直接在调解明细上过滤数据(正确的业务逻辑),返回一个迭代器,方便逐个处理聚合的业务iter返回的聚合里面,由于调解批次中的调解明细的数据量可能一次就会加载上千条,而调解批次本身不会受到调解明细的状态影响,所以这里我们也是加载部分实体组装聚合。具体代码如下LocalDateTimenowLocalDateTime。now();IteratorMediationBatchiteratorrepository。iter(now);。。。while(iterator。hasNext()){MediationBatchbatchiterator。next();timeoutAssetDetailIdList。addAll(batch。timeout(now));批量更新timeoutAssetDetailIdList}
  repository的实现根据面向对象原则,仅仅提供如何查询过滤数据库数据publicIteratorMediationBatchiter(LocalDateTimemediationDetailDeadlineMin){LambdaQueryWrapperMediationRecordDOwrappernewLambdaQueryWrapper();wrapper。le(MediationRecordDO::getDeadline,mediationDetailDeadlineMin);returnnewBatchIterator(newMediationIteratorUnit(wrapper,detailMapper,batchMapper));}
  迭代器的实现提供了迭代职责实现AntGroupCopyright(c)20042021AllRightsReserved。packagecom。antgroup。antchain。donpa。infrastructure。importjava。util。Cimportjava。util。Iimportjava。util。Limportjava。util。function。S批量迭代器authorversionpublicclassBatchIteratorTimplementsIteratorT{privatefinalSupplierListTbatchSprivatebooleanhasNprivateIteratorTlocalCacheIteratorCollections。emptyIterator();publicBatchIterator(SupplierListTbatchSupplier){this。batchSupplierbatchSloadCache();}OverridepublicbooleanhasNext(){更新hasNextloadCache();returnhasN}OverridepublicTnext(){returnlocalCacheIterator。next();}privatevoidloadCache(){if(!localCacheIterator。hasNext()){ListTdataListbatchSupplier。get();if(dataList。isEmpty()){hasN}localCacheIteratordataList。iterator();}}}
  至此实现了批处理加载聚合的逻辑,同时可以提供聚合的部分加载(需要注意业务的正确性不会因为聚合的不完全加载而产生问题)。
  最后总结一下领域驱动最初的初衷根据我的理解和论坛上其他人的讨论我们都认为是为了回归面向对象,提供更简单更易维护的方式解决业务。可以根据产品文档和业务语义拆解,根据场景模拟的方式分辨当前代码实现到底是技术还是业务,业务我们可能不熟,但是我们可以反向从更熟悉的技术入手采用排除法分辨。针对不同域之间概念交叉形成的业务,可以采用接口继承或组合的形式提供概念的结构,如果最终需要技术细节实现,则在infrastructure中实现。对于对象职责分离后,产生的底层方法无法适配优化性能的问题,在业界更容易被接收的做法就是domain的repository等接口提供适配的方法。其实这也提醒了我们编写领域驱动代码时,对象职责分离不代表我们开发人员的逻辑也是割裂的。我们需要用全局视角写出一个个独立的对象逻辑,这一点我认为是更为重要的一点。对于尽可能少加载对象的时候,我们可以根据面向对象原则,infrastructure实现时屏蔽底层实现逻辑,为domain提供通用的但是性能更好的实现方案
投诉 评论

麻烦不断?三星S22Ultra出现GPS问题,万元机皇变圾皇三星GalaxyS22系列发布以来风波不断,先后遭遇跑分作弊、限制性能、体验不佳等诸多问题。而最新有用户爆料,S22Ultra作为万元机皇竟然出现GPS失灵低级错误,网友批评其……为啥安卓的手机用久了会卡,而苹果不卡苹果和安卓本质上都是一种手机操作系统,安卓系统是开源的,苹果系统是封闭的。但各自系统对App应用的管理和分发机制是大不相同的。安卓App开发只需要符合安卓api接口规范,……强制安装软件谷歌被韩国罚款约1。77亿美元据美联社报道,韩国公平贸易委员会9月14日表示,由于美国谷歌公司将安卓操作系统强加给智能手机制造商,韩国已要求谷歌初步支付2074亿韩元(约合1。77亿美元)的罚款。报道称,这……苏宁收购万达百货续已接手运营,4月将完成交割新京报讯(记者陈维城)4月10日,据苏宁内部知情人士透露,三十多位万达百货总经理抵达南京苏宁总部,他们此行是参加苏宁时尚百货集团的月度例会。这意味着苏宁易购管理团队已全面……你手机收藏了什么美图,分享一下可以吗?把你手机收藏的美图发来分享一下?每个地方都有不同的美景,你的家乡美吗?手机里珍藏的美景图,美人图发来共享一下吧!答,好的,以下是我拍摄的一些风光照片和人像摄影,美女……苹果12应该买128还是256G?就一般而言,苹果应用的安装包可能比安卓有的要大一些,但安装好后实际占用用的空间两者是差不多的。所以判断你手机需要多大空间最好的方法,就是查看你目前手机使用了多少储存空间。……18年,三代人!他们发现玉米增产关键基因来源:人民网人民网北京4月9日电(记者孙竞)近日,中国农业大学与华中农业大学玉米研究团队合作研究成果在《科学》杂志发表。研究首次挖掘出同时控制玉米和水稻产量性状的关键基因……领域驱动设计(DDD)实践之路(第二篇)在领域驱动里面,infrastructure作为基础设施,是提供技术细节的模块。需要强调的是,很多人会误以为infrastructure就是传统的DAO层,其实infrastr……中国工业互联网能否引领世界?领军企业与顶级学者这样说每经记者:可杨每经编辑:陈俊杰随着数据成为新时代的石油,我国对于工业互联网的探索也在加速。由清华大学社会科学学院、清华大学全球产业研究院共同主办,每日经济新闻协办的……四大运营商9。2日政企信息化项目本日四大运营商中标全国政企信息化项目5亿1898万元,其中电信中标17592万元,占比33。9,排名第二;广电2343。52万元,占比4。52,排名第四;联通12048万元,占……2021年上半年新能源汽车各城市销量排行榜前三十北、上、广、深及杭州位列前五,不愧为我国的经济发达城市,上海可以说是独一档,半年销量突破11万辆,深圳位居第二,半年销量也超过6万辆,北京、杭州、广州分别排在第三、四、五位,半……了解Kubernetes资源类型在深入研究Kubernetes资源之前,让我们先澄清一下资源一词在这里指的是什么。我们在Kubernetes集群中创建的任何东西都被视为一种资源:部署、pod、服务等。在本文中……
有个喜欢拍Vlog,平时也爱玩和平精英的女朋友,送啥手机好?IEEE专家展望2022年新常态数智消费体验的机遇与挑战美团王兴捐赠145亿股票!互联网巨头为何都热衷于捐股份?一农民工自封互联网先知并扬言要做后互联网时代最大公司为iPhone14让路,苹果手机产品线售价大调整情人节送什么礼物最合适?用TCLT7E玩吃鸡好不好?高通宣布aptX无损蓝牙能听无损CD音质了Neo4j针对2022图数据平台发展的十大预测为什么国内很多中小企业生存周期只有3到5年甚至更短?麒麟90005G双环主摄HMS3。0,Mate系列定档3月份SpringBoot进阶之整合Shiro鉴权框架(四)彩礼超过两万就是贩卖人口罪吗所有的突然离开,其实都是很长的决定初中生物会考知识点归纳生物与环境初中物理教师工作总结什么面膜可以去白头粉刺在Win7系统中kxescore。exe出现错误怎么办?有关改变人的一生名言笔记本触控板的滑动条失灵怎么办AMDRadeonRX5300的泄漏表明Red团队可能很快会香港十大美女排行榜有一种绝美叫做王祖贤微笑的女人,最迷人一次性的染发膏怎么用

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找七猫云易事利