概述

在实践领域驱动设计(DDD)的过程中,我们会根据项目的所在领域以及需求情况捕获出一定数量的领域对象。设计得足够好的领域对象便于我们更加透彻的理解业务,方便系统后期的扩展和维护,不至于随着需求的扩展和代码量的累积,系统逐渐演变为大泥球(Big Ball of Mud)。

虽然领域驱动设计的思想很诱人,但我们依然会面临各种隐藏的困难,就比如今天我们要讲的主题“持久化”:即使前期我们设计了足够完整的领域对象,但是依然需要持久化它们到数据库中,而普通的关系型数据库可能很难维持领域对象的原有结构,所以我们必须要使用一些特有的手段来处理它。

开篇

本篇文章属于《如何运用领域驱动设计》系列的一个补充,如果您阅读过该系列的其它文章,您就会发现关于“持久化”的这个问题已经不止在一篇博文中提及到了。

那么,到底是什么原因让我们面临这个问题呢? 是的!值对象! 如果您认真的了解过值对象的话(如果还不了解值对象,您可以参考 如何运用领域驱动设计 - 值对象),您会发现值对象是由许多基元类型构成的(比如string,int,double等),所以我们可以理解它为对细粒度基元类型的包裹,构成我们所在领域中的一个基础类型,比如说下面这个例子:

public sealed class City : ValueObject
{
public string Name { get; }
public int Population { get; } public City(string name, int population)
{
Name = name;
Population = population;
}
}

我们假设现在有一个叫做City的值对象,它是由名称(Name)和人口数量(Population)构成。通常我们这样建立值对象的原因很简单,在该领域中我们一联系到“人口”数量就会和“城市”连同在一起(你不会说我想知道人口数量,而你会说我想知道纽约的人口数量),所以“城市”这一概念成为我们该领域中的小颗粒对象,而该对象在代码实现中是由多个小基元类型构成的,比如该例子就是由一个string和一个int。

这样建模的好处之一就是我们考虑的问题是一个整体,将零碎的点构建为一个整体对象,如果该对象的行为需要发生改变,只需要修改该对象本身就可以了,而不是代码散落在各处需要到处查找(这也是滚成大泥球的原因之一)。

如果您喜欢捕猎有关DDD的知识,您可能不止一次会看到这样一条建议规则:

In the world of DDD, there’s a well-known guideline that you should prefer Value Objects over Entities where possible. If you see that a concept in your domain model doesn’t have its own identity, choose to treat that concept as a Value Object.

该建议的内容就是提倡DDD实践者多使用值对象。当然也不是说无论什么东西都建立成值对象,只是要我们多去发现领域中的值对象。

但是这往往给持久化带来了难度,先来想一下传统的编码持久化方式:一个对象(或者POCO)里面包含了各个基元类型的属性,当需要持久化时,每个属性都对应数据库的一个字段,而该对象就成为了一个表。 但是这在领域驱动设计中就不好使用了,值对象成了我们考虑问题的小颗粒,而它在代码中成了一个类,如果直接持久化它是什么样子呢?表,使用它的实体或者聚合根也是一个表,两个表通过主外键关系链接。

那么这样持久化方式好不好呢? 答案是不确定的,可能了解了下文的这些方案后,您会有自己的见解。

本篇文章的持久化方案都是基于关系型数据库,如果您是非关系型数据库(比如mongodb),那么您应该不会面临这样的问题。

字段 Or 表

将值对象持久化成字段好呢?还是将值对象持久化为表好呢? 这个问题其实也有很多广泛的讨论,就好比.NET好还是Java好(好吧,我php天下**),目前其实也没有个明确的结果:

  • 觉得持久化为表字段的原因是 如果持久化为表,必须给表添加一个ID供引用的实体或者聚合关联,这就不满足值对象不应该有ID的准则了。
  • 觉得持久化为表的原因是 数据表模型并不代表代码层面的模型,代码里面的值对象其实并没有ID的说法,所以它是符合值对象的,而持久化为字段的话,同一个值对象数据会被复制为多份导致数据冗余。

当然哈,各有各的道理,我们也不用特别偏向于使用哪个结论。应该站在客观的角度,实际的项目需要哪种手段就根据切实的情况来选择。

来说一下持久化为字段的情况

该手段其实在近期来说比较流行,特别是在EFCore2.0之后,为什么呢?因为EF Core2.0提供了一个叫做 从属实体类型 的概念,其实这个技术手段在EF中很早就有了,在EF中有一个叫做Complex的东西,只是在EF Core 1.x时代没有引入而已。

在EFCore引入了Owned之后,微软那个最著名的微服务教程 eShopOnContainers 也顺势推出了用于该特性来持久化值对象的方案:

