DDD随谈

  前言  

  最近再次拜读了Eric的奠基之作【Domain-Driven Design –Tackling Complexity in the Heart of Software】,还有Vernon的【Inplementing Domain-Driven Design】,虽然是java版本,但并不影响阅读,毕竟设计思想是通用的,结合个人使用DDD的一些经验,做个随谈。

  在我看来,DDD绝非什么标新立异的的思想,更多的是软件发展的自然结果。就像20世纪六七十年代出现的软件危机后,面向对象编程被搬上了舞台;瀑布式开发在快速发展的互联网时代,敏捷成为了它的救赎;而DDD更多的是对传统的以数据为中心的开发习惯的一种反思。

  如果你关心软件工艺,而不仅仅是coding,那么领域驱动设计便是非常重要的一项技能。

  防软件退化利器    

  随着软件业的快速发展,软件规模越来越大,生命周期也越来越长,推倒重新开发的风险越来越大。这时,软件团队急需在较低成本的状态下持续维护一个系统很多年。

  然而,事与愿违,随着时间的推移,程序越来越乱,维护成本越来越高,软件退化成了无数软件团队的噩梦。

  我们的软件总是经历着这样的轮回,软件设计质量最高的时候是第一次设计的那个版本,当第一个版本设计上线以后就开始各种需求叠加和变更,这常常又会打乱原有的设计。

  特别是当我们许多团队都在实践敏捷开发,但敏捷开发的落地对开发团队的设计能力、设计质量,提出了非常高的要求。因为每个敏捷周期,都是在对上一个版本的更新。如果设计质量跟不上,更新速度越快,代码退化就越快。代码质量下降了,还能敏捷得起来吗?所以我们如何在快速进行迭代交付的同时,又能保持着高质量的软件设计呢?    

  现状  

  长久以来,我们程序员都是很好的技术型思考者,我们总是擅长从技术的角度来解决项目问题。但是,一个软件系统是否真正可用,是通过它所提供的业务价值体现出来的。

  遗憾的是,我们更多的开发人员,对DDD的实践都是停留在"地面"上。

  过度拘泥于实现细节,而不是从一开始就居高临下俯视我们的软件,会错失让我们在天空概览的机会。

  我们开发人员总是在技术层面追求着高内聚,低耦合的完美设计,对不起,如果你不懂DDD在战略层面在业务系统存在的意义,凭着战术层面的指导,是实现不了这样的完美设计的。  

   意义

  有没有什么方式可以让我们保持软件的质量呢?有,那就是领域驱动设计!

  首先DDD并不是关于技术的,而是关于讨论,聆听,理解和发现业务价值。因此,与其每天钻在那些永远也学不完的技术中,何不将我们的关注点向软件系统所提供的业务价值方向思考,这也正是DDD所试图解决的问题。

  DDD的核心理念中,是划分为战略设计(天空)以及战术设计(地面)两部分的。  

  战略设计主要从高层“俯视”我们的软件系统,帮助我们精准地划分领域,明确各个领域的边界以及处理各个领域之间的关系;而战术设计则从技术实现的层面教会我们如何具体地实施DDD。

