DDD理论学习系列——案例及目录


1.引言

实体对应的英语单词为Entity。提到实体,你可能立马就想到了代码中定义的实体类。在使用一些ORM框架时,比如Entity Framework,实体作为直接反映数据库表结构的对象,就更尤为重要。特别是当我们使用EF Code First时,我们首先要做的就是实体类的设计。在DDD中,实体作为领域建模的工具之一,也是十分重要的概念。

但DDD中的实体和我们以往开发中定义的实体是同一个概念吗?

不完全是。在以往未实施DDD的项目中,我们习惯于将关注点放在数据上,而非领域上。这也就说明了为什么我们在软件开发过程中会首先做数据库的设计,进而根据数据库表结构设计相应的实体对象,这样的实体对象是数据模型转换的结果。

在DDD中,实体作为一个领域概念,在设计实体时,我们将从领域出发。

2.DDD中的实体

DDD中要求实体是唯一的且可持续变化的。意思是说在实体的生命周期内,无论其如何变化,其仍旧是同一个实体。唯一性由唯一的身份标识来决定的。可变性也正反映了实体本身的状态和行为。

3. 唯一标识

举个例子,在有双胞胎的家庭里,家人都可以快速分辨开来。这得益于家人对双胞胎性格和外貌的区分。然而邻居却不能,只能通过名字来区分。上小学后,学校里尽然有重名的,这时候就要取外号区分了。上大学后,要坐火车去学校,买票时就要用身份证号来区分了。

针对这个例子,如果我们要抽象出一个User实体,要如何定义其唯一标识呢?

其中性格、外貌、昵称、身份证号都可以作为User实体的属性,在某些场景下某个属性就可以对对象进行区分。但为了确保标识的稳定性,我们只能将身份证号设为唯一身份标识。

3.1.唯一标识的类型

唯一标识的类型在不同的场景又有不同的要求。

主要可以分为有意义和无意义两种。

在一个简单的应用程序里,一个int类型的自增Id就可以作为唯一标识。优点就是占用空间小,查询速度快。

而在一些业务当中,要求唯一标识有意义,通过唯一标识就能识别出一些基本信息,比如支付宝的交易号,其中就包含了日期和用户ID。这种就属于字符串类型的标识,这就对唯一标识的生成提出了挑战。

在一些复杂的业务流程中,对唯一标识没有要求,我们可以使用GUID类型来生成唯一标识,很显然GUID占用空间就毕竟大,且不利于查询。

3.2.唯一标识的生成时机

有某些场景下,唯一标识的生成时机也各不相同,主要分为即时生成和延迟生成。

即时生成,即在持久化实体之前,先申请唯一标识,再更新到数据库。

延迟生成,即在持久化实体之后。

3.3.委派标识和领域标识

基于领域实体概念分析确定的唯一身份标识,我们可以称为领域实体标识。

而在有些ORM工具,比如Hibernate、EF,它们有自己的方式来处理对象的身份标识。它们倾向于使用数据库提供的机制,比如使用一个数值序列来生成识。在ORM中,委派标识表现为int或long类型的实体属性,来作为数据库的主键。很显然,委派标识是为了迎合ORM而创建的,且委派标识和领域实体标识无任何关系。

那既然ORM需要委派标识,我们就可以创建一个实体基类来统一指定委派标识。而这个实体基类又被称为层超类型。

3.3.1.实现层超类型

首先定义层超类型接口:

public interface IEntity
{
} public interface IEntity<TPrimaryKey> : IEntity
{
TPrimaryKey Id { get; set; }
}

通过定义泛型接口,以支持自定义主键类型。

