关于领域驱动设计与开发过程中的一些疑惑请道友帮忙解惑,谢谢。

16-06-06 smcdl
大家好,对于领域驱动设计开发来说我是一个新人,目前我在项目中强制自己实施领域驱动设计开发,为的是让自己能快速理解领域驱动设计并在开发中运用自如,但实际结果是处处都是问题,手里有两本书,但看了之后感觉问题更多,下面我问几个我目前最关心的问题,希望在此路上已有成就的道友能帮我一把。

1.领域驱动设计里的所说的Repository的概念是我们以前用的DAO概念吗?目前我在使用Spring的JPA来进行开发,里面有一个JPARepository的概念, 但我的理解JPARepository的概念就是以前所熟悉的DAO模式,但是就是不知道是否是领域驱动设计里所说的Repository的概念了。

2.聚合对象(或者聚合根对象,我也不知道这两个是不是一个东西)大概是一个什么样的对象,书里基本上都是讲概念而没什么例子,如果一个对象里有另一个对象的集合,而对另一个对象集合中添加删改都通过当前对象来操作,那么当前对象就是聚合根吗?

3.Model与Entity可以是一个东西吗?Model一般指的是领域模型对象,Entity一般指数据库中的实体对象,但就我以前的开发经验来看Entity肯定是贫血模型,而领域驱动中设计的Model对象肯定一本都不是贫血模型,另外书中有说Model是通过Repository取得,如果Model和Entity是一回事,那么Repository岂不是和DAO是一回事了吗?之所以提出这个问题在于,我现在应用的模型是Model为充血模型,而Model是从JPARepository中取得的,但JPARepository在基础架构层,而Model在领域层,这样层间的引用关系岂不是反过来了吗?虽然书上有说依赖倒置原则,但那是对接口而言,对于我这种情况肯定不是依赖倒置的问题啊。

4.在一本书里看到对于Repository的描述,其中一种是类似Hibernate那种,对于Hibernate我没用过太多,对它的理解也不是很透彻,但通过书中的描述我理解是通过Repository取出的Model后,其Model对象本身就能直接操作数据库,也就是对对象的任何修改都能直接更新数据库,不需要显示的再次调用Repository的save方法,与之对应的另一种模式就是显示调用Repository的save方法才能将Model的改动持久化到数据库中,那么无论采用那种模式,从这段描述中我觉得领域驱动中的Repository越来越像DAO了,如果我的理解不对,请道友指正。

5.最后一个问题是与我目前的项目相关的,根据六边形法则,接口层应该放置不同类型用户访问的接口,我的项目有两个接口,一个HTTP接口,提供Restful API给移动设备使用,另一个是Socket接口,提供字符流给一些单片机设备使用。Socket接口使用Netty组件来实现,Netty组件里已经封装了很多Socket的东西,但业务相关的东西还是需要自己来实现,但哪些是我需要放在基础架构层的,哪些是需要放在接口层的呢?我以前的做法是将关于Socket接口的全部代码都放在基础架构层,但看了六边形法则后我觉得Socket也是一种接口,只不过面向的不是移动设备而是单片机设备而已,那么问题是将哪一部分拿到接口层呢,目前我是将自己的业务实现写在继承ChannelDuplexHandler的类中,那么我感觉应该把关于业务实现的类拿到接口层中,不知道我想的对不对,请道友帮忙解惑。

以上是我在应用领域驱动设计与开发过程中遇到的问题,希望道友不吝赐教,谢谢。

1
banq
2016-06-06 12:39
DAO是数据访问对象(Data Access Object),而Repository是对象仓储,实际是数据库数据Data和内存对象Object不同侧重点的称谓,这种侧重点不同实际是鸡生蛋还是蛋生鸡的问题。

DAO是侧重数据库数据Data,内存中对象是为数据库数据服务的,这个对象根据数据表数据可以任意合成任意对象,有时一个数据表会合成很多DAO对象,尽管这些DAO对象之间可能就相差几个字段。因此, DAO是先有数据表Schema结构设计以后,再有Java等语言的类或对象。

