领域驱动设计 ——一种将概念模型化的方式

原文发布于:http://www.gufeng.tech/  谷风的我的主页算法

1.引子

      2004年Eric Evans 发表了一本书:《Domain-Driven Design: Tackling Complexity in the Heart of Software》(中文名:《领域驱动设计:软件核心复杂性应对之道》),在这本书中做者提出了领域驱动设计(DDD)的概念,到如今已经10多年的时间了。数据库

1.1 面向对象与面向对象语言

      面向对象思想已经存在至关长的历史了(相对于软件的历史),我而们使用的语言,不少也都是面向对象的,可是咱们使用面向对象的语言就必定能写出来面向对象的程序吗?显然是不可能的。业务逻辑代码的堆积、缺少良好设计的系统或模块亦或是功能,这样是不能保证代码的复用性、扩展性的。缓存

1.2  领域模型

      领域驱动设计的出现,就是为了解决这一问题的。领域驱动设计是以创建正确的领域模型为核心,以构建清晰的分层架构基础,从而使面向对象的开发进入到了一个新的阶段。架构

      领域驱动设计的前提是有一种可以在领域专家(业务专家)、设计人员、开发人员(为何会有开发人员,咱们会在后面介绍缘由)三类参与者通用的沟通语言,在三类参与者的不断交流、沟通中发现领域概念(业务概念),再将概念固化成模型,最后由领域模型驱动设计并实现。ide

      说到这里,看上去领域模型并无什么特别的地方,与咱们平常分析的方式没什么大的区别,咱们首先来简单介绍写领域模型的两个特色:编码

      1)业务逻辑集中在领域对象(类)上;架构设计

      2)每一个领域对象是完整和独立的,并具备本身的属性和行为。翻译

      在接下来的内容中,咱们一块儿来了解下如何实现领域驱动设计以及领域驱动设计的优势。设计

2.领域驱动设计

      领域驱动设计涵盖了领域模型、领域语言、架构设计、实现几部份内容,下面咱们逐一了解一下。对象

2.1 领域模型

      关于什么是领域模型以及领域模型的特色,在前面内容中咱们有了总体的了解,接下来咱们就领域模型自己进行一下简单的了解。

2.1.1 抽象模型

      领域模型是某个边界内的领域的一个抽象,是客观世界的模型,首先它使有边界的,清晰的边界是领域模型抽象是否完整的一个重要衡量指标。在该领域模型内,咱们只关心领域内的内容。

      领域模型只是实际业务的一种反映,与具体实现技术无关。能够说领域模型创建的成功与否,直接关系到最终的实现、使用等等。领域模型确保任参与人在任什么时候间看到的内容都是同样的,了解了模型,就能知道实现的步骤。

      领域模型对于提升软件的维护性、复用性以及业务可理解性等方面都有很好的帮助。领域模型贯穿整个分析、设计、开发过程,前面提到的三类参与者使用一种你们都能理解的语言进行沟通,确保全部人对模型的理解是一致的,这样最终开发出来的结果和最初的设计才能最大程度的吻合。

      要创建一个好的领域模型并不简单,甚至多是一路坎坷,须要领域专家、设计人员、开发人员通力配合、深刻交流、共享信息和知识。最后,领域模型要经过文档或图形方式展示出来(推荐使用图形分解总体结构,配以文字说明)。设计足够好的领域模型,确定是符合业务需求的,同时也可以快速响应需求变化。

      与领域模型紧密相关的还有另一组概念:聚合、聚合根。下面咱们来简单了解下这两个概念。

      聚合:经过定义对象间的隶属关系和边界来实现领域模型的内聚,

      聚合根:聚合内的某个实体,外部调用聚合时,必须从聚合根开始调用,不能绕过。

      关于聚合的一些特色:

      1)每一个聚合有一个根和边界;

      2)内部对象可互相引用,可是外部对象访问聚合时,必须从聚合根开始;

      3)除根外,其它对象在聚合内保持惟一便可;

      4)聚合内部对象能够保持对其它聚合根的引用;

      5)删除聚合根时,必须同时删除其它聚合内对象。

      全部具备独立含义而且可以被单独访问的内容是聚合。

2.1.2 领域通用语言

      设想一下,领域专家满口的专业术语,设计人员满口的设计理论,开发人员满口的开发语言及算法,这样的团队怎么沟通!固然能够引入“翻译”,可是“翻译”的结果以及对结果的理解会形成多大程度上的信息丢失,谁也不肯定。

      基于以上的缘由,迫切须要一种你们都可以表达出来和理解的语言——这就是领域通用语言。领域通用语言是领域驱动设计的基础和前提。在三类参与者的各类形式沟通中,都要使用领域通用语言,确保本身的信息可以被其余人完整、快速的理解。

2.1.3 模型到实现

      假设咱们已经拥有了一个很是正确且严谨的模型,那么是否能将这个模型直接转换成代码吗?确定是不行的。因此要求咱们在领域建模和设计时,就要考虑最终的代码实现,将领域模型与实现紧密关联起来,这就是为何要有开发人员参与的缘由。

      这样的结构(开发人员参与模型创建、结构设计)有利于尽早发现那些不适合在软件中实现的模型部分并要求修正,这样也避免了在最后实现时发现问题、修正设计所带来的巨大时间损失。同时,由于开发人员参与了模型设计,因此在编码实现时,都会尽力保护模型不被破坏(由于这是你们共同努力的结果),同时当开发人员发现编码实现有不知足模型或者不完善的地方,也会去完善它,进行代码重构,这样可以在很大程度上提高软件的可靠性,也便于其余人员在接手时能快速了解模型、掌握实现。