所以这也是为什么大家都在使用Owned持久化值对象的原因。(当然,大家项目中只有Address被建立为值对象的习惯不知道是不是从这儿养成的

【DDD】持久化领域对象的方法实践的更多相关文章

  1. 我的“第一次”,就这样没了:DDD(领域驱动设计)理论结合实践

    写在前面 插一句:本人超爱落网-<平凡的世界>这一期,分享给大家. 阅读目录: 关于DDD 前期分析 框架搭建 代码实现 开源-发布 后记 第一次听你,清风吹送,田野短笛:第一次看你,半弯 ...

  2. DDD领域驱动设计和实践(转载)

    -->目录导航 一. DDD领域驱动设计介绍 1. 什么是领域驱动设计(DDD) 2. 领域驱动设计的特点 3. 如果不使用DDD? 4. 领域驱动设计的分层架构和构成要素 5. 事务脚本和领域 ...

  3. DDD(领域驱动设计)理论结合实践

    DDD(领域驱动设计)理论结合实践   写在前面 插一句:本人超爱落网-<平凡的世界>这一期,分享给大家. 阅读目录: 关于DDD 前期分析 框架搭建 代码实现 开源-发布 后记 第一次听 ...

  4. 【DDD】领域驱动设计实践 —— 框架实现

    本文主要了在社区服务系统(ECO)中基于SpringMVC+mybatis框架对DDD的落地实现.本文为系列文章中的其中一篇,其他内容可参考:通过业务系统的重构实践DDD. 框架实现图 该框架实现基本 ...

  5. 【DDD】领域驱动设计实践 —— Application层实现

    本文是DDD框架实现讲解的第二篇,主要介绍了DDD的Application层的实现,详细讲解了service.assemble的职责和实现.文末附有github地址.相比于<领域驱动设计> ...

  6. 【DDD】领域驱动设计实践 —— Domain层实现

    本文是DDD框架实现讲解的第三篇,主要介绍了DDD的Domain层的实现,详细讲解了entity.value object.domain event.domain service的职责,以及如何识别出 ...

  7. [Abp vNext 源码分析] - 5. DDD 的领域层支持(仓储、实体、值对象)

    一.简要介绍 ABP vNext 框架本身就是围绕着 DDD 理念进行设计的,所以在 DDD 里面我们能够见到的实体.仓储.值对象.领域服务,ABP vNext 框架都为我们进行了实现,这些基础设施都 ...

  8. 【DDD】领域驱动设计实践 —— UI层实现

    前面几篇blog主要介绍了DDD落地架构及业务建模战术,后续几篇blog会在此基础上,讲解具体的架构实现,通过完整代码demo的形式,更好地将DDD的落地方案呈现出来.本文是架构实现讲解的第一篇,主要 ...

  9. 一缕阳光:DDD(领域驱动设计)应对具体业务场景,如何聚焦 Domain Model(领域模型)?

    写在前面 阅读目录: 问题根源是什么? <领域驱动设计-软件核心复杂性应对之道>分层概念 Repository(仓储)职责所在? Domain Model(领域模型)重新设计 Domain ...

随机推荐

  1. oracle之简null空值问题,用nvl(a,b)函数解决

    oracle之简null空值问题,用nvl(a,b)函数解决 原文链接:https://blog.csdn.net/u013821825/article/details/48766749 oracle ...

  2. java 多线程安全问题的解决方法

    三种方法: 同步代码块: synchronized(obj) { //obj表示同步监视器,是同一个同步对象 /**..... TODO SOMETHING */ }   同步方法 格式: 在方法上加 ...

  3. linux如何查看nginx是否启动

    Nginx是一个高性能的反向代理服务器,现在一般作为我们网站或其他Web服务的第一层代理,用户在浏览器请求首先经过的就是Nginx服务. 如果Nginx服务没有启动或异常结束,将会影响Web服务的正常 ...

  4. java List接口中常用类

    Vector:线程安全,但速度慢,已被ArrayList替代. ArrayList:线程不安全,查询速度快. LinkedList:链表结构,增删速度快.取出List集合中元素的方式: get(int ...

  5. redis_Cacha 爬虫链接redis配置文件

    import redisimport json class RedisCache(object): """ 使用redis进行爬虫结果的缓存,并可以进行增量爬取 &quo ...

  6. 11-28-----vertor和list使用场景

    1.vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随机访问,而不子啊胡插入和删除的效率,使用vector, 2.list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关 ...

  7. MySQL排序问题

    直接上SQL语句: SELECT * FROM user_test ORDER BY user_name ,user_money DESC; #user_name中如果是数字会先排在前面,然后在按字母 ...

  8. vue-learning:34 - component - 内置组件 - 缓存组件keep-alive

    vue内置缓存组件keep-alive <keep-alive>标签内包裹的组件切换时会缓存组件实例,而不是销毁它们.避免多次加载相应的组件,减少性能消耗.并且当组件在 <keep- ...

  9. java打包上传服务器的一些命令

    Maven下package打包成jar包和war包,都在target目录下 其中War包扔在tomcat的webapps目录下.随tomcat启动自行启动 运行jar包命令. nohup java - ...

  10. Java虚拟机参数,增加虚拟机最大内存,在/etc/profile增加如下: export JAVA_OPTS="-Xms9g -Xmx9g"

    一.运行class文件 执行带main方法的class文件,Java虚拟机命令参数行为: java <CLASS文件名> 注意:CLASS文件名不要带文件后缀.class 例如: java ...