实现层超类型:

    public class Entity : Entity<int>, IEntity
{
} public class Entity<TPrimaryKey> : IEntity<TPrimaryKey>
{
public virtual TPrimaryKey Id { get; set; } public override bool Equals(object obj)
{
if (obj == null || !(obj is Entity<TPrimaryKey>))
{
return false;
} //Same instances must be considered as equal
if (ReferenceEquals(this, obj))
{
return true;
} //Must have a IS-A relation of types or must be same type
var typeOfThis = GetType();
var typeOfOther = other.GetType();
if (!typeOfThis.GetTypeInfo().IsAssignableFrom(typeOfOther) && !typeOfOther.GetTypeInfo().IsAssignableFrom(typeOfThis))
{
return false;
} return Id.Equals(other.Id);
} public override int GetHashCode()
{
return Id.GetHashCode();
} public static bool operator ==(Entity<TPrimaryKey> left, Entity<TPrimaryKey> right)
{
if (Equals(left, null))
{
return Equals(right, null);
} return left.Equals(right);
} public static bool operator !=(Entity<TPrimaryKey> left, Entity<TPrimaryKey> right)
{
return !(left == right);
}
}

可以看到默认的委托标识为int类型。我们重写了Equals,GetHashCode方法,以及==和!=两个操作符。

通过这样一种方式,我们进行约定,所有的实体必须继承自Entity,即可实现委托标识的统一定义。

4.可变性

解决了实体的唯一身份标识问题后,我们就可以保证其生命周期中的连续性,不管其如何变化。

那可变性说的是什么呢?可变性是实体的状态和行为。

而实体的状态和行为就要对具体的业务模型加以分析,提炼出通用语言,再基于通用语言来抽象成实体对应的属性或方法。

我们拿订单环节来举例说明:

当顾客从购物车点击结算时创建订单,初始状态为未支付状态,支付成功后切换到正常状态,此时可对订单做发货处理并置为已发货状态。当顾客签收后,将订单关闭。

从以上的通用语言的描述中(在通用语言的术语中,名词用于给概念命名,形容词用于描述这些概念,而动词则表示可以完成的操作。)

我们可以提取订单的相关状态和行为:

  • 订单状态:未支付、正常、已发货、关闭。针对状态,我们需定义一个状态属性即可。
  • 订单的行为:支付、发货和关闭。针对行为,我们可以在实体中定义方法或创建单独的领域服务来处理。

实体既然存在状态和行为,就必然会与事件有所牵连。比如订单支付成功后,需要知会商家发货。这时我们就要追踪订单状态的变化,而追踪变化最实用的方法就是领域事件。关于领域事件,我们后续再讲。

5.实体的验证

验证的目的是为了检查模型的正确性和有效性。检查的对象可以为某个属性,也可以是整个对象,或是多个对象的组合。针对验证的方式,不一而足,根据需要可自行发挥。

6.总结

实体作为领域建模的工具之一,唯一的身份标识是实体最基本的特征,其次是可变性。唯一身份标识和可变性也是用来区分实体和值对象的主要特征。

为了正确建立实体模型,我们需要将关注点从数据转向领域,从业务模型中提炼通用语言,再基于通用语言分析其状态和行为。

所以,我们可以认为:实体 = 唯一身份标识 + 可变性【状态(属性) + 行为(方法或领域事件或领域服务)】

