在单体应用的一些DDD实践经验

阅读此文须要必定的DDD基础,若是你是第一次接触DDD读者,建议先去阅读一些DDD相关的书籍或者文章以后再来阅读本文。html

 

背景


 

自从我在团队中推行DDD以来,咱们团队经历了一系列的磨难——先是把核心项目重构,接着又在一些衍生项目中尝试全面落地DDD, 最终探索了一些经验出来,特此记录一下。java

本文采用语言无关的角度陈述,不管你是Java或者c#的开发同窗相信均可以无障碍阅读。git

请注意本文并非介绍如何实现DDD,由于这个话题实在太大了。github

此次的主题是分享一些咱们团队在实践DDD过程当中碰到问题和如何克服它们,以及介绍一下咱们所使用的架构体系。docker

 

先说说为何标题限定在“单体应用”这个范围内,数据库

  1. 咱们团队此次实践的应用全是单体应用
  2. 若是是分布式的应用,那么拆分限界上下文(BoundedContext)的最佳实践是什么?固然是微服务!c#

    我相信如今讨论微服务的文章确定不在少数,微软也专门出过容器化微服务架构的电子书。传送门点我后端

    资源如此丰富,固然就不须要我多此一举了。设计模式

 

领域模型


 

领域模型的分析能够说是DDD当中最为核心的部分,由于你整个系统的业务逻辑代码都是基于领域模型而构成的。网络

而要将业务逻辑转换成领域模型除了对业务的熟悉外还须要极高的抽象能力,因此通常须要业务专家和建模专家共同完成。

怎样提炼一个好的领域模型是一个很是大的话题,推荐你阅读如下书籍:

  • 《领域驱动设计:软件核心复杂性应对之道Eric Evans
  • 《实现领域驱动设计》Vaughn Vernon
  • 领域驱动设计与模式实战》Jimmy Nilsson

另外微软架构电子书上还有推荐其余几本DDD的书籍,遗憾的是,JD和TB都没搜到。

 

在团队刚开始分析领域模型时,对全部相关者都是一个极大的挑战,我这里分享几点经验帮助团队更好地度过这段时期:

  1. 不要想着可以一次提炼出完美的领域模型(除非团队中有着经验丰富的DDD实践者),一般来讲,咱们会在会议上决定一个粗略的模型,而后在开发过程当中你会发现有一些不天然的地方,好比某些上下文频繁地与其余上文通讯,或者某个实体的行为不是很恰当,这个时候再去修正领域模型,这样演进式的过程能够大大下降大家在初期的压力。
  2. 若是你的团队总体能力不足以支撑领域模型的推行,或者他们在初期的配合度不高时,你能够选择把你的项目中业务逻辑最为复杂的部分使用弱化的领域模型拆解,好比仅使用充血模型和领域服务,这样至少你能够对最为复杂的部分引入一些DDD战术模式或设计模式。
  3. 就算你的团队能力够了,但大部分人都没有DDD的经验的话,我也建议先只引入部分模式(好比只引入实体,值对象和仓储这类比较容易理解的模式)来提升团队的敏感度以后再采用完整的领域模型。
  4. 领域模型会对查询带来必定的复杂性,这种时候你能够采用CQRS来分离Query和Command,只有在Cammand的时候你才须要发挥领域模型的威力,至于Query,SQL语句显然是更好选择。

 

基础架构


了解DDD的同窗都应该知道,DDD当中最为重要的部分就是限界上下文(BoundedContext),在领域模型中咱们区分好了上下文以后,下一步就是选择一种技术手段来确保每一个上下都是低耦合高内聚且自治的。

在分布式应用中,多数设计者和包括微软架构的电子书都会推荐使用一个上下文对应一个微服务的方式来实现(确实微服务和上下文的设计需求不谋而合)。

但单体应用该怎么办呢?

有同窗说,咱们能够经过命名空间来隔离它们啊。

不错,咱们能够这样作,可是有如下几个缺点

  1. 在使用IDE的智能引用时,你得确认你引用的实体到底是位于当前上下文以内仍是以外。
  2. 会致使你的项目结构层次过深,不便于查看。(至于过深的标准是多少,看我的了,对于我来讲,5层是能够接受的上限,理想是控制在4层之内)
  3. 不便于向微服务架构迁移

因此咱们选择了使用程序集(java是使用jar包)的方式来隔离每一个上下文,这样作克服了以上的缺点,但却带来了新的问题:动态加载这些上下文。

不过这种程度的问题比起带来的收益几乎能够忽视。

咱们团队使用一个基础平台来动态加载这些上下文,

咱们采用了 Abp 框架提供的插件功能来实现,若是你也是.net 的使用者,也能够采用 Abp 来构建这个应用。

固然本身写一个动态加载功能也并不困难。

基础架构以下图所示:

 

