在比较早期的软件程序设计中,或者说,当软件需求十分简单的时候。我们往往会按照如下的方式进行设计:
这样的操作流程是直观的,往往在进行第二步库表设计的时候,对应数据库字段中的内容就是需要展示的“列表”、“详情页“或者”表单“等内容。
但是在这样的流程操作加,我们实际上是有两次对需求中的业务数据进行了两次全量分析,第一次是在进行数据库设计的时候,而第二次是在进行程序设计的时候。两次对需求的整理会导致工作量的增加,使得开发延缓。
尽管如此,由于简单项目的时候这个方法工作量的增加并不明显,所以很难直观地说这种方法有多大的问题。
当我们使用领域模型设计的时候,则是如下流程:
简单的从流程上看,我们仅用在进行领域建模的时候需要对这对需求进行数据。而这之后需求就被抽象称为领域模型了,所以之后的程序设计和数据库设计就只用基于领域模型进行设计就可以了。而领域模型一般用类图的设计,所以其中类的属性可以指导数据库中需要的字段,又可以通过类中的方法指导程序设计。
这时候我们发现,数据库的设计仅由领域模型指导,从而数据库设计就弱化为了领域对象的持久化设计。
那么什么叫做领域对象持久化呢?
显然在领域模型设计中,对领域的增删改即对领域对象的增删改。如果不考虑数据丢失的问题,则领域对象完全可以在内存中存放。而如果考虑到持久化的问题,可以将暂时不用的数据拆解为一个或多个数据库表进行存储,当需要使用的时候再从数据库中将领域对象查询并组装起来。
所以,作为领域对象,其实是不关心具体的数据库持久化实现的。及时重新的对表进行拆分,只要可以还原出来领域对象,则对于上层业务是无感知的。
所以,在领域驱动设计中,数据库设计变成了 如何将领域模型转化为数据库设计的过程。
从工程上来说,实际上就是类似转化为表的过程。
所谓类到表的转化,我们除了需要处理类中的属性外,还要处理类之间的关系。
实际上,类间的关系与传统的表间的关系有相同的 4 种传统关系,分别是:
由于这 4 种关系来表和类中都存在。所以我们可以直接根据表的设计规则来进行转化,常用的方法为:
对于类来说,存在类间的继承关系,而这种关系在关系型数据库中没有直接的对应关系。所以一般来说,我们有三种方式来描述领域模型间的继承关系:
单独一张表包含所有父类、子类的全部属性:
这种方案的优点就是简单。实现方案为将所有对象的字段类型都一股脑存放在一张表里面。缺点是容易造成表稀疏(在关系型数据库中存在大量空字段)。
由于会浪费空间,如果子类比较多,或者子类个性化字段比较多的情况下就不适合这种方案的。
每个子类一张表:
第二种方案是一个子类新建一张表,子类前一段是父类的字段,后面是自己个性化的字段。需要注意的是,所有子表需要使用同一个主键生成器,要保证各个子表的主键不能冲突。
如果业务大部分是根据单个子类类型来进行查询展示,则可以使用这种方案。但如果业务需求是需要查询所有类型,就需要遍历所有的表,这种方式效率较低,这种方案就不适用。
父类一张表,每个子类一张表:
父表中记录所有的公共信息以及被检索信息,而子表中记录各个子类的特别字段。当进行数据查询的时候,只需要根据父类表进行数据过滤,然后在根据个数据的主键与类型字段查询对应子表中的详情数据,聚合成领域对象再返回。
但是这方法只能支持父类中的字段作为检索条件,如果需要对子表中的详情字段进行搜索,则该模式会比需要 join 方法,比第二种慢很多。
总的来说在类继承关系的持久化中,没有一种确定的方案,需要根据实际的业务场景进行选择。
上文中我们聊的都是关系型数据库。而关系型数据库中为了大幅度减小数据的冗余,在数据库设计的时候我们会遵循第三范式的思想。但对空间进行这样优化后,使得我们在查询联合数据的时候需要频繁使用 join 操作,从而产生高并发时的性能瓶颈。
由于领域建模设计的单元是类,同时也是对象,如果如果是使用 NoSQL 数据库来直接对整个对象进行存储,则就相当于在查询数据前完成了“join”操作,直接将对象存入到数据库中。当需要对数据查询时,就不需要再“join”而能直接根据需要检索单表中的字段。
同时,由于 NoSQL 的非结构化特性,使得即便像上文所提到的“继承关系第一种建表方式”那样将多个子类合并到一张表,也不会出现表“稀疏”,从而影响存储与查询性能。
所以,NoSQL 数据库的主要思想是将对象直接存入单表,从而避免查询数据时的 join 操作。
最常用的就还是 Mongo 数据库来。
我们在进行完毕领域建模后,进行数据库设计的时候,可以直接参考模型类的关联关系。如果是传统关系则可以直接映射,如果是继承关系则需要考虑一下业务场景。但是对于聚合根,也可以直接存储为 NoSql 数据,减少了查询聚合时的 join 次数,从而减少了数据库压力。
需要特别注意的是,领域模型只是指导数据库设计,实际库表可能和类并不是一一对应。
领取专属 10元无门槛券
私享最新 技术干货