DDD理论学习系列(6)-- 实体的更多相关文章

  1. DDD理论学习系列(4)-- 领域模型

    DDD理论学习系列目录 1.引言 我们还是先来拆词理解,领域模型可以拆为"领域"和"模型"二词. 领域:按照我们之前的文章的理解,DDD中的领域是指软件系统要解 ...

  2. DDD理论学习系列(5)-- 统一建模语言

    DDD理论学习系列--案例及目录 1.引言 上一节讲解了领域模型,领域模型主要是将业务中涉及到的概念以面向对象的思想进行抽象,抽象出实体对象,确定实体所对应的方法和属性,以及实体之间的关系.然后将这些 ...

  3. DDD理论学习系列(7)-- 值对象

    DDD理论学习系列--案例及目录 1.引言 提到值对象,我们可能立马就想到值类型和引用类型.而在C#中,值类型的代表是strut和enum,引用类型的代表是class.interface.delega ...

  4. DDD理论学习系列(8)-- 应用服务&领域服务

    DDD理论学习系列--案例及目录 1. 引言 单从字面理解,不管是领域服务还是应用服务,都是服务.而什么是服务?从SOA到微服务,它们所描述的服务都是一个宽泛的概念,我们可以理解为服务是行为的抽象.从 ...

  5. DDD理论学习系列(10)-- 聚合

    DDD理论学习系列--案例及目录 1.引言 聚合,最初是UML类图中的概念,表示一种强的关联关系,是一种整体与部分的关系,且部分能够离开整体而独立存在,如车和轮胎. 在DDD中,聚合也可以用来表示整体 ...

  6. DDD理论学习系列(11)-- 工厂

    DDD理论学习系列--案例及目录 1.引言 在针对大型的复杂领域进行建模时,聚合.实体和值对象之间的依赖关系可能会变得十分复杂.在某个对象中为了确保其依赖对象的有效实例被创建,需要深入了解对象实例化逻 ...

  7. DDD理论学习系列(12)-- 仓储

    DDD理论学习系列--案例及目录 1. 引言 DDD中Repository这个单词,主要有两种翻译:资源库和仓储,本文取仓储之译. 说到仓储,我们肯定就想到了仓库,仓库一般用来存放货物,而仓库一般由仓 ...

  8. DDD理论学习系列(13)-- 模块

    DDD理论学习系列--案例及目录 1. 引言 Module,即模块,是指提供特定功能的相对独立的单元.提到模块,你肯定就会想到模块化设计思想,也就是功能的分解和组合.对于简单问题,可以直接构建单一模块 ...

  9. DDD理论学习系列——案例及目录

    目录 DDD理论学习系列(1)-- 通用语言 DDD理论学习系列(2)-- 领域 DDD理论学习系列(3)-- 限界上下文 DDD理论学习系列(4)-- 领域模型 DDD理论学习系列(5)-- 统一建 ...

随机推荐

  1. 【Java】大文本字符串滤重的简单方案~

    本文章也同步至本人的CSDN博客中: http://blog.csdn.net/u012881584/article/details/70477832 今天来说一个Java中处理大文本字符串虑重的两个 ...

  2. [bug] Cannot proceed because system tables used by Event Scheduler were found damaged at server start

    本地:mac 10.12.3  mysql 5.6 远程:linux 7.3    mysql 5.7.18.  (远程数据库yum安装,又5.6升级到5.7) 步骤:从本地数据库导出数据到远程数据库 ...

  3. 【知识学习】如何寻找真实IP

    1.多地点ping查询IP,如果都一样可能没有使用cdn,如果有cdn,尝试海外地点ping查询IP 2.ping一下没有WWW的域名,可能存在真实IP.比如www.baidu.com设置了cdn,那 ...

  4. Yii2框架---GII自动生成

    本地环境配置完成后.访问路径直接加上/gii 例如 localhost/gii 即可生成YII活动记录类 即可生成模块

  5. 浅谈JavaScript时间与正则表达式

    时间函数:var box = new Date() 函数       Demo:         alert(Date.parse('4/12/2007'));    //返回的是一个毫秒数11763 ...

  6. 微信公众号开发笔记1(nodejs开发的)

    本篇记录了微信公众号开发的一些笔记 一.微信服务器与我们服务器的交流 微信开发者拥有自己的服务器,在我们服务器上可以与微信服务器进行交流.既然可以交流,那就必定需要前提条件(微信认证),也就是说,只有 ...

  7. poj2774 Long Long Message 后缀数组求最长公共子串

    题目链接:http://poj.org/problem?id=2774 这是一道很好的后缀数组的入门题目 题意:给你两个字符串,然后求这两个的字符串的最长连续的公共子串 一般用后缀数组解决的两个字符串 ...

  8. Java学习笔记——设计模式之五.工厂方法

    水边一只青蛙在笑 --石头和水 工厂方法模式(Factory Method),定义了一个用于创建对象的接口,让实现类决定实例化哪一个类.工厂方法使一个类的实例化延迟到其子类. 这里在简单和工厂的基础上 ...

  9. Redis学习-持久化

    Redis 提供了多种不同级别的持久化方式: RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot). AOF 持久化记录服务器执行的所有写操作命令 ...

  10. ASP实现计算机爱好者网站,可以直接浏览

    利用ASP制作的计算机爱好者协会网站,内容比较全面,具有母版和子页的功能,利用ACCESS数据库进行资源存储.适合新手学习和网页制作比赛参考 下载地址:http://download.csdn.net ...