2.2 领域驱动设计的架构分层

      咱们先来看一张Eric Evans 在他的《Domain-Driven Design: Tackling Complexity in the Heart of Software》一书中提到的分层图:

     关于这张图可能都不陌生,可是每一层在领域驱动设计中的职责是什么?完成什么样的功能?层与层之间的协做关系是什么样的?这些问题会在后面一一解释。

2.2.1 分层

      1)用户界面

      人机交互部分,没有特殊内容。

      2)应用

       此应用非彼应用,这里的应用只是很薄的一层,用于给User Interface提供功能接口,并调用Domain完成功能逻辑,看到这里应该有了比较明确的认识了,Application不包括任何业务,只是将根据User Interface的须要提供接口,并完成对一个或者多个Domain的调用。

       本层包含了全部软件系统要完成的任务,经过本层就能了解总体功能,User Interface仅仅是一种展示方式。

      3)领域

       这一层是整个系统的核心部分,包括了所有的业务逻辑、业务规则等所有业务相关内容。

      4)基础设施

       这里的基础设施指的是基础技术组件,包括消息通讯、持久化、缓存等等全部的基础技术组件。

2.2.2 几种辅助模式

      1)实体(Entity)

      具备跨越系统的生命周期甚至能超越软件系统的一系列的延续性和标识符的对象成为实体。简单说就是具备绝对惟一标识的对象。好比银行帐户的ID是惟一的标识,那么一个银行帐户就是一个实体。实体拥有本身的属性,管理本身的内部状态并对外暴露行为。

      2)值对象(Value Object)

      当咱们关心对象的惟一标识而只关心其属性值的时候,这个对象就是一个值对象。对于值对象,理论上能够被轻易的建立以丢掉。若是是可共享值对象,那应该确保它的值是不可变的。“值对象应该保持尽可能的简单。当其余当事人须要一个值对象时,能够简单地传递值,或者建立一个副本。”

      3)服务(Service)

      是否是全部的领域都能映射成对象呢?显然是不可能的,那么如何处理不可以映射成对象的领域呢?这时候就须要服务这个东西了。服务一般对应领域的动做,表明领域中得一些重要行为,而这些行为又不属于任何一个实体或者值对象。这些行为能够定义为服务对象。

      服务可能存在于领域层、基础设施层等,因此要区分服务,不要滥用服务。

      服务对象不包含内部状态,只有行为,因此它提供的主要是行为,做为操做接口存在。咱们须要注意的是,不须要对每个操做建立服务。咱们一块儿来一下服务的几个特征:

    (1)服务执行的操做涉及一个领域概念,这个领域概念一般不属于一个实体或者值对象;

    (2)被执行的操做涉及到领域中的其余的对象;

    (3)操做是无状态的。

      4)模块(Module)

      当模型巨大,难以总体讨论时,须要把这个大得模型拆成几个关联的模块。

      5)聚合&聚合根

      聚合是针对数据变化能够考虑成一个单元的一组相关的对象。聚合使用边界将内部和外部的对象划分开来。每一个聚合有一个根,是一个实体,而且它是外部能够访问的惟一的对象。

      关于聚合与聚合根的内容,可参考2.1.1节。

       6)Factory(工厂)

      引入工厂模式,是由于领域模型自己的复杂性决定的,建立领域对象要远远比建立pojo对象复杂得多,尤为是聚合会更加复杂。此时引入工厂模式,能够将复杂的实现放在工厂内,外部调用工厂方法便可获得相应领域对象,同时也隐藏了建立逻辑(主要是提供给Application和Infrastructure使用的)。

       7)Repository(仓储、资源库)

      仓储最初的设计目的是用来管理内存中的对象,但咱们能够扩展使用,对于须要持久化的领域对象,使用Repository将其持久化到数据库(或其它持久化存储)中,再次须要时,能够经过Repository将对象从数据库中恢复。一般状况下,一个聚合对应一个仓储。

      那么对于那些不可以经过单一Repository查询出来的结果(好比界面中须要展示的数据来源于多个Repository的状况)咱们该怎么办呢?固然能够经过调用多个Repository查询出结果,但更好的方式是经过CQRS架构来实现,也就是说对于查询可绕过Domain,直接由Application发起调用另外的架构或者层来实现。

       8)CQRS(Command Query Responsibility Segregation,命令查询职责分离)

      从字面理解,就是命令和查询要分离开,那么什么事命令呢?非查询的操做即命令。结合领域驱动设计,咱们能够理解成命令能够经过领域驱动设计完成,查询则可以使用简单、直接的方式完成(如直接写SQL)。

      因为是分离的,因此两部分能够采用相同甚至彻底不一样的架构来实现,由此引伸,是否是数据库也能够分开设计呢?固然是能够的。

3.结束

      本文中咱们粗略的了解了领域驱动设计的一些基本概念、原则和一些所谓的“模式”,在实际使用或者叫“领域驱动设计落地”的过程当中,除了一些必须遵照的原则外,咱们能够根据本身的业务特色、团队优点进行裁剪。

      没有任何一种语言是具备绝对优点的,一样也没有任何一种设计方法是绝对正确的。找准咱们本身的方向,找出适合咱们业务特色、团队特色的方法,并对该方法进行落地裁剪,使之更具生命力、可以解决咱们的实际问题。

      最后,不要迷信、迷恋任何一种或几种方法、模式,全部的方法都是人根据经验总结出来,方法、模式能够参考并综合使用,最终达到拥有本身的方法、本身的模式,这样才能更好的服务于本身的业务,创造技术体系。

相关文章
相关标签/搜索