当前位置: 首页 > 科技观察

转转价格系统DDD实践

时间:2023-03-21 10:25:58 科技观察

客观理解DDDDDD,即领域驱动设计,不仅给我们带来了一套全新的概念,还提供了一套全新的设计思路,应用于构建大型和复杂的软件系统。与DDD相比,我们使用的传统设计思想往往被称为数据驱动设计,并且经常应用于中小型项目。互联网项目往往迭代很快。起初,一个小项目会逐渐演变为一个中到大型项目。在演化过程中,结构容易腐败,内部模块边界不清晰,耦合严重。全身一动,这时候,重建的方法往往就迎刃而解了。但重构毕竟费时费力,对业务的好处并不直观,所以人们往往会想,在长期的迭代中,如果有一些方法可以让系统保持稳定的架构和逻辑清晰,那么可以节省很多成本,甚至可以节省写文档的成本(代码就是文档)。问题往往有很多种解决方案,EricEvans为我们总结了一套DDD理论。其实解决问题的思路并不局限于DDD。换句话说,我们所理解的DDD可能与EricEvans总结的有所不同。只要是解决问题的有效方法,都值得赞扬。DDD和传统的设计思路,其实没有必要逐点比较。每个都有自己的优点和缺点。追求或否定某一个是不客观的。我们解决不同类型的问题并使用不同的方法。解决问题时,尽可能使用最简单的思维。对于迭代次数不多的小系统,没有必要使用DDD(或全套DDD),它会给你带来更多的问题,保持精简是很有必要的。但是,对于大型系统,或者不断向大型系统迭代的系统,一些在小型系统中不容易突出的问题会逐渐被放大和突出(比如逻辑臃肿、模块边界不清晰)。使用简单的设计思想往往无法约束这些问题的扩展。这时候就需要更加细化的规范来抑制问题的发生和发展。因此,也可能会产生更多的概念,这也是为什么会出现很多DDD概念的原因。对于DDD的很多概念,学习成本会变高,并且会给实现增加很多困难。一个项目可能伴随着一个烦恼,就是“一不小心又回到了传统的方式”,最后觉得自己做了一个“四不一样的东西”。其实我们要做的DDD并不是说要把所有的内容都按照EricEvans总结出来的理论概念去实践。它只能作为指南。具体情况要结合我们自己的公司和项目组成员。、经营状况等因素来决定。这就像马克思主义理论在中国的实践,必须结合中国的具体国情。为了DDD而进行DDD是没有意义的。始终关注我们的目的是一个非常重要的原则,目的也决定了我们在遇到一些细节性的技术选择时如何取舍。DDD在转让价格体系实践过程中的业务理解转让价格体系(估算器)是一个非常复杂的系统,它承载着转让回收和众多门店的价格计算和水平计算能力,同时也提供价格实验能力。由于系统的复杂度较高,下面介绍的内容是系统的简化版。价格体系估价的一般过程是估价员收到机检报告后,首先对机检报告进行分析,然后将机检报告转化为计价项。然后根据请求的参数找到合适的报价流程,在报价流程中执行配置的报价方式计算价格。因为价格的计算是根据不同的场景,比如转转C2B线上回收,转转门店回收,转转B2C门店,转转门店零售门店等,不同的场景需要关联不同的参数配置和报价方式,所以这里我们抽象出一个概念“场景”来关联这些参数配置和报价方式。价格的计算必须根据商品的客观条件和质量。转转作为专业的二手交易平台,可以出具专业的机器检测报告。那么价格的计算取决于检验报告中给出的数据。转转检测报告数据由检测工程师填写,检测项目专业、详尽、丰富。如果直接发给运营人员进行价格维护,会有很大的维护成本。因此,需要将机检报告中的项目通过关系转换为易于人工维护的计价项目。后续运营商维护价格会更加方便快捷。此步骤为计价项目转换。评估项目转换后,必须执行评估流程。估值流程封装了一种或几种估值方法或估值算法,我们暂且称之为估值方法,例如人工价目表、等级价格、算法模型等。此外,估值流程还封装了不同估值方法的执行过程.比如B2C零售店,先用算法模型,如果不能给出价格,就用人工价目表。最后,根据这些逻辑,输出一个价格。在开始实践之前,了解业务非常重要。需要统一每个概念的语言,即消除团队成员之间的理解偏差。我们的目标是构建一个架构好、可测试性强、学习成本低、易于扩展和维护的系统。策略设计通过对业务逻辑的理解,我们可以得到如下的子域划分:场景子域,通用域,为其他域提供配置参数。检验报告对子字段和支撑字段进行分析,为估值提供前置数据支持。估价项目转换子字段,即支持字段,也为估价提供前置数据支持。报价流程子域,支撑域,为报价提供流程封装。报价方法子域,核心域,提供报价的计算方法,是业务的核心,需要付出很大的努力。在战术设计的这一步,我们设计了限界上下文。在这里,每个限界上下文对应一个子域,并详细设计生成的域模型。例如下图是机器检测报告的解析上下文,绿色代表实体,黄色代表值对象。此上下文依赖于外部机器检查报告服务,并使用防腐层进行适配。此上下文输出解析的机器检查报告。对于机器检验报告,每个产品都有唯一的副本和唯一的标识,因此属于一个实体。机检报告应包括产品类别、品牌、型号及其机检项目,均属于价值对象,汇总为机检报告实体。ContextintegrationContextintegration可以简单理解为每个context是一种什么样的关系。从概念上讲,有许多上下文集成关系:单独的方式客户-供应客户/供应商发布-订阅发布者/订阅者开放主机服务和发布语言开放主机服务,公共语言反腐败层反腐败层尊重者从众共享内核共享内核合作伙伴伙伴关系其中很多概念很少用到,引入太多的概念对我们解决问题可能意义不大。这里只用了“合作伙伴”、“开放主机服务和发布语言”、“防腐层”。“合作者”可以表示系统中两个限界上下文之间的依赖关系,后两者可以表示与外部系统限界上下文的依赖关系。其中,“PS”代表合作关系,“U”代表上游,“D”代表下游。在这里,上游和下游的方向正好与依赖方向相反。“ACL”代表反腐败层,与“OHS/PL”开发托管服务和发布语言结合使用。架构设计架构理论发展至今,各种新的架构不断涌现。除了我们常用的分层架构外,还有整齐的架构、六边形架构、洋葱架构、CQRS架构等,各有各的特点和优缺点。从学习成本和团队成员体验的角度出发,这里采用松散(跨层可调用)的分层架构。api层作为接口定义层,被其他服务所依赖。应用层作为应用服务层,实现api层的接口。领域作为领域层来实现核心业务逻辑。基础设施作为基础数据层,为上层提供数据接口和对外调用的防腐。除了核心估值业务逻辑外,系统还包括后台维护功能。这部分主要是数据的增删改查操作。逻辑简单,不需要进入领域层,直接从基础设施中获取应用层。工程结构与架构一致。在此基础上,每一层可能依赖相同的常量、工具和基本算法,可以单独打包为一个通用包。于是得到如下项目结构:evaluation_sys?api?application?domain?infrastructure?通用业务逻辑实现在传统的MVC模式中,我们经常使用三层架构,即controller、service、dao或者类似的方法。这种架构会把所有的业务逻辑都堆在服务下面,领域实体只做数据传输,没有任何行为。随着项目的迭代,服务可能会变得臃肿,业务逻辑非常多。如果把服务做肥了,业务逻辑就会变得混乱,理解和维护的成本会非常巨大。但是,我们希望代码不仅用于执行,还用于阅读。表达的业务逻辑一目了然,一目了然,这就是我们所追求的。一个好的代码结构就是把各个业务逻辑按照一定的原则分离出来,然后用一种机制把它们组织好。根据DDD的思想,应用服务编排领域服务来描述业务的主要逻辑,每个领域的详细逻辑都被领域服务封装起来,逻辑清晰分离。例如在价格系统中,评估应用服务是这样实现的:publicEvaluateResulteval(Scenarioscenario,EvaluateContextcontext){//获取检测报告QcReportreport=qcReportService.parseReport(context.getQcCode());//EvaluationitemConvertEvaluateItemsevaluateItems=evaluateItemsService.transfer(report,scenario);//执行评估流程EvaluateResultresult=evaluateProcessService.evaluate(scenario,context,evaluateItems);//返回结果returnpreresult;}其次,DDD提倡域对象的行为,这不仅更符合面向对象的理念,让对象更贴近客观世界,将逻辑重新划分,让领域服务中的主干逻辑和细节逻辑实现清晰分离.例如评估流程的领域服务实现如下:(scenario);//获取评估流程EvaluateProcessprocess=EvaluateProcessFactory.create(scenario,context,algorithms,evaluateItems);//执行评估流程reutrnprocess.evaluate();}//...}其中一个评估流程实现如下:/**?最高价评估流程的实现*/publicclassMaxPriceEvaluateProcessimplementsEvaluateProcess{//对象属性privateScenarioscenario;privateEvaluateProcesscontext;privateListalgorithms;privateEvaluateItemsevaluateItems;/**?Object行为,计算价格*/publicEvaluateResultevaluate(){longmaxPrice=0;//遍历算法,计算价格分别为(EvaluateAlgorithmalgorithm:algorithms){longprice=algorithm.calculate(context,evaluateItems);if(maxPrice