DDD随谈

  可以看出,战略设计在整个DDD落地过程中,是占核心地位的,它给我们提供了高屋建瓴的宽阔视野。  

  DDD的战略设计帮助我们清晰的划分不同的业务系统和各自的业务关注点,这样可以有效的保护各自系统并实现高内聚低耦合的设计原则。   

   困境

  DDD理念面世这么多年了,为什么业界内还是很少实际的案例呢?个人认为有几点原因:

  1. DDD提倡的是基于现实世界的现实行为进行建模,这就限制了案例的产生,毕竟这是业界的核心业务,不太可能作为案例披露出来的。

  2. 没有好的领域专家,DDD一直强调领域专家的重要性,在我们这个行业,业务和技术都深入的,太少了,这个行业充斥着急功近利,没有多少人真正是对某个行业进行钻研透彻的。

  3. 概念繁多,对人的要求极高,特别是抽象能力,容易让直性思维的人绕进去,学习使用成本会比较高。

  4. 忽视战略层面的意义,导致很多案例胎死腹中。

   即使是会面临不少的困境,但DDD仍然是诸多公司追捧的宠儿,除了它能使我们开发者提高抽象能力之外,DDD它本身的作用是简单化,而不是复杂化。

  在使用DDD时,我们应该采用最简单的方式进行复杂领域建模,而不是使问题变得更加复杂。    

  突破的关键点

  当我们在实施过程中面临着各种各样的问题时,有哪些策略是可以指导我们进行专项突破的点吗?  

  有的,从战略设计先入手。

  上面提到了,我们使用DDD,更多的是从战术设计这个”地面“着手,所以会出现了DDD-Lite的情况。而这是本末倒置的,DDD首先让我们关注的不是技术,而是业务语言。  

  领域划分/限界上下文

   既然是领域驱动设计,那么我们的设计重点肯定是在领域了,以及领域模型的正确设计了。

  首先领域并不是很高深的词汇,它是问题域集合的代名词。我们确定我们产品所属的领域后,所面临的问题是确定的,比如说我们是一个电商系统,它是属于电商领域,那么会遇到用户,订单,购物车,商品,交易,物流等明确的问题集合需要我们解决,这些问题域是确切的。

  在领域驱动设计中,强调对于子域的正确划分,即使是在日常开发中,我们通常会也将一个大型的软件系统拆分成若干个子系统。这种划分有可能是基于架构方面的考虑,也有可能是基于业务的。

  在DDD中,我们对系统的划分是基于领域的,也明确是基于业务的。即我们可以基于领域专家的领域业务知识,将整个系统划分成许多相对独立的业务场景(子域),然后在一个一个的子域中进行领域模型分析与建模。

  然后我们很快就发现了问题,哪些概念应该建模在哪些子域里面?我们可能会发现一个领域模型建模在子系统A中是可以的,而建模在子系统B中似乎也合乎情理。

  如何能正确定于模型含义呢?限界上下文!

  限界上下文是一个显式的边界,领域模型便存在这个边界内,创建边界的原因在于,每一个模型概念,包括它的属性和操作,在这个边界内都是具有特殊含义的。  

  DDD随谈

   从这里可以看出,如果光凭名字,我们是无法区分两个账户的意思的,只有通过它们所在的限界上下文,我们才能看出它们之间的区别。

  限界上下文是用来为领域提供语境的,它保证在领域之内的通用语言、领域模型有一个确切的含义,没有二义性。

  行为丰富的模型

  我一直认为,DDD中的领域模型才是一个真正意义上的OOP,它所推崇的充血模型,是映射着我们真实世界的真实行为。我们平时口中所谓的OOP,实体只是单纯的数据载体,没有更多的功能,DDD推荐的领域对象,是跟我们现实生活中的概念是一致的,有具体的行为,是一个行为饱满的对象,这样才是DDD的威力所在,也是我们实现高内聚低耦合的途径。

  领域模型即业务。

  从DDD的名称我们就可以看出,领域驱动设计中,领域模型是最核心的点所在,所以在设计得到模型后,DDD要求我们在代码中无偏差地实现模型,也就是所谓模型驱动开发(Model-Driven-Design, MDD)。

  行为丰富的领域模型,才是DDD最大的威力,能设计出行为丰富并且涵盖诸多现实业务的模型,就是消化领域知识的最好体现。

  由于这种模型是我们现实世界的真实描述,鉴于我们真实世界的知识跨度的速度(参考地心说的统治时间),是可以维持软件到一定的生命周期的。这种模型的行为丰满,符合真实世界的认知,且业务纯净,减少了犯错的可能性。

  要创建行为饱满的领域对象并不难,首先出发点是认真思考我们真实世界的可靠行为,把这些行为提炼到模型中,再次我们需要转变一下思维,将领域对象当做是服务的提供方,而不是数据容器,结合真实世界多思考一个领域对象能够提供哪些行为,而不是数据。

  DDD和微服务

  这几年当DDD再次映入我的眼帘,是微服务的兴起,DDD已经面世好多年了,随着微服务的兴起,DDD重新活跃在我们的眼中,或者说,微服务的兴起,也依赖了DDD的铺垫。它们是相辅相成的。

  微服务的特点

  微服务的一个核心点就在于服务的划分,整个系统被被分成了很多个轻量的模块,它的一个原则就是划分出来的模块在于“专”(小并不是微服务的重要的考虑因素,具体参看【微服务架构 - 正确的开始】),即每个服务的松散耦合上,也就是我们常说的高内聚,低耦合。

  无独有偶,DDD也是基于高内聚,低耦合的思想来指导我们如何划分正确的子域。

  限界上下文/上下文映射图

  我们上面讲了,在限界上下文中,其中的领域模型都是高内聚的存在,它们的关联性是非常强的,它们只会在同一个原因的条件下进行软件变化,所以通常情况下,一个限界上下文下的子域是可以设计为一个微服务应用程序,而这个微服务的边界,就是这个限界上下文,服务间的关系,就是上下文映射图。

  在微服务中借助DDD的思想划分服务是大概这么一个过程:

  • 按照限界上下文进行微服务的拆分,按照上下文映射图定义各微服务之间的接口与调用关系;
  • 通过限界上下文的划分,在各自的子域内进行建模,并基于充血模型或者贫血模型落地各个微服务的领域模型;
  • 按照领域模型设计各个微服务的数据库;
  • 将以上的设计最终落实到微服务之间的调用、领域事件的通知。

  数据一致性

  在微服务中,会面临着我们分布式系统的常见问题,其一就是事务的一致性。在DDD中,领域事件便可以用于处理这些问题,此时最终一致性取代了事务一致性,通过领域事件的方式达到各个组件之间的数据一致性。

  后续

  洋洋洒洒的聊了些个人对DDD的一些看法,其中的部分概念会后续在这个系列的博文章节里继续探讨。

  DDD也不是"银弹"

  正如微服务架构中的“微服务不是银弹”,领域驱动设计也会面临同样的问题。作为架构师,我始终认为我们在任何的情况下对于任何的特定技术,都可以活学活用,所以个人使用DDD的一个理念的是,不要为了DDD而去DDD。

  领域驱动设计作为面向对象编程的高级方法论,它其中的很多设计是非常美妙以及契合实际的,然而所谓设计,是要以我们的团队的知识、经验和智慧,全面充分的考虑各种内外因素后,在设计方案中作出合理的选择的过程。

  我们的目标是什么?是追求完美的DDD吗?不是,我们的目标是把系统做得更健壮,赋予产品强大且持久的生命力,所以我们在真正的使用过程中,其实是借助了DDD很多的设计思想来指导我们的系统设计。

  没人在乎你是否是一个纯正的DDD,老板以及用户注重的,是你所使用的技术带来的业务价值。

  所以在使用领域驱动设计时,并不代表整个系统的方方面面都必须遵从领域驱动设计的原则,需要根据实际情况,让适合的部分使用领域驱动设计,让不适合的部分使用面向过程的设计。   

  DDD落地是一个深入了解业务的过程

  DDD 的真谛是领域建模,即深入理解业务。我们不可能一步到位深刻理解业务,它是一个逐步深入的过程。只有深入理解业务,将对业务的深入理解设计到领域模型中,设计出来的软件才更加专业。因此,基于每个限界上下文进行领域建模,不断地将每个功能加入模型中,落地每个微服务的设计。

  当业务越来越复杂,理解越来越深入的时候,我们要适时地调整原有的模型,就能适应新的功能。正因为 DDD 就是要应对的是软件的这样的不确定性的复杂,才会通过结合现实世界的理解,领域建模去抽象复杂业务,让复杂业务得到简化,从而简化软件的设计,使设计始终处于高质量的水准上。

  因此,我们学习 DDD,首先就要把设计做到位,准确理解那些领域,限界上下文,聚合、仓库、领域事件等基础概念,并在设计实战中做出正确的设计。


   DDD中有很多概念是相对来说是比较抽象的,特别是对习惯逻辑思维的程序员来说,抠概念是比较痛苦的,所以很多技术人员在初学DDD时,更多的是关注战术层面的设计,然而过度地强调DDD的技术性将使我们错过由战略设计带来的好处,毕竟战略层面可以升个级为整个软件的业务架构,这个架构是支撑软件生命周期重要的依据。    

  记住,领域模型的设计并不会一蹴而就,我们需要反复研究领域知识,不断重构模型,才能将领域中的重要概念提炼成简单而清晰的模型。

DDD随谈

上一篇:【Linux面试题2】目录结构及相关命令


下一篇:Arrays.asList();