代码是什么意思(DDD)
如果有更好的建议或者想看更多关于综合百科技术大全及相关资讯,可以多多关注茶馆百科网。

与常规的MVC架构相比,简介:'s DDD更加抽象和难以理解,不同的开发者对DDD有不同的解释。那么哪种设计方法更好呢?你学习的时候怎么知道哪个DDD更正统,不被别人引入歧途?本文试图用& quotDDDasCode & quot,即用DSL代码描述DDD,统一DDD的设计思路,通过一个案例详细介绍如何基于ContextMapper完成一个基于DDDDSL的项目的表达,分享DDD的设计过程与微服务的关系。
网上有很多关于DDD的文章,这当然是件好事。每个人都想掌握好的设计方法来解决软件开发中的问题。但是,也存在一些问题。如果你随便在网上打开几篇DDD的文章,虽然每个作者都说自己按照DDD的思路设计了建筑,但细心的同学会发现,每个作者的DDD文章中的结构描述和建筑图纸都大相径庭。你会非常惊讶的。这些都是DDD的设计吗?这里其实存在一个问题,就是在通过文字和图表描述一些抽象概念的时候,会有很大的差异。不要用盲人摸象的概念来类比。这是不合适的。即使两个学生非常熟悉DDD,并且已经实践了许多项目好几年,他们所写的仍然是不同的。我进入Java早了一点,当然你可以说我老了,保守了。我记得最开始没有那么多中间件,但是我是基于Struts1.x这种MVC框架开发的,不同同学写的设计文档也有很大差异。这么简单的MVC架构可以有不同的架构设计文档,而DDD相对更抽象,更难理解,所以架构设计文档长短不一也是可以理解的。
那么我们是否必须接受这个事实& quot不同作者对DDD的解释不一定相同& quot和& quotDDD设计文件可以以不同的形式呈现& quot?如果是这样,想学DDD的学生会有很大的负担。哪个设计表达更好更容易理解。同时,我怎么知道我的DDD是比较正统的,没有被别人引入歧途?我不是说不能进行创造性思维,只是从说教的角度来说,还是要尊重理论事实。
我们都知道代码在表达一些业务或者逻辑的时候是可以反映真实情况的。即使是不同的开发者写的,考虑到设计模式、命名规范和开发语言的限制,代码基本都是一样的,容易理解。如果有单元测试和代码评审就更好了。这也是当一些文件不完善时,许多学生选择阅读代码,有些学生说,& quot直接看代码,不要看他们的PPT和文档,会有误导,不然不知道怎么死。"另外我们都知道现在的一切都是非常好的做法,比如基础设施easCode的Terraform,Platformascode的Kubernetesyall,Diagrams Code的PlantUML等等。那么,是否可以使用DDDasCode的概念,让我们的设计更统一,更方便表达设计思想,更容易被人理解?
DDDDSL
用DSL来表达DDD,这种方式已经存在很久了,但是更倾向于DDD的TacticDesign和代码层面,比如Sculpor [1]和Fuin。OrgddDSL [2],一般认为是基于Xtext的DDD码生成器。我要努力学那么多,就是为了生成一些代码,而且只是Java代码,所以一般关注度不高。能否让DDDDSL更有策略设计除了代码生成,突出设计思路,那么DDDasCode就全面多了。接下来我们将介绍ContextMapper的框架。
术语解释:许多学生对DDD的战略设计和战术设计的区别有一些疑问。DDD有专门的介绍,如下:
战术DDD:实体,价值对象;聚合、根实体、服务、域事件。工厂,仓库.战略DDD:有界语境,语境图;PublishedLanguage,SharedKernel,OpenHostService,客户-供应商,一致性,反腐败层(contextrelationshiptypes).其实比较简单,战略设计更大更宏观。你可以理解为公司高层讨论的业务和技术方向,各个团队或产品之间的分工和合作;战术设计要小得多,主要集中在一个BoundedContext中,比如如何设计DDD实体、服务、存储库等。加上应用开发可能的技术选择,可以说更注重技术层面。
ContextMapper框架介绍
ContextMapper是一个开源项目[3],主要为DDD设计提供DSL支持,如DDD的战略设计、ContextMapping、BoundedContext建模、服务解耦等,那么我们来看看如何基于ContextMapper完成一个基于DDDDSL的项目表达式。在介绍ContextMapper的时候,先说明一下项目背景。如花是架构师,对DDD非常熟悉。
悉,而且有过几个项目的DDD实践,最近他加入会员线,负责完成对会员系统的改造,更好地配合公司的微服务化的设计思路。会员线之前就是三个应用:会员中心对外提供的大量的RESTAPI服务;会员注册和登录应用;会员中心,处理会员登录后如修改个人密码、基本信息、SNS第三方绑定和支付方式绑定等。如花加入会员团队后,和大家沟通了基于DDD+MicroServices的架构思想,大家都表示同意,但是如何落实到具体的架构设计和文档上,大家就犯难啦。让我们看一下最典型的DDD设计图:
其中的概念,如SubDomain、BoundedContext、Entity、ValueObject、Service、Repository、DomainEvent,以及Context映射关系(ContextMapping),这些都没有问题,但是如何向他人表达这个思想?总不能每次都把DDD设计图和分层图都贴上去,然后说我就是按照DDD设计的。
从SubDomain开始
如花开始DDD的第一步,也就是Subdomain的划分。当然DDD中包括三种类型的SubDomain,分别为通用(Generic)、支撑(Supporting)和核心(Core)三种类型,这里稍微说明一下这几者的区别:
通用(Generic)Domain:通用Domain通常被认为已经被行业解决的问题,如架构设计中的可观测性的Logging、Metrics和Tracing,各种云服务(CloudService)等,这些都已经有比较好的实现方案,对接就可以。当然业务上也有,如成熟的行业解决方案,如ERP、CRM、成熟硬件系统等,你购买就可以啦。支撑(Supporting)Domain:和通用Domain类似,但是系统更来自内部或者还需要在通用的基础上进行一些定制开发。如一个电商系统,会员、商品、订单、物流等业务系统,当然还有一些内部开发的技术类型支撑系统。核心(Core)Domain:也就是我们常说的业务核心,当然如果是技术产品,就是技术核心,这个也就是你最要关注的。这三者整体关系如下:Core是最与众不同且花费精力比较多的,在复杂性Y维度,我们要避免高复杂度的通用和支撑Domain,这样会分散你的注意力,同时还要投入非常大的精力,如果确实需要,购买服务的方式可能最佳。
图源:https://github.com/ddd-crew/ddd-starter-modelling-process
如花首先将会员先划分为几个SubDomain,如处理账号相关的Account,处理会员打标的UserTag,处理支付方式的PaymentProfile,处理社交平台集成的SnsProfile,还有一个其他Profiles,这里我们不涉及Generic和SupportingDoman的规划,主要从业务核心Domain出发。一个同学用PPT阐述了划分结构和出发点,如下:
但是也有同学说是不是UML的Component图更好一些,方便和后面的UML图统一,如下:
当然还有其他如Visio等非常多的图示工具用于展现结构图。DDD的第一步:SubDomain的划分和展现,就有不同的理解方式,如何描述、如何图形化展现,都有不少的分歧。
回到问题的出发点,我们就想划分一下SubDomain,那么是不是下述的DSL代码也可以:
DomainUser{domainVisionStatement="Userdomaintomanageaccount,tags,profilesandpaymentprofile."SubdomainAccountDomain{type=CORE_DOMAINdomainVisionStatement="Accountdomaintosavesensitivedataandauthentication"}SubdomainUserTagDomain{type=GENERIC_SUBDOMAINdomainVisionStatement="UserTagdomainmanageuser'sKVandBooleantag"}SubdomainPaymentProfileDomain{type=CORE_DOMAINdomainVisionStatement="Userpaymentprofiledomaintomanagecredit/debitcard,Alipaypaymentinformation"}SubdomainSnsProfileDomain{type=CORE_DOMAINdomainVisionStatement="UserSnsprofiledomaintomanageuserSnsprofileforWeibo,Wechat,FacebookandTwitter."}SubdomainProfilesDomain{type=CORE_DOMAINdomainVisionStatement="Userprofilesdomaintomanageuserbasicprofile,interestprofileetc"}}虽然目前我们还不知道对应的DSL代码语法,但是我们已经知道Domain的名称、domain类型以及domain的愿景陈述(visionStatement),至于后期以何种方式展现系统Domain,如表格、图形等,这个可以考虑基于现在的数据进行展现。其中的UserTagDomain类型为GENERIC_SUBDOMAIN,这个表示打标是通用性Domain,如我们后期可以和商品、图片或者视频团队合作,大家可以一起共建打标系统。
注意:Subdomain不只是简单包括type和domainVisionStatement,同时你可以添加Entity和Service,其目的主要是突出核心特性并方便你对Domain的理解,如Account中添加resetPassword和authBySmsCode,相信大多数人都知道这是什么含义。但是注意不要将其他对象添加到Subdomain,如VO,Repository,DomainEvent等,这些都是辅助开发的,应该用在BoundedContext中。
SubdomainAccountDomain{type=CORE_DOMAINdomainVisionStatement="Accountdomaintosavesensitivedataandauthentication"EntityAccount{longidStringnickStringmobileString^emailStringnameStringsaltStringpasswdintstatusDatecreatedAtDateupdatedAt}ServiceAccountService{voidupdatePassword(longaccountId,StringoldPassword,StringnewPassword);voidresetPassword(longacountId);booleanauthByEmail(Stringemail,Stringpassword);booleanauthBySmsCode(Stringmobile,Stringcode);}}ContextMap
ContextMap主要是描述各个Domain中各个BoundedContext间的关联关系,你可以理解为BoundedContext的拓扑地图。这里我们先不详细介绍BoundedContext,你现在只需要理解为实现Domain的载体,如你编写的HSF服务应用、一个处理客户请求的Web应用或者手机App,也可以是你租用的一个外部SaaS系统等。举一个例子,你的系统中有一个blog的SubDomain,你可以自行开发,也可以架设一个WordPress,或者用Medium实现Blog。回到微服务的场景,如何划分微服务应用?SubDomain对应的是业务或者虚拟的领域,而BoundedContext则是具体支持SubDomain的微服务应用,当然一个SubDomain可能对应多个微服务应用。
既然是描述各个BoundedContext关系,必然会涉及到关联关系,如DDD推荐的Partnership([P]<->[P])、SharedKernel([SK]<->[SK])、Customer/Supplier([C]<-[S])、Conformist(D,CF]<-[U,OHS,PL])、OpenHostService、AnticorruptionLayer([D,ACL]<-[U,OHS,PL])、PublishedLanguage等,详细的介绍大家可以参考DDD图书。这些对应关系都有对应的缩写,就是括号内的表述方法。这里给出关联关系CheatSheet说明图:
图源:https://github.com/ddd-crew/context-mapping
如果你自行画图来表达这些关系,一定有非常多的工作量,细致到箭头类型,备注等,不然会引发误解。这里我们就直接上ContextMapperDSL对ContextMap的描述方式,代码如下:
ContextMapUserContextMap{type=SYSTEM_LANDSCAPEstate=TO_BEcontainsAccountContextcontainsUserTagContextcontainsPaymentProfileContextcontainsSnsProfileContextcontainsProfilesContextcontainsUserLoginContextcontainsUserRegistrationContextUserLoginContext[D]<-[U]AccountContext{implementationTechnology="RSocket"exposedAggregates=AccountFacadeAggregate}ProfilesContext[D]<-[U]UserTagContext{implementationTechnology="RSocket"exposedAggregates=UserTags}UserRegistrationContext[D,C]<-[U,S]UserTagContext{implementationTechnology="RSocket"exposedAggregates=UserTags}UserRegistrationContext[D,C]<-[U,S]SnsProfileContext{implementationTechnology="RSocket"}}大家可以看到Map图中包含的各个BoundedContext名称,然后描述了它们之间的关系。在关联关系描述中,涉及到对应的描述。前面我们说明BoundedContext为Domain的具体系统和应用的承载,所以涉及到对应的技术实现。如HTTPRESTAPI、RPC、Pub/Sub等,如blog系统为Medium的话,那么implementationTechnology=”RESTAPI"。还有exposedAggregates,表示暴露的聚合信息,如class对象和字段,服务接口等,方便通讯双方做对接,这个我们会在BoundedContext中进行介绍。
BoundedContext
在ContextMap中我们描述了它们之间的关联关系,接下来我们要进行BoundedContext的详细定义。BoundedContext包含的内容相信大多数同学都知道,如Entity,ValueObject,Aggregate,Service,Repository、DomainEvent等,这个大家应该都比较熟悉。这里我们给出一个ContextMapper对BoundedContext的代码,如下:
BoundedContextAccountContextimplementsAccountDomain{type=APPLICATIONdomainVisionStatement="Managingaccountbasicdata"implementationTechnology="Kotlin,SpringBoot,MySQL,Memcached"responsibilities="Account","Authentication"AggregateAccountFacadeAggregate{ValueObjectAccountDTO{longidStringnickStringnameintstatusDatecreatedAtdeftoJson();}/*AccountFacadeasApplicationService*/ServiceAccountFacade{@AccountDTOfindById(Integerid);}}AggregateAccounts{EntityAccount{longidStringnickStringmobileString^emailStringnameStringsaltStringpasswdintstatusDatecreatedAtDateupdatedAt}}}这里对BoundedContext再说明一下:
BoundedContext的名称,这个不用说啦,这个和ContextMap中名称一致。implementsAccountDomain:表示要实现哪一个SubDomain,我们都知道一个Subdomain可能会包含多个BoundedContext,这些BoundedContext配合起来完成Subdomain的业务需求。ContextMap还提供refines,来表示BoundedContext要实现一些usercase,官方文档有对应的说明。BoundedContext的属性字段:type表示类型,如APPLICATION、SYSTEM等。domainVisionStatement描述一下BoundedContext的职责。implementationTechnology表示具体的技术,前面我们说到BoundedContext已经涉及具体的应用和系统等,所以要说明对应的技术方案实现,核心的部分描述一下就可以。responsibilities表示BoundedContext的职责列表,这里只需要关键字就可以,如Account要负责安全验证等。AccountFacadeAggregate:表示提供给外部调用的聚合,这里DTO的对象定义、服务接口的定义等。AggregateAccounts:这个表示BoundedContext内部的聚合,如entity、valueobject、service等。这里说明一下,DDD中的那个Aggregate是entity,valueobject的聚合对象,而ContextMapper中的Aggregate表示为一些资源的集合,如Service集合等。BoundedContext的更多信息,可以参考sculptor的文档[4],根据实际的情况可以添加对应的部分,如DomainEvent、Repository等。
个人觉得这里BoundedContext还没有涉及到UbiquitousLanguage,还是需要对应的辅助设计文档,需要交代相关的项目背景,技术决策等等。个人是推荐采用C4架构设计作者编写的《Visualise,documentandexploreyoursoftwarearchitecture》[5],非常实用,作为DDD架构设计文档,完全没有问题。
文章的一开头我们说到之前的DDDDSL更多的是代码生成器,如果是代码生成器,那么生成的代码一定有对应的规范和结构等,如entity、valueobject,service,repository保存的目录,生成的代码可能还包括一定的Annotation或者interface,标准字段等等。当然这里我们不讨论代码生成器的问题,但我们希望大家的DDD架构设计还是要采用一定的规范目录结构,这里有几个标准推荐给大家:
ddd-4-java:BaseclassesforDDDwithJava[6]jDDD:LibrariestohelpdevelopersexpressDDDbuildingblocksinJavacode[7]ddd-base:DDDbasepackageforjava[8]这三者其实出发点都是一致的,就是在代码层面来描述DDD,核心是一些annotation、interface,baseclass,当然也包括推荐的package结构。
ContextMapper的其他特性
讲到这里,其实DDD整体上来说,我们已经阐述清楚:Domain划分、整体Domain的BoundedContext拓扑图和关联关系、BoundedContext具体定义和架构设计文档规范。但是ContextMapper还提供了UserStory和UseCase对应的DSL,让我们来看一下。
UserStory
好多同学都问UserStory如何写,有了这个DSL,同学们再也不用担心如何编写UserStory啦。这个DSL比较明确的,主要是三元素:作为“aaa",我希望能"xxx",我希望能”yyyy",以便"zzz",也是符合UserStory的典型三要素:角色、活动和商业价值。
UserStoryCustomers{Asa"LoginUser"Iwanttoupdatea"Avatar"Iwanttoupdatean"Address"sothat"Icanmanagethepersonaldata."}UseCase
UseCase是描述需求的一种方式,在UML图就有对应的UseCase图,核心就是actor,交互动作和商业价值,对应的DSL代码如下:
UseCaseUC1_Example{actor="InsuranceEmployee"interactions=createa"Customer",updatea"Customer","offer"a"Contract"benefit="Iamabletomanagethecustomersdataandoffertheminsurancecontracts."}在Aggregate聚合中,你可以设置useCases属性来描述对应的UseCase,如下:
AggregateContract{useCases=UC1_Example,UC2_Example}ContextMapper带来的收益
按照你的说法,我们用DSL代码方式来描述DDD,这个有什么收益?
架构设计标准化
这种代码方式,一目了然且非常规范。如果你代码写错会有什么问题,当然是编译不通过,IDE都会帮你纠正。所以DDDDSL也是这样,完全无歧义。目前ContextMapperDSL包括Eclipse和VSCode插件,在IntelliJIDEA可以通过自定义FileTypes和Livetemplate方式来辅助你编写cml文件。
生成器(Generators)支持
前面我们聊到DDDDSL支持代码生成器,可以辅助你生成代码,相信这个大家都能明白,因为DDDDSL代码是标准的,基于这个CodeModel生成其他形式的代码,这个当然可以。
另外ContextMapper还支持其他模型生成,如ContextMap图形化展现、PlantUML的结构图,对应的代码在这里[9]。我这里给大家一些截图:
当然ContextMapper还提供通用的生成器,也就是基于DDDDSL模型,加上Freemarker模板,然后就可以生成你想要的各种输出,如生成JHipsterDomainLanguage(JDL)用于快速创建文件脚手架也不奇怪。相信很多Java程序员对此都不陌生,我们开发Web应用时就是使用Freemarker生成HTML的。更多细节访问这里[10]。
现实中的DDD设计流程
我们有了DDDDSL来描述我们的架构设计,是不是就全面了,完全够用,开发不愁了呢。还不是,我们知道在软件架构设计和编写代码前,都有需求调研、客户走访、领域专家沟通、需求分析、研讨等等,这个在现实生活中还是少不掉的,其目的就是为了后续的架构设计提供素材并做铺垫。那么如何将DDD和这些前期操作整合起来?其实DDD有涉及这方面的内容,如EventStorming卡片:
BoundedContextCanvas卡片:
如果你在需求分析阶段注意这些DDD卡片的使用,那么后续的DDD设计就会有更好的素材,当然还有UserStory和UseCase等。
个人建议:如果你有时间的话,强烈建议关注一下ddd-crew[11],有非常全面的DDD相关的最新并实用的知识和实践。
DDD和MicroServices的关系
和DDDDSL无关,只是稍微提及一下。微服务架构设计在于如何将复杂的业务系统划分为密切合作的微服务应用,划分的依据就显得非常重要。SubDomain从业务的角度出发,进行业务边界的划分,而BoundedContext则是关注于业务领域对应的应用承载。而Generic类型BoundedContext可以同时支撑多个SubDomain,能够做到不同业务系统的应用复用。如果在CloudNative的场景中,我们希望更多的使用System类型的BoundedContext,也就是重复利用云上的系统,从而减少自己的开发和维护成本。回到Appplication类型的BoundedContext,这个就是你要具体开发的应用,你选择哪些微服务框架,这个你可以自行决定。整个过程,DDD都起到应用划分的理论基础作用。
但这里还有一个问题,就是微服务之间的通讯问题,你可以反复强调我们需要构建强大的分布式应用,但是推荐的技术栈是什么?如何去做?而且还要做的更好,这个并没有明确说明,所以大家选择RESTAPI、gRPC、RPC,Pub/Sub等等混合通讯技术栈。
关于BoundedContext之间的关联关系DDD已经给出了(partnership,c/s,sharekernel等),但是具体到通讯和协作,并没有给出很好的理论基础,但是这个在DDD社区也有一些共识,就是基于异步化的消息通讯+事件驱动是比较好的方案,所以你看到DDD的首席布道师VaughnVernon反复讲到DDD+Reactive,就是为了解决ContextMapping的通讯问题。
说到这里,如果你看到ContextMapper支持MDSL(Micro-)ServiceContractsGenerator的输出,那么也就不奇怪了,也是理所当然的事情。
更多的关于MicroServices和DDD关系,你可以参考《MicroservicesloveDomainDrivenDesign,whyandhow?》[12]
总结
ContextMapper提出的DSL概念还是非常好的,至少让大家在DDD的理解上歧义少啦,同时也规范啦,DDD初学者的门槛也降低,虽不能到架构设计的地步,至少阅读理解起来无障碍。在我编写这篇文章的时候,ContextMapperDSL5.15.0版本已经发布,相关的特性都已经全部开发完毕啦,使用起来还是非常顺畅的。当然落实到实际开发,DDDasCode这种方式是否有效,还希望做DDD实践的同学给出宝贵的意见。
当然我一篇文章并不能将ContextMapper阐述的非常清楚,contextmapper[13]上有非常详细的文档和对应的相关论文,当然你可以不采用DSL这一套思路,但是这些思想和相关的资料对DDD设计还是帮助非常大的。
另外个人更觉得,如果你是DDD的初学者,那么ContextMapper可能更合适,DDD是方法论,那些图书都枯燥的要死,看两章节不犯困几乎非常难的。相反如果你学习DDDDSL那就简单多,这个DSL再复杂也不会比你学习的编程语言复杂吧?相反这个DSL是非常简单的,通过简单的DDDDSL学习,你会很快掌握其中的概念、思路和方法,不行就看一下其他人的代码(DDDDSLexamples),也会帮助你很快学习,掌握这些方法论,回头你再使用图书和文章进行巩固一下,也是非常好的。
作者:茶什!
本文为阿里云原创内容,未经允许不得转载
本文主要介绍了关于代码是什么意思(DDD)的相关养殖或种植技术,综合百科栏目还介绍了该行业生产经营方式及经营管理,关注综合百科发展动向,注重系统性、科学性、实用性和先进性,内容全面新颖、重点突出、通俗易懂,全面给您讲解综合百科技术怎么管理的要点,是您综合百科致富的点金石。
以上文章来自互联网,不代表本人立场,如需删除,请注明该网址:http://23.234.50.4:8411/article/83715.html