但是咱们的平台要承担不少功能,好比开放RESTful的API与Webservice(为了兼容老的接口), 同时还要提供受权(使用了基于Oauth2.0协议的三种模式)、数据库初始化、处理请求上下文等等,我就不一一列出来了。

咱们但愿BC(BoundedContext,后文都会简写为BC)里不须要关注网络层面的东西而只聚焦于应用,因此不少通用的事情都由平台来承担, 并且有时还会有一些交互,好比在验证权限时你得跟用户权限上下文通讯。

在这种前提下,咱们抽出了一个用于链接平台和这些BC的交互层,咱们把它称做——桥接组件(BrigeComponent),它负责联系起平台和这些BC,外加上一些共用的基础设施,咱们的架构图变成了这样:

 

 

这样一来,你能够把每一个BC都看成微服务来处理,每个BC内的分层结构你能够按你的喜欢的来,若是你喜欢标准的三层架构(UI + BLL + DAL),你能够将BC设计那样。

你甚至能够每一个BC都采用不一样的风格,好比一个采用N层架构,而另外一个采用事件驱动架构(EDA)。

这里咱们的BC都用了相同的DDD推荐分层架构(这里省去了 表现层, 由于现代应用大多都是先后端分离了的),以下图所示:

 

 

 

好了,如今总体架构和领域模型都已经肯定下来后,咱们开始编码了,但很快咱们就遇到了阻碍。

 

“结算上下文须要访问用户权限上下文,它须要知道这个用户的机构信息,我能够直接引用吗?”

“账户上下文这里输出的数据须要通用上下文提供一些有效性校验,我能够直接引用吗?”

“我这里也须要访问通用上下文!”

……

 

好吧,若是咱们直接提供引用,会有如下问题:

  1. 因为咱们采用了程序集分割上下文,因此相互引用是不被容许的。
  2. 就算克服了相互引用的问题,最终也会致使引用拓扑图混乱不堪。
  3. 强耦合,这会直接影响到之后的拓展性。

在微服务中,为了克服服务间的互相通讯问题,目前我了解的有两类解决方案,

一是相似于ESB(企业服务总线)的中心化通讯模式,好比大名鼎鼎的SprinCloud。

二是如今微服务界炒得沸沸腾腾的ServiceMesh(服务网格),好比 Linkerd 和 Istio。

 

咱们项目选择了前者,使用了相似于ESB中心化通讯方式来解决,简单来讲,你须要一个通讯中介者(Mediator)来负责BC之间的交互,结构图以下:

 

若是你是 .Net 的开发者,请允许我给你安利一下咱们在项目中使用的,本身开发的组件——ServiceAnt,它目前只支持进程内的通讯,但不久后会开发分布式的。

详细状况你能够点击上面的链接进去查看,也能够查看我写的  另外一篇博客  了解ServiceAnt是作什么的,固然你也能够选择 Mediator 来实现这个通讯中间件。

Java的话,因为经验较少,没有发现相似的项目,Mule ESB什么的就跟 NServiceBus 同样是重量级的组件,不适用咱们这样的场景。

 

以上就是咱们用于实现DDD的基础架构,基于这样的架构咱们能够很轻松地将现有应用向微服务拆分。

固然,上面的架构隐藏了不少细节,好比大量的基础设施(Ioc,Aop, Logger, cache等等),

缘由之一是由于这些东西的设计都很常见,网上你随便就能够搜到相关设计的文章,

缘由之二是由于我不想这些细节影响到了读者的关注点,我但愿咱们能够聚焦于如何实现DDD而不是系统的其余部分。

 

其余的一些话


 

在推行DDD过程当中,总会有一些成员会问我,DDD给咱们带来的好处是什么。

我总会不厌其烦地告诉他们,为了下降系统的维护成本和更合理地去解决系统业务的复杂性。

但后来我渐渐发现,实现DDD自己就不是一件容易的事情,它会对项目引入新的复杂性,有时候你会发现你团队花上大量时间去建模以后,在开发过程当中却依然须要不断修正模型。

这很容易让整个团队士气变低,而且让开发人员有挫败感,这种时候我常常会怀疑DDD对咱们而言是否真的有价值。

不过坚持下去,在你使用DDD完成一到两个项目以后,你会发现建模是一件很是有意思的事情——提炼业务并将其转换为一个无关技术的模型,这就跟搭积木同样。

 

最后给全部但愿经过DDD来改善项目,而且提高本身的同窗说如下两点:

1,不要奢望光经过阅读就能充分地理解DDD,你须要真正去实践(固然,框架和架构设计也是同样的,不要作象牙塔里的架构师)

2,实践的过程你总会遇见疑惑和挫折,好比彻底不知道如何拆分上下文,也不知道该如何使用那些战术模式,这个时候再把那几本书拿出来翻翻,你就会发出“啊,原来这种场景还能够这样处理”的感概。

 

那句话怎么说来着,

The one trying to wear the crown must withstand the weight.

 

出处:https://www.cnblogs.com/RobotZero/p/8304980.html