应用程序框架实战十三:DDD分层架构之我见

  前面介绍了应用程序框架的一个重要组成部分——公共操作类,并提供了一个数据类型转换公共操作类作为示例进行演示。下面准备介绍应用程序框架的另一个重要组成部分,即体系架构支持。你不一定要使用DDD这样的架构,使用单层架构和普通三层架构一样可以,不过你如果希望获得更进一步的复用性和封装度,使用更加面向对象的技术是必经之程。

  我在2010年以前还在使用古老的ASP.NET WebForm和原始的Ado.Net。之前我有个观念:.NET技术发展太快,跟着微软屁股后面跑太累,所以只使用它一些原始的东西,自己封装一下也能满足工作上的需求。对于像Linq这样的技术只是随便看了下,特别是当时很多人告诉我Linq已死,千万别学,我当时很喜欢这样的言论,因为不学习新知识就有了充分的理由。

  到了2010年,我有一次上博客园,浏览了一些文章,发现充满各种缩写和名词,什么Dto、工作单元一类,我才知道新一代的.Net技术已经开始普及,我已经Out了。之后我开始学习EF和MVC,在刚开始接触EF的时候,我从一些博客了解到,为了发挥EF的威力,必须使用DDD进行设计。为了扫清拦路虎,我购买了几本DDD的书来学习,学习过程中才发现面向对象和敏捷开发才是关键,于是开始大量购书,一发不可收拾,四年时间买了接近两百本后,终于把基础补起来一点。

  DDD的核心思想是描述如何使用面向对象的方法对业务领域建模,怎样获得更好的领域模型。虽然看了不少DDD的资料,但还是感觉它异常抽象,另外面向对象的思想也很难进步,可能和前些年的编程习惯有关,已经习惯于从数据的角度考虑问题,形成了思维定势。

  DDD虽然抽象,但它还是提供了一些技术上的支持。大部分人都是从DDD分层架构入手来进行学习和实践,当然,DDD并不是分层架构,分层架构只是DDD的一个技术构造。下面简单介绍一下我对DDD分层架构的理解,由于我使用DDD的时间不长,我所描述的观点都是我自己的一些开发经验,不一定正确,欢迎各位高手批评指正,共同进步。

  DDD分层架构总体上和三层架构相似,不过对各层提出了更具体的职责和构造块。我也经常与一些在使用DDD分层架构的朋友交流,我问他们DDD分层架构与普通三层架构有何区别,大部分人都感觉差不多,除了一些名词术语有所变化。如果你也是这个感觉,那么可能本文对你是有帮助的,因为我明显感觉出它们之间有所不同。

  DDD分层架构与传统三层架构示意图如下。

(领域驱动设计分层架构示意图)