而Repository正好相反,是侧重内存中对象,这个仓储是用来储备什么的呢?储备对象的,储备哪儿去?储备到数据库中,数据库类似仓储中的一个存储地点,可以有很多其他存储地点。那么被储备的对象是从哪儿来的呢?是从需求业务分析而来的。这个对象成为领域模型对象,因此,这个对象不像DAO那样是随意任意构建的,而是代表业务模型,数据库表结构也必须服从业务模型。

所以,是先有业务模型还是先有数据表结构,决定DAO和Respository侧重点不同。如同先有鸡或先有蛋,那么世界观就会不同一样。

具体实现时DAO和Repository的不同可以参考:http://www.jdon.com/48016,名词虽然不同,但是都是在谈论这块。

如果你确定先有业务模型,那么聚合对象就容易理解,聚合对象是一群业务模型,比汽车是一个聚合对象,其有车轮 发动机 车身等众多子对象组成。

如果你确定先有业务模型,那么就知道Model和Entity是否是一个东西,Model是业务模型,而Entity是数据库中实体数据在内存中的对象。Model是一个代表业务模型对象,真正对象是应该有行为的,而Entity只是数据表数据的影子,当然不需要行为,是贫血模型,这些可查看以前帖子讨论。

关于socket将业务层和基础层分离思路是对的,接口层需要想象成是一个interface,如同Java的接口代码,很少很薄,只是个面子,因此接口层不能放入太多代码。总之这些都根据一些基本设计原则去考虑。

smcdl
2016-06-06 13:45
感谢板桥您的答复,但我还有不明白的地方,请您帮我解惑。

那么对于Model与Entity、Repository与DAO,我是不是可以这样理解,DAO是针对数据库表进行操作,他操纵的对象是Entity,而Repository是针对领域模型对象的操作,他操纵的对象是Model,如果从数据库中取出一个领域对象,需要通过Repository来操纵DAO来从数据库取出Entity对象,然后再通过相应的Factory等设施构造Model对象并用Entity对象的值来填充Model对象,目的是封装与领域无关的底层数据存储功能,让调用Repository的上层无需关心领域对象的是如何持久化的。

另外问一下Spring里的JPARepository是不是就是DAO模式,虽然它带有Repository的字样。

对于聚合的例子,我一直没有直观的概念,一个Model对象可以是一个聚合根吗?还是说聚合根是一个独特的对象,从聚合根里面取出Model对象。就像UML中的Stereotype那样,聚合根对象是否需要继承什么类或者实现什么接口声明自己是聚合类型,还是仅仅暴露一些方法针对包含的部件集合进行增删改就可以了呢?

[该贴被smcdl于2016-06-06 13:52修改过]

banq
2016-06-06 14:04
2016-06-06 13:45 "@smcdl"的内容
那么对于Model与Entity、Repository与DAO ...

不是你说的意思,你说的已经进入操作层面。

假设我们要做一个论坛系统,怎么做?首先做什么?有两种:

A.如果首先设计数据表结构,比如Forum和Post两个表。

B.如果DDD,那么设计Forum和Post两个领域模型Model对象,可以用UML类图表达,然后写出Java或其他类的代码。

如果先从A开始做,那么无疑使用Entity表达Forum和Post,有两个实体对象,对应两张表。至于DAO就是用来进行对象和表之间转换的持久层。

如果先从B开始做,那么使用Repository将Forum和Post两个领域对象进行持久,这时需要确定持久到哪里,如果持久到数据表,再设计数据表结构,或者使用Hibernate自动生成数据表。

所以,先有鸡和先有蛋决定了后面前后顺序不一样。理解上面区别,你对聚合就会了解,因为我们直接使用模型对象反映需求,那么Car这个对象反映了业务世界汽车,而汽车是由车轮 方向盘 马达等组成,对应我们的Car模型对象由Wheel、Engine 等对象组成。类的代码如下:

public class Car{
   private Wheel wheel;
   private Engine engine;

   ..
}
<p>

上面代码就表示汽车由方向盘和马达组成,我们就直接用类表达了业务需求,这时不会掺入任何数据表的事情。这时的汽车由于聚合了几个子对象,称为聚合根,如同小组长一样。

而如果首先从数据表开始做系统,则会设计出数据表结构:

CREATE TABLE car(
  carID       BIGINT NOT NULL,
  name          VARCHAR(100) NOT NULL,
  PRIMARY KEY   (carID)
);

CREATE TABLE wheel(
  wheelID       BIGINT NOT NULL,
  name          VARCHAR(100) NOT NULL,
  PRIMARY KEY   (wheelID)
);

CREATE TABLE engine(
  engineID       BIGINT NOT NULL,
  name          VARCHAR(100) NOT NULL,
  PRIMARY KEY   (engineD)
);

<p>

上面是设计好MySQL数据表,最多在wheel和engine中设计一个外键指向carID,这就是用表关联表达汽车和方向盘与马达的关系。当然这种表达有很多缺点,不能表达很紧密的关系,有可能把很多没有紧密关系的表关联在一起,导致一个数据表更新带动数十个更新,性能低下。等等。

上面就是先有鸡和先有蛋导致的不同分析设计实现系统。两者系统当然先有数据表入手简单,但是随着系统复杂维护性下降;而先有领域模型对象的设计入手复杂一点,但是对于复杂系统有好处,因为它不怕系统复杂,随着系统规模扩展,开发效率不会降低。

smcdl
2016-06-06 14:14
谢谢您的回复,我对领域驱动设计理解得不够透彻,所以就像我之前回复的,如果就是进入到操作层面,那么通常的操作流程是像我描述的那样吗?对于聚合我产生了另一个问题,无论是Hibernate还是JPA这类ORM都有一对多多对多等关系,可以从数据库中直接拿出一个对象树出来,那么这个根对象还是Entity而不是Model对吗?还是需要在领域仓储(Repository)中转换成对应的Model对吗?

另外,领域驱动设计里所说的领域层中包含的“实体”,无论从哪个方面来看都很像ORM中的与数据表对应的对象,那么这个“实体”到底是Model呢还是Entity呢?

[该贴被smcdl于2016-06-06 14:24修改过]

[该贴被smcdl于2016-06-06 14:25修改过]

smcdl
2016-06-06 14:50
谢谢您的回复,我对领域驱动设计理解得不够透彻,所以就像我之前回复的,如果就是进入到操作层面,那么通常的操作流程是像我描述的那样吗?对于聚合我产生了另一个问题,无论是Hibernate还是JPA这类ORM都有一对多多对多等关系,可以从数据库中直接拿出一个对象树出来,那么这个根对象还是Entity而不是Model对吗?还是需要在领域仓储(Repository)中转换成对应的Model对吗?

另外,领域驱动设计里所说的领域层中包含的“实体”,无论从哪个方面来看都很像ORM中的与数据表对应的对象,那么这个“实体”到底是Model呢还是Entity呢?

还有,《实现领域驱动设计》一书中所说的Repository中的第一种形式,集合模式(好像是这个名称吧),越看越像是在描述Hibernate的实现,这两个

banq
2016-06-06 15:00
2016-06-06 14:14 "@smcdl"的内容
无论是Hibernate还是JPA这类ORM都有一对多多对多等关系,可以从数据库中直接拿出一个对象树出来,那么这个根对象还是Entity而不是Model对吗?还是需要在领域仓储(Repository)中转换成对应的Model对吗?

另外,领 ...

我认为你这两个问题还是因为你没有理解我前面讲的意思,你需要冷静仔细想一想,如同唯物主义和唯心主义两者因为思考角度不同导致操作层面不同。

DDD因为我们先有模型类对象,而且聚合表达了一对多的关系,比如前面Car这个模型对象,那么再具体操作层面,无论使用Hibernate的ORM或JPA,它们都是将我们的Car聚合映射到数据表中,包括一对多关系,什么是ORM?首先O在前面,也就是Object在前面,Hibernate是冬眠的意思,谁冬眠?是对象Object冬眠。

而你讲:”无论是Hibernate还是JPA这类ORM都有一对多多对多等关系,可以从数据库中直接拿出一个对象树出来,“

这段话的言下之意的前提是:你先有了数据库,然后从数据库通过ORM或JPA直接拿出一个对象树出来,而我们DDD是先有了对象树,通过ORM或JPA生成了一系列关联的数据表。

下面你接着的问题:“那么这个根对象还是Entity而不是Model对吗?还是需要在领域仓储(Repository)中转换成对应的Model对吗?”

