通用语言:
通过团队交流达成共识的能够简单清晰准确传递业务规则的语言(可以是文字、图片等)即可称为通用语言。
领域:
一个领域本质上可以理解为就是一个问题域,只要是同一个领域,那问题域就相同。所以,只要我们确定了系统所属的领域,那这个系统的核心业务,即要解决的关键问题、问题的范围边界就基本确定了。领域即问题域。
- 领域是有范围界限的,也可以说是有边界的。
- 核心域是业务系统的核心价值所在,承载着一个系统的重中之重。
- 通用子域可以理解为业务系统所有子域的消费者,提供着通用服务。
- 支撑子域专注于业务系统的某一重要的业务,来支撑和完善业务系统。
限界上下文:
观察角度的不同,限界上下文划定的边界也有所不同。大体可以分为如下三个方面:
领域逻辑层面:限界上下文确定了领域模型的业务边界,维护了模型的完整性与一致性,从而降低系统的业务复杂度。
团队合作层面:限界上下文确定了开发团队的工作边界,建立了团队之间的合作模式,避免团队之间的沟通变得混乱,从而降低系统的管理复杂度。
技术实现层面:限界上下文确定了系统架构的应用边界,保证了系统层和上下文领域层各自的致性,建立了上下文之间的集成方式,从而降低系统的技术复杂度。
这三种边界体现了限界上下文对不同边界的控制力,业务边界是对领域模型的控制,工作边界是对开发协作的控制,应用边界是对技术风险的控制。引入限界上下文的目的:其实不在于如何划分边界,而在于如何控制边界。
怎么理解呢,就是说对限界上下文的划分可以从三个角度去看。
- 从领域逻辑的角度,即根据业务知识进行划分,比如将电商项目划分为订单、商品、促销、物流、售后等限界上下文。
- 从技术实现角度,比如为了解决系统的高并发,决定引入缓存,那就可以考虑将缓存服务抽离为一个独立的限界上下文作为支撑。
- 从团队合作的角度,即回到了以人为本的思想。比如根据领域业务划分的限界上下文组建领域特性团队进行职责划分以确定团队协作边界。明确的团队边界有利于团队的沟通和协作
领域模型:
领域反应的是我们业务上需要解决的问题,模型是我们针对该问题提出的解决方案。
综合来说,领域模型就是用来描述我们正在解决的问题和提出的解决方案。
领域模型按照我个人的理解,就是将业务中涉及到的概念以面向对象的思想进行抽象,抽象出实体对象,确定实体所对应的方法和属性,以及实体之间的关系。然后将这些实体和实体之间的关系以某种形式(比如UML、图形、代码、文字描述等)展现出来。
实体 :
实体 = 唯一身份标识 + 可变性【状态(属性) + 行为(方法或领域事件或领域服务)】
唯一标识的类型在不同的场景又有不同的要求。
主要可以分为有意义和无意义两种。
在一个简单的应用程序里,一个int类型的自增Id就可以作为唯一标识。优点就是占用空间小,查询速度快。
而在一些业务当中,要求唯一标识有意义,通过唯一标识就能识别出一些基本信息,比如支付宝的交易号,其中就包含了日期和用户ID。这种就属于字符串类型的标识,这就对唯一标识的生成提出了挑战。
在一些复杂的业务流程中,对唯一标识没有要求,我们可以使用GUID类型来生成唯一标识,很显然GUID占用空间就毕竟大,且不利于查询。
值对象:
值对象=值+对象=将一个值用对象的方式进行表述,来表达一个具体的固定不变的概念。
- 表示一个具体的概念
我们上面设计的Address类,也能表示出地址这个概念。 - 通过值的属性对其识别
也就是不需要唯一标识,删去我们设计的AddressId即可。 - 属性判等
重写Equals方法,比较属性判断。 - 固定不变
就是通过构造函数来初始化,所有属性均不提供修改入口。
应用服务&领域服务:
DDD传统分层架构
- 应用层(Application):负责展现层与领域层之间的协调,协调业务对象来执行特定的应用程序任务。它不包含业务逻辑。
应用服务是用来表达用例和用户故事(User Story)的主要手段。
应用层通过应用服务接口来暴露系统的全部功能。在应用服务的实现中,它负责编排和转发,它将要实现的功能委托给一个或多个领域对象来实现,它本身只负责处理业务用例的执行顺序以及结果的拼装。通过这样一种方式,它隐藏了领域层的复杂性及其内部实现机制。
- 领域层(Domain):负责表达业务概念,业务状态信息以及业务规则,是业务软件的核心。
领域服务是用来协调领域对象完成某个操作,用来处理业务逻辑的,它本身是一个行为,所以是无状态的。状态由领域对象(具有状态和行为)保存。
以下情况使用领域服务:
- 执行一个显著的业务操作过程
- 对领域对象进行转换
- 以多个领域对象为输入,返回一个值对象。
- 服务是行为的抽象。
- 应用服务通过委托领域对象和领域服务来表达用例和用户故事。
- 领域对象(实体和值对象)负责单一操作。
- 领域服务用于协调多个领域对象共同完成某个业务操作。
- 应用服务不处理业务逻辑,领域服务处理业务逻辑。
领域事件:
领域事件是一个领域模型中极其重要的部分,用来表示领域中发生的事件。忽略不相关的领域活动,同时明确领域专家要跟踪或希望被通知的事情,或与其他模型对象中的状态更改相关联。
针对官方释义,我们可以理出以下几个要点:
- 领域事件作为领域模型的重要部分,是领域建模的工具之一。
- 用来捕获领域中已经发生的事情。
- 并不是领域中所有发生的事情都要建模为领域事件,要忽略无业务价值的事件。
- 领域事件是领域专家所关心的(需要跟踪的、希望被通知的、会引起其他模型对象改变状态的)发生在领域中的一些事情。
通过将领域中所发生的活动建模成一系列的离散事件,并将每个事件都用领域对象来表示,来跟踪领域中发生的事情。
也可以简要理解为:领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。
聚合:
聚合是领域对象的显式分组,旨在支持领域模型的行为和不变性,同时充当一致性和事务性边界。
聚合不仅仅是简单的对象组合,其主要的目的是用来封装业务和保证聚合内领域对象的数据一致性。
那聚合设计时要遵循怎样的原则呢?
- 遵循领域不变性
- 聚合内实现事务一致性,聚合外实现最终一致性
一个事物一次仅更新一个聚合。当业务用例要跨域多个聚合时,使用领域事件进行事务拆分,实现最终一致性。 - 基于业务用例而非现实生活场景
- 避免成为集合或容器
对聚合的一大误解就是,把聚合当作领域对象的集合或容器。当发现这个征兆时,你要考虑你聚合是否需要改造。 - 不仅仅是HAS-A关系
聚合不是简单的包含关系,要确定包含的领域对象是否为了满足某个行为或不变性。 - 不要基于用户界面设计聚合
聚合不应该根据UI界面的需求进行设计。而应该通过加载多个聚合数据映射到UI展示需要的视图模型中。 - 创建具有唯一标识的聚合根
聚合根作为聚合的网关,通过聚合根完成聚合中领域对象的持久化和检索。 - 优先使用值对象
聚合根内的其他领域对象优先设计成值对象 - 使用ID关联,而非对象引用
对象引用不仅会导致聚合边界的模糊,而且会导致延迟加载的问题。 - 通过唯一标识引用其他聚合
聚合边界之外的对象不能持有聚合内部对象的引用;聚合内部的领域对象可以持有其他聚合根的引用。 - 避免在聚合内使用依赖注入
对于依赖的对象,我们应该在调用聚合方法之前查找获取并通过参数传递。可以在应用服务中通过依赖注入资源库或领域服务获取聚合依赖的对象,然后传入聚合。 - 使用小聚合
通常,较小的聚合使系统更快且更可靠,因为更少的数据传输以及更少的并发冲突。
大聚合会影响性能:聚合的每一个成员都增加了从数据库加载和保存到数据库的数据量,直接影响到性能。
大聚合容易导致并发冲突:大的聚合可能有多个职责,意味着它涉及到多个业务用例。我们可以量化一个聚合涉及到的业务用例数,数量越大,设计的聚合边界越应该被质疑,尝试将其细化拆解成小聚合。
大聚合扩展性差:聚合的设计要关注可扩展性。大聚合可能会跨越多个数据库表或文档,这就在数据库级别形成了耦合,它将阻碍你对数据子集进行数据迁移。同时,在业务改变时,大聚合不能很好的适应变化。
工厂:
而针对工厂模式的实现主要有四种方式:
- 简单工厂:简单实用,但违反开放封闭;
- 工厂方法:开放封闭,单一产品;
- 抽象工厂:开放封闭,多个产品;
- 反射工厂:可以最大限度的解耦。
对象创建不是一个领域的关注点,但它确实存在于应用程序的领域层中。通过使用工厂可以有效的保证领域模型的干净整洁,以确保领域模型的对现实的准确表达。使用工厂具有以下好处:
- 工厂将领域对象的使用和创建分离。
- 通过使用工厂类,可以隐藏创建复杂领域对象的业务逻辑。
- 工厂类可以根据调用者的需要,创建相应的领域对象。
- 工厂方法可以封装聚合的内部状态。