(传统三层架构示意图)

  DDD分层架构与传统三层架构最重要的区别可能是重心不同,即传统三层架构的重心在业务逻辑层,而DDD分层架构的重心在领域层。

  面向对象设计的核心是基于业务概念建模,并映射到代码中,这样的好处是减轻程序员将业务概念转换到技术的负担,因为更容易理解。传统三层架构虽然也把业务概念转换到实体层的Model对象,但实体层只是一个辅助设施,这些Model只是用来装数据的容器,作用并不显著。DDD分层架构把领域层提到核心地位,这些Model成为业务逻辑的一个主要放置场所。

  使用DDD分层架构的第一个好处就是业务逻辑高度内聚到领域层,换句话说,如果有逻辑问题,找领域层就对了。对于这一点,有些人认为传统三层架构也可以,找业务逻辑层不是一样吗?这可能是大多数对领域驱动设计分层架构认识无法突破的关键。

  虽然你可以按照分层架构的要求,把全部业务逻辑都写到BLL层,但你无法精确定位你需要的业务逻辑究竟处于什么位置,换句话说,你需要业务逻辑的一个唯一访问点。由于你无法轻易找到业务逻辑的访问点,所以产生冗余代码就再所难免,一段相同或相似的冗余代码会在多个地方产生,从而导致可维护性的降低。通过强制约束代码和目录规范以及提取公共方法可以缓解部分问题,但要从根本上解决,你还得向面向对象求救。

  那么,哪里是业务逻辑最好的落脚点,最直观,最容易被大家想到的唯一访问点在哪呢?比如你要处理一个订单,让你到其它地方去找处理订单的代码,你自然找起来困难。那么如果这段代码处于订单实体的内部,情况就大不相同了,你可以在最短的时间内找到它。在领域实体中内聚业务逻辑,可以为你创建一个业务逻辑的唯一访问点。大家以后需要某个逻辑的时候,先看看实体中有没有自己需要的,这样就能显著降低代码冗余,从而更好维护。

  所以,我的第一条DDD使用经验就是,使用充血模型,将业务逻辑尽量放到领域实体中。充血模型有很多争论,不过你大可不必理会别人的说法,自己实践才能出真知。用得不爽,你后面不用就是了,对你基本没啥影响。目前我使用充血模型,感觉它主要的问题是,如果采用分布式架构,比如中间采用WCF远程调用,需要通过一层专门的DTO来进行传输,而且需要增加一个远程外观的服务,会导致工作量上升。

  当把充血模型用起来以后,下一步是要把聚合用起来。聚合这个概念很好理解,就是包含关系。在UML中有两种包含关系,第一种叫聚合,表示比较弱的包含关系,聚合内部的东西在外面可以直接访问。第二种叫组合,即组成聚合,是很强的包含关系,表示外部的对象由内部的多个子对象组成,内部的子对象在外面不能直接访问,必须通过外部的对象间接引用。DDD虽然用了聚合这个词,但它表示UML中的组成聚合,所以它把外部的对象称为根,即聚合根,要访问内部对象,必须先访问聚合根。

  概念上的理解,除了能吹吹牛以外,没多大帮助。我在刚接触DDD的时候,也能理解聚合的概念,说起来一样口沫横飞,但真正用起来过了差不多一年。除了我反应比较迟钝以外,还有一个原因是被之前以数据为中心的思维定势所束缚。

  我也经常下载一些DDD的Demo来学习,但是这些例子大多都非常简单,所以我主要还是依靠看书和自己摸索。我刚开始的用法是一个表对应一个领域实体,每个领域实体对应一个仓储。我在使用的过程中,隐隐发现哪里不对,但是无法找出具体的原因。经过大半年,我也使用DDD开发了几个简单的项目,逐步积累了一些经验,在一次看书的时候,我突然领悟到我的DDD用法主要毛病是依赖关系混乱,而解决这些依赖关系的手段就是聚合。

  聚合的主要影响是显著减少仓储数量,以及集中管理高度依赖的相关实体。把高度相关的实体内聚到一个聚合中,可以把这些依赖关系封装到一个更小的空间,外部只与聚合根打交道,与聚合内部子对象的依赖关系就会明显降低。一个聚合对应一个仓储,而不是一个实体对象一个仓储,可以减少仓储数量,从而进一步降低依赖关系。

  后面我重新阅读了一些博客和书籍,发现别人其实都说清楚了,只是自己当时看过去没有理解而已,这真是纸上得来终觉浅 绝知此事要躬行。

  我的第二条DDD使用经验是,把高度相关的实体封装到聚合中,为每个聚合根创建一个仓储。

  观察上面的DDD分层架构示意图,会发现领域层只依赖于应用程序框架服务,仓储采用了接口分离模式将实现和接口分离到不同的程序集,领域层中只包含仓储的接口,这个设计让领域层非常纯净,和外部的依赖关系降到最低。这对我们意味着什么?更低的依赖让我们可以方便的对业务逻辑进行单元测试,特别是采用了TDD方式的话,这一点将显得尤其重要。我们可以在单元测试中使用模拟框架对仓储以及外部依赖进行模拟测试,从而大幅度提升业务逻辑的稳定性和健壮性。

  另外,观察传统三层架构,业务逻辑层一般直接依赖数据访问层,让单元测试变得困难,从而转向更粗粒度的集成测试。

  通过上面的分析,可以看到采用DDD分层架构可以获得比传统三层架构更好的可复用性、可维护性、可测试性等。

  当然不可能把所有业务逻辑全部放入领域实体中,有些功能需要操作多个实体,或者需要使用某些设计模式,这时候需要使用领域服务。这里的要点是尽量把领域服务的操作委托给领域实体,因为这样业务逻辑可以更加集中。

  DDD分层架构还有一些构造块,我会在后面的文章详细介绍。如果没有介绍到的,说明我还处于学习和摸索阶段,还没有多少心得,等我有些经验以后再告诉大家。

  现在来总结一下。

  使用DDD分层架构有哪些好处

  • 帮你更集中的管理业务逻辑。
  • 帮你降低各层间,以及各业务模块间的依赖关系。
  • 帮你更方便的进行单元测试。

  我的DDD分层架构使用经验

  • 使用充血模型,将业务逻辑尽量放到领域实体中,领域实体为业务逻辑提供一个唯一访问点。
  • 不能放入领域实体的逻辑,尽量放到领域服务,总之,业务逻辑应该高度内聚到领域层。
  • 把高度相关的实体封装到聚合中,为每个聚合根创建一个仓储。

  最后,提醒一下,我们使用一些DDD分层架构构造块,可能并不算真正用上了DDD。但是,我们的目标是使用DDD吗?不是,我们的目标是把业务逻辑做得更稳定,更好维护。所以不用在意自己使用的技术正不正宗,标不标准,只要比以前更好,就应该坚持下去。

  .Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

  谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