如果按照DDD来讲,你从数据库直接拿出一个对象树这种做法就是错误的,就不是DDD,如果按照DDD,应该先有对象树,然后通过Repository转换成其他格式,比如XML或JSON或数据表结构,进行持久化。所以,你这第二个问题就很难回答,如果你不按照DDD,还是传统做法,先有数据库,那么就用不到Respository这个概念,因为仓储Respository这个概念是领域仓储,是领域对象的仓储。

这个先后顺序你难道还没有搞明白吗?

你的另外一个问题:“另外,领域驱动设计里所说的领域层中包含的“实体”,无论从哪个方面来看都很像ORM中的与数据表对应的对象,那么这个“实体”到底是Model呢还是Entity呢?”

DDD中的实体是实体对象,是业务实体,而ORM中与数据表对应的实体也称为实体,但不一定是业务实体,而是数据表实体,实体的“实”的意思是“包含实在的数据”的物体;而DDD实体是指有“唯一标识”且在客观世界持续存在的物体。这两个都使用了实体,但是实体含义不同。

至于这个实体是否Model或Entity?这个问题不重要,我们这里的Model是指Domain Model,不是Data Model,如果谈论Data Model,就是默认以数据库为核心的系统,或者以数据库为驱动的系统,也就是先有数据库的系统;而Domain Model则是相反,是以模型对象为核心的系统,不是以数据库为驱动的系统。所以,关键你的这个Model是指哪个Model?还是你这个Entity是指哪个Entity,Entity就是实体的英文。所以,Model有两种,Entity实体也有两种意思。

smcdl
2016-06-06 15:13
2016-06-06 15:00 "@banq"的内容
我认为你这两个问题还是因为你没有理解我前面讲的意思,你需要冷静仔细想一想,如同唯物主义和唯心主义两者因为思考角度不同导致操作层面不同。

DDD因为我们先有模型类对象,而且聚合表达了一对多的关系,比如前面Car这个模型对象,那么再具体操作层 ...

我是非常想从领域的角度去构建我的系统,这是我学习领域驱动的目的所在。但就像您说的,就算是领域对象最终也要持久化到数据库中,当然持久化的方式不一定仅仅是数据库,但目前大多数的项目都是使用数据库作为持久化的一种媒介,这里就假设我们使用数据库作为最终持久化,那么既然领域对象能存到数据库,那么就能被取出来,我之前一直在谈取的过程,您认为我的没有理解您的意思,那么我们就谈谈存的过程,一般来说将另一领域对象存到存到数据库中我们都需要使用Repository的save来操作吧,如果是单一一个领域对象,那么只存它自己就OK了,如果是聚合根(我可能把它理解为对象树吧),那么还要把他的组件(或者部件)取出逐一存到数据库,那么就需要在一个Repository中调用其他Repository的save方法来实现对吧,那么在Repository的save方法里是不是要把领域对象(即Model)转换为数据库的实体对象(即Entity),然后调用DAO的insert或者update来保存呢?

另外,即使我从数据库里取也不意味着先有数据库,我也是先设计领域模型,然后从领域模型之间的关系着手设计数据库结构,我的数据库结构是手动设计的,不是通过Hibernate之类的工具自动生成的,即使这样也不能说我是先有数据库吧。

[该贴被smcdl于2016-06-06 15:22修改过]

banq
2016-06-06 15:48
现在感觉你的思路已经清晰了,非常棒,再给你挑个小刺:

“那么我们就谈谈存的过程,一般来说将另一领域对象存到存到数据库中我们都需要使用Repository的save来操作吧,如果是单一一个领域对象,那么只存它自己就OK了,如果是聚合根(我可能把它理解为对象树吧),那么还要把他的组件(或者部件)取出逐一存到数据库”

我们以Insert或Update分开看吧:

1. Insert操作:如果先有对象树,而且我们是创建这个对象树的数据,是从前台表单输入的,数据库里面肯定还不存在,比如carId=123的这条代表Car聚合根的记录肯定在数据库中不存在,而聚合根是和对象树中所有对象同生死,也就是生命周期是一致的,因此,对应carId=123的Car中wheel或engine记录也不存在,因此不存在从其他数据表取出这个考虑。当然你可能有其他想法。

