前言  

  最近再次拜读了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理念面世这么多年了,为什么业界内还是很少实际的案例呢?个人认为有几点原因:

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

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

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

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

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

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

  突破的关键点

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

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

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

  领域划分/限界上下文

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

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

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

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

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

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

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

  

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

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

  行为丰富的模型

  我一直认为,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随谈的更多相关文章

  1. 从壹开始微服务 [ DDD ] 之六 ║聚合 与 聚合根 (下)

    前言 哈喽大家周二好,上次咱们说到了实体与值对象的简单知识,相信大家也是稍微有些了解,其实实体咱们平时用的很多了,基本可以和数据库表进行联系,只不过值对象可能不是很熟悉,值对象简单来说就是在DDD领域 ...

  2. 浅谈我对DDD领域驱动设计的理解

    从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...

  3. (转载)浅谈我对DDD领域驱动设计的理解

    原文地址:http://www.cnblogs.com/netfocus/p/5548025.html 从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来 ...

  4. 浅谈DDD

    从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...

  5. DDD:再谈:实体能否处于非法状态?

    背景 实体能否处于非法状态吗?如果实体只承担其作为实体的职责,我不认为实体可以处于非法状态,如果您将实体在不同的分层之间传递,如:UI->Application->Domain-Data, ...

  6. 初学者浅谈我对领域驱动设计(DDD)的理解

    一.为什么要学习领域驱动设计 如果你已经设计出了优雅而万能的软件架构,如果你只是想做一名高效的编码程序员,如果你负责的软件并不复杂,那你确实不需要学习领域驱动设计. 如果用领域驱动设计带来的收获: 能 ...

  7. 谈架构设计中DDD思想的运用

    首先,描述一下我的业务场景及项目分层结构,非标准DDD(其实我不觉得有标准),只是思考的时候有带入DDD思想. 业务场景:这是一个ERP系统对中台提供的接口项目,仓储操作大多都是存储过程去完成的. 项 ...

  8. 面试官:谈一下你对DDD的理解?我:马什么梅?

    领域模型(domain model)是对领域内的概念类或现实世界中对象的可视化表示.领域模型也称为概念模型.领域对象模型和分析对象模型. ——<UML和模式应用> 我们在日常开发中,经常针 ...

  9. 谈一谈 DDD

    一.前言 最近 10 年的互联网发展,从电子商务到移动互联,再到"互联网+"与传统行业的互联网转型,是一个非常痛苦的转型过程.在这个过程中,一方面会给我们带来诸多的挑战,另一方面又 ...

随机推荐

  1. 13、解决java -version命令报错

    13.1.问题描述: 安装jdk后在dos界面中输入"java -version"回车的时候报如下错误: Error opening registry key'software\J ...

  2. 个人使用uploadify插件遇到的一些问题

    当uploadify上传插件遇到的好几个问题 现在开始自我反省,留下脚印希望能够帮助其他遇到同样问题的朋友. 我遇到的第一个是, 在firefox不能执行uploadify事件onUploadSucc ...

  3. POJ 2299 Ultra-QuickSort 求逆序数 线段树或树状数组 离散化

    我用的线段树写的. num数组表示已插入的数值的个数. 由于a[i]数值很大,但是n不是很大,所以要离散化处理 9 1 0 5 4 离散化后 4 1 0 3 2 这样保证最大值不会超过n #inclu ...

  4. 查看JVM中的线程名

    实例说明 在Java虚拟机中(JVM):除了用户创建的线程,还有服务于用户线程的其他线程.它们根据不同的用途被分到不同的组中进行管理.本实例将演示JVM中线程的名字及其所在组的名称. 关键技术 线程组 ...

  5. ansible 配置详解

    ansible 安装方式 ansible安装常用两种方式,yum安装和pip程序安装.下面我们来详细介绍一下这两种安装方式. 使用 pip(python的包管理模块)安装 首先,我们需要安装一个pyt ...

  6. CentOS-安装node_exporter导出机器指标

    注:node_exporter导出机器指标配合 Grafana+Prometheus使用,可参考:远程监控服务器指标 创建相关目录 $ mkdir /home/prometheus/ -p $ cd ...

  7. @Valid 注解的使用

    限制 说明 @Null 限制只能为null @NotNull 限制必须不为null @AssertFalse 限制必须为false @AssertTrue 限制必须为true @DecimalMax( ...

  8. linux 生成密钥

    p.p1 { margin: 0; font: 16px "Helvetica Neue" } span.s1 { font: 16px ".PingFang SC&qu ...

  9. Java实验项目三——递归实现字符串查找和替换操作

    Program:按照下面要求实现字符串的操作: (1)设计一个提供下面字符串操作的类 1)编写一个方法,查找在一个字符串中指定字符串出现的次数. 2)编写一个方法,参数(母字符串,目标字符串,替换字符 ...

  10. Oracle如何以逗号分隔的字符串拆分为多行数据

    近期在工作中遇到某表某字段是可扩展数据内容,信息以逗号分隔生成的,现需求要根据此字段数据在其它表查询相关的内容展现出来,第一想法是切割数据,以逗号作为切割符,以下为总结的实现方法,以供大家参考.指教. ...