时间: 2024-10-13 19:48:06

应用程序框架实战十三:DDD分层架构之我见的相关文章

应用程序框架实战十三:DDD分层架构之我见(转)

前面介绍了应用程序框架的一个重要组成部分——公共操作类,并提供了一个数据类型转换公共操作类作为示例进行演示.下面准备介绍应用程序框架的另一个重要组成部分,即体系架构支持.你不一定要使用DDD这样的架构,使用单层架构和普通三层架构一样可以,不过你如果希望获得更进一步的复用性和封装度,使用更加面向对象的技术是必经之程. 我在2010年以前还在使用古老的ASP.NET WebForm和原始的Ado.Net.之前我有个观念:.NET技术发展太快,跟着微软屁股后面跑太累,所以只使用它一些原始的东西,自己封

应用程序框架实战十七:DDD分层架构之值对象(层超类型篇)

上一篇介绍了值对象的基本概念,得到了一些朋友的支持,另外也有一些朋友提出了不同意见.这其实是很自然的事情,设计本来就充满了各种可能性,没有绝对正确的做法,只有更好的实践.但是设计与实践的好与坏,对于不同的人,以及处于不同的环境都有不同的诠释,这是一个仁者见仁,智者见智的问题.DDD非常抽象,以至于它的每一个概念,对于不同的人都有不同的看法,更何况基于DDD的.Net实践,就更难分辨哪一个用法更标准.更正宗. 我对DDD的认识虽然还很肤浅,用得也很山寨,但这可能更加适合初步接触DDD的朋友.还是那

应用程序框架实战十五:DDD分层架构之领域实体(验证篇)

在应用程序框架实战十四:DDD分层架构之领域实体(基础篇)一文中,我介绍了领域实体的基础,包括标识.相等性比较.输出实体状态等.本文将介绍领域实体的一个核心内容——验证,它是应用程序健壮性的基石.为了完成领域实体的验证,我们在前面已经准备好了验证公共操作类和异常公共操作类. .Net提供的DataAnnotations验证方法非常强大,Mvc会自动将DataAnnotations特性转换为客户端Js验证,从而提升了用户体验.但是客户端验证是靠不住的,因为很容易绕开界面向服务端提交数据,所以服务端

应用程序框架实战十八:DDD分层架构之聚合