2.Update操作:更新Car对象树之前,首先确实需要从数据库中取出Car的对象树,使用工厂构建Car这个领域模型对象,因为Car对象群内生命周期是一致的,因此Car取出同时,wheel和engine也取出,比如carId=123的三个对象同时取出,如果有巨大子对象,就不能这么做。当前端用户更新Car代表这个对象树时,更新好后,只要保持内存中对象树和数据库的car几个表数据一致就可以了,这有很多方式,比如采取缓存数据表同步方式,可以使用事件驱动更新,或者先更新数据库记录,再用Factory重新构建获得新的Car对象群。

关于先有领域模型对象,数据表如何生成,采取Hibernate自动也可以,采取手工也可以,采取关系数据库也可以,采取NoSQL等也可以,其实采取NoSQL更好,因为少了关系数据库与对象之间的严重不匹配,比如使用Key-value这样NoSQL就很好,Key是CarId,Value就是Car这个对象树的序列化(也就是直接将对象用二进制保存,类似Blob类型)。

smcdl
2016-06-06 16:02
2016-06-06 15:48 "@banq"的内容
现在感觉你的思路已经清晰了,非常棒,再给你挑个小刺:

“那么我们就谈谈存的过程,一般来说将另一领域对象存到存到数据库中我们都需要使用Repository的save来操作吧,如果是单一一个领域对象,那么只存它自己就OK了,如果是聚合根(我可 ...

板桥您好,我想回到我之前的问题,就是无论存与取,是不是我说的那种套路,即保存领域对象的时候是在Repository中将Model转换为Entity并使用Dao保存Entity,反之从数据库中获取领域对象的时候也是在Repository中调用Dao查询Entity,然后将Entity转换为Model返回给调用方呢?

banq
2016-06-06 16:23
2016-06-06 16:02 "@smcdl"的内容
即保存领域对象的时候是在Repository中将Model转换为Entity并使用Dao保存Entity,反之从数据库中获取领域对象的时候也是在Repository中调用Dao查询Entity,然后将Entity转换为Model返回给调用方 ...

是的,如果不这样做,还有其他办法吗?这里实际是完成对象与数据表的转换翻译工作,是琐碎的脏活,也不应该是我们关注的重点,因此不是问题重心。

如果你觉得Dao比较累赘,可以在Repository中将Model直接保存数据库,因为数据库本来是服从Model的。因此,也可以是这样答案:

保存领域对象的时候是在Repository中将Model保存,反之从数据库中获取领域对象的时候也是在Repository中查询Model返回给调用方.

上面这个答案和你需要确认的都可以,只是麻烦不同而已。

smcdl
2016-06-06 21:55
2016-06-06 16:23 "@banq"的内容
是的,如果不这样做,还有其他办法吗?这里实际是完成对象与数据表的转换翻译工作,是琐碎的脏活,也不应该是我们关注的重点,因此不是问题重心。

...

谢谢您一直耐心的为我解惑,我还有一个问题是Model和Entity可以不可以是一个对象呢?看了《实现领域驱动设计》这本书,其译者滕云在infoq上发表一篇文章里面提到仓储Repository,原话是这样说的“资源库用于保存和获取聚合对象,在这一点上,资源库与DAO多少有些相似之处。但是,资源库和DAO是存在显著区别的。DAO只是对数据库的一层很薄的封装,而资源库则更加具有领域特征。另外,所有的实体都可以有相应的DAO,但并不是所有的实体都有资源库,只有聚合才有相应的资源库。”这句话是什么意思呢?难道非聚合的领域对象都是从DAO中直接取出的吗?

because
2016-09-21 15:31
看了两位的讨论,我也有些疑惑,Model和Entity的关系到底是怎样的?如果Entity是真实世界的对象,那Model是不是可以理解是真实对象的抽象?拿Car来举例,那Car到底应该是Model还是Entity?如果Car是Model的话,是不是Car=123是一个Entity?那么Wheel和Engine呢,是不是Entity?那么Entity是不是还是贫血的?它和VO的区别是不是Entity只存在于Repositoty中,而VO哪都可以用?

猜你喜欢