领域驱动模型/领域驱动设计(简称 ddd)概念开源于2004年著名建模专家eric evans 发表的他最具影响力的书籍:《domain-driven design –tackling complexity in the heart of software》(中文译名:领域驱动设计——软件核心复杂性应对之道),书中提出了“领域驱动设计(简称 ddd)”的概念。
领域驱动设计一般分为两个阶段:
领域驱动设计告诉我们,在通过软件实现一个业务系统时,建立一个领域模型是非常重要和必要的,因为领域模型具有以下特点:
- 领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;领域模型是有边界的,只反映了我们在领域内所关注的部分;
- 领域模型只反映业务,和任何技术实现无关;领域模型不仅能反映领域中的一些实体概念,如货物,书本,应聘记录,地址等;还能反应领域中的一些过程概念,如资金转账等;
- 领域模型确保了我们的软件的业务逻辑都在一个模型中,都在一个地方;这样对提高软件的可维护性,业务可理解性以及可重用性方面都有很好的帮助;
- 领域模型能够帮助开发人员相对平滑地将领域知识转化为软件构造;
- 领域模型贯穿软件分析、设计,以及开发的整个过程;领域专家、设计人员、开发人员通过领域模型进行交流,彼此共享知识与信息;因为大家面向的都是同一个模型。所以可以防止需求走样,可以让软件设计开发人员做出来的软件真正满足需求;
- 要建立正确的领域模型并不简单,需要领域专家、设计、开发人员积极沟通共同女里,然后才能使大家对领域的认识不断深入,从而不断细化和完善领域模型;
- 为了让领域模型看的见,我们需要用一些方法来表示它;图是表达领域模型最常用的方式,但不是唯一的表达方式,代码或文字描述也能表达领域模型;
- 领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化;
根据eric evans的定义:“一个由它的标识定义的对象叫做实体”。通常实体具备唯一id,能够被持久化,具有业务逻辑,对应现实世界业务对象。
实体一般和主要的业务/领域对象有一个直接的关系。一个实体的基本概念是一个持续抽象的生命,可以变化不同的状态和情形,但总是有相同的标识。
需要注意的是:
一些开发人员将实体当成了ORM意义上的实体,而不是业务所有和业务定义的领域对象。在一些实现中采用了transaction script风格的架构,使用贫血的领域模型。这种认识上的混乱,在领域驱动架构中,不愿意在领域对象中加入业务逻辑而导致贫血的领域模型,同时还可能使混乱的服务对象激增。
值对象的定义是:描述事物的对象;更准确地说,一个没有概念上标识符描述一个领域方面的对象。这些对象是用来表示临时的事物,或者可以认为值对象是实体的属性,这些属性没有特性表示但同时表达了领域中某类含义的概念。
通常值对象不具有唯一id,由对象的属性描述,可以用来传递参数或对实体进行补充描述。
作为实体属性的描述时,值对象也会被存储。在uml的类图上显示为一对多或一对一的关系。在ORM映射关系上需要采用较复杂的一对多或一对一关系映射。
关于实体与之对象的一个例子:比如员工信息的属性,如住址,电话号码都可以改变;然而,同一个员工的实体的标识将保持不变。因此,一个实体的基本概念是一个持续抽象的生命,可以变化不同的状态和情形,但总是有相同的标识。
实体与值对象的区别
实体具有唯一标识,而值对象没有唯一标识,这是实体和值对象间的最大不同。
实体就是领域中需要唯一标识的领域概念。有两个实体,如果唯一标识不一样,那么即便是提的欺压所有属性都一样,也认为是两个不同的实体;一个实体的基本概念是一个持续抽象的生命,可以变化不同的状态和情形,但总是有相同的标识。
不应该给实体定义太多的属性或行为,而应该寻找关联,发现其他一些实体或值对象,将属性或行为转移到其他关联的实体或值对象上。
如果两个对象的所有的属性的值都相同,我们会认为它们是同一个对象的话,那么我们就可以把这种对象设计为值对象。值对象在判断是否是同一个对象时是通过他们的所有属性是否相同,如果相同则认为是同一个值对象;而实体是否为同一个实体的区分,只是看实体的唯一标识是否相同,而不管实体的属性是否相同。
值对象另外一个明显的特征是不可变,即所有属性都是只读的。因为属性是只读的,所以可以被安全的共享;当共享值对象时,一般有复制和共享两种做法,具体采用哪种做法还要根据实际情况而定。
聚合是用来定义领域对象所有权和边界的领域模式。聚合的作用是帮助简化模型对象间的关系。聚合,他共过定义对象之间的清晰的所属关系和边界来实现领域模型的内聚,并避免了错综复杂的难以维护的对象关系网的形成。聚合定义了一组具有内聚关系的相关对象的集合,我们把聚合看作是一个修改数据的单元。
划分aggregation是对领域模型的进一步深化,aggregation能禅师领域模型内部对象之间的深层关联,对aggregation的划分会直接映射到程序结构上。比如:ddd 推荐按aggregation设计model的自爆。每个aggregation配备一个repository。aggregation内部的非root对象是通过 导航获得的。
一个聚合是一组相关的被视为整体的对象。每个聚合都有一个根对象(聚合根实体),从外部访问只能通过这个对象。根实体对象有组成聚合所有对象的引用,但是外部对象只能引用根对象实体。
聚合有以下一些特点:
如何识别聚合?
聚合中的对象关系是内聚的,即这些对象之间必须保持一个固定规则,固定规则是指在数据变化时必须保持不变的一致性规则。
当我们在修改一个聚合时,我们必须在是无级别确保整个聚合内的所有对象满足这个固定规则。
作为一条建议,聚合尽量不要太大,否则即便能够做到在事务级别保持聚合的业务规则完整性,也可能会带来一定的性能问题。
如何识别聚合根?
如果一个聚合只有一个实体,那么这个实体就是聚合根;如果有多个实体,可以思考聚合内哪个对象有独立存在的意义并且可以和外部直接进行交互。
并不是所有的实体都是聚合根,但只有实体才能成为聚合根。
工厂用来封装创建一个复杂对象尤其是聚合是所需的知识,作用是将创建对象的细节隐藏起来。客户传递给工厂一些简单的参数,然后工厂可以在内部创建出一个复杂的领域对象然后返回给客户。当创建实体和值对象复杂时建议使用工厂模式。这并不意味着我们一定要使用工厂模式。如果创建对象很简单,使用构造器或者控制反转/依赖注入容器足够创建对象的依赖,此时,我们就不需要通过工厂模式来创建实体或值对象。
良好工厂的要求:
每个创建方法都是原子的。一个工厂应该只能生产透明状态的对象昂。对于尸体,意味着创建整个聚合时满足所有的不变量。
一个单独的工厂通常生产整个聚合,传出一个根实体的引用,确保聚合的不变量都有。如果对象的内部聚合需要工厂,通常工厂方法的逻辑放在聚合根上,这样对外部隐藏了聚合内聚的实现,同时赋予了根确保聚合完整的职责。如果聚合根不是子实体工厂的合适的家,那么继续创建一个单独的工厂。
仓储是用来管理实体的聚合。
仓储里面存放的对象一定是聚合,原因是domain是以聚合的概念来划分边界的;聚合作为一个整体概念,要么一起被取出来,要么一起被删除。外部访问不会单独对某个聚合内的子对象进行单独操作。因此,我们只对聚合设计仓储。
仓储还有一个重要的特征就是分为仓储定义部分和仓储实现部分,我们在领域模型中定义仓储的接口,而是在基础设施层实现具体的仓储。也符合按照接口分离模式在领域层定义仓储库接口的原则。
注意:repositories本身是一种领域组建,但repositories的实现却不是领域层中的。
respositories 和 dao:
dao 和 respository 在领域驱动设计中都很重要。dao是面向数据访问的,是关系型数据库和应用之间的契约。
repository:位于领域层,面向aggregation root。repository是一个独立的抽象,使用领域的通用语言,它与dao进行交互,并使用领域理解的语言提供对领域模型的数据访问服务的“业务接口”。
dao 方法是细粒度的,更接近数据库,而repository 是一个独立的抽象,使用领域的通用语言,它与dao进行交互,并使用领域理解的语言提供对领域模型的数据访问服务的“业务接口”。
dao方法是细粒度的,更接近数据库,而repository方法的粒度粗一些,而且更接近领域。领域对象应该只依赖于repository接口。客户端应该始终调用领域对象,领域对象再调用dao将数据持久化到数据存储中。
处理领域对象之间的依赖关系(比如实体及其repository之间的依赖关系)是开发人员经常遇到的典型问题。解决这个问题通常的设计方案是让服务类或外观类直接调用repository,在调用repository 的时候返回实体对象给客户端。
服务提供的操作是它提供给使用它的客户端,并突出领域对象的关系。
所有的service只负责协调并委派业务逻辑给领域对象进行处理,其本身并不真正实现业务逻辑,绝大部分的业务逻辑都由领域对象承载和实现了。
service可与多种组件进行交互,这些组件包括:其他的service、领域对象和repository或dao。
服务的三个特征:
领域服务与domain对象的区别
一般的领域对象都是有状态和行为的,而领域服务没有状态只有行为。需要枪带哦的是领域服务是无状态的,它存在的意义就是协调领域对象共同完成某个操作,所有的状态还是都保存在响应的领域对象中。
通常,对开发人员来说创建不应该存在的服务相当容易;要么在服务中包含了本应存在于领域对象中的领域逻辑,要么扮演了缺失的领域对象角色,而这些领域对象并没有作为模型的一部分去创建。
企业级应用程序事件大致可以分为三类:系统事件、应用实践和领域事件。领域事件的触发点在领域模型(domain model)中。它的作用是将领域对象从对repository或service的依赖中解脱出来,避免让领域对象对这些设施产生直接依赖。它的做法就是当领域对象的业务方法需要依赖到这些对象时就发出一个事件,这个事件会被相应的对象监听到并作出处理。
通过使用领域事件,我们可以实现领域模型对象状态的异步更新、外部系统接口的委托调用,以及
通过事件派发机制实现系统集成。另外,领域事件本身具有自描述性。它不仅能够表述系统发生了什么事情,而且还能够描述发生事件的动机。
domian 时间也用表进行存储。
dto - data transfer object (数据传输对象):dto在设计之初的主要考量是以粗粒度的数据结构减少网络通信并简化调用接口。