前面已经介绍了DDD分层架构的实体和值对象,本文将介绍聚合以及与其高度相关的并发主题. 我在之前已经说过,初学者第一步需要将业务逻辑尽量放到实体或值对象中,给实体“充血”,这样可以让业务逻辑高度内聚,并为你提供业务逻辑的唯一访问点.而聚合则是第二步,它将多个相关业务概念包装到单一的概念中,从而大幅简化系统设计,由于受传统数据建模思维影响,我在聚合方面吃过大亏,花了将近一年才真正用起来,为了你少走弯路,我会把一些要点总结出来供你参考. 什么是聚合? 聚合包装一组高度相关的对象,作为一个数据修改的单

应用程序框架实战十六:DDD分层架构之值对象(介绍篇)

前面介绍了DDD分层架构的实体,并完成了实体层超类型的开发,同时提供了验证方面的支持.本篇将介绍另一个重要的构造块——值对象,它是聚合中的主要成分. 如果说你已经在使用DDD分层架构,但你却从来没有使用过值对象,这毫不奇怪,因为多年来养成的数据建模思维已经牢牢把你禁锢,以致于你在使用面向对象方式进行开发时,还是以数据为中心. 当我们完成了基本的需求分析以后,如果说需要进行设计,那么你能想到的就是数据库表及表关系的设计,这就是数据建模.数据建模的主要依据是数据库范式设计,根据要求严格程度的递增分为

应用程序框架实战十四:DDD分层架构之领域实体(基础篇)

上一篇,我介绍了自己在DDD分层架构方面的一些感想,本文开始介绍领域层的实体,代码主要参考自<领域驱动设计C#2008实现>,另外参考了网上找到的一些示例代码. 什么是实体 由标识来区分的对象称为实体. 实体的定义隐藏了几个信息: 两个实体对象,只要它们的标识属性值相等,哪怕标识属性以外的所有属性值都不相等,这两个对象也认为是同一个实体,这意味着两个对象是同一实体在其生命周期内的不同阶段. 为了能正确区分实体,标识必须唯一. 实体的标识属性值是不可变的,标识属性以外的属性值是可变的.如果标识值

应用程序框架实战七:分层架构的选择

建立应用程序框架,首先要考虑的问题是,你准备采用哪种分层架构,然后根据应用程序框架的逻辑层次来确定需要创建的VS解决方案和程序集. 如果项目很小,需求很简单,时间异常紧迫,且你手上没有任何积累,那么,单层架构将是首选,最简单的单层架构如下图所示(为了集中你的注意力,我把不相关的文件都删除了). 单层架构的主要优势是代码火力集中,干活直截了当,不像多层架构那样拐弯抹角,每个操作都需要层层传递.对于上图的User.aspx,所有相关代码都直接写到aspx页面或后置代码中,包括界面上的控件操作,业务操

应用程序框架实战三十六:CRUD实战演练介绍

从本篇开始,本系列将进入实战演练阶段. 前面主要介绍了一些应用程序框架的概念和基类,本来想把所有概念介绍完,再把框架内部实现都讲完了,再进入实战,这样可以让初学者基础牢靠.不过我的精力很有限,文章进度越来越慢,所以准备切换一下介绍顺序,把实战演练提前,以方便你阅读代码. 实战演练介绍 本系列实战演练共分两个部分. 实战演练第一部分介绍如何快速解决CRUD机械操作,这一部分我将手把手带领各位同学从搭建VS环境开始,创建程序集及各程序集间的依赖关系,以及引入依赖的外部DLL,并手工完成代码示例中Ap

应用程序框架实战三十八:项目示例VS解决方案的创建(一)

进行项目开发的第一步,是创建出适合自己团队习惯的VS解决方案,虽然我已经提供了项目示例,但毕竟是我创建的,你直接使用可能并不合适,另外你如果尝试模仿重新创建该示例,中间可能碰到各种障碍,特别是项目间的依赖关系. 本文的目的是帮助.Net架构初学者能顺利搭建起适合自己的VS解决方案,我会在本文演示曾经用过的几种不同风格的目录结构,你可以根据自己的习惯选择一种并自行修改. 本系列假定你已经熟悉如何创建.NET类库等基础知识,并具有.Net开发经验,我不会详细到每一个细节.如果你是.Net初学者,尚未