这是一个由DDD群引发的随笔

在写了上一篇随笔《关于ORM的浴室沉思》后一些朋友私聊我,很多刚接触DDD的朋友会对Repository(仓储层)这东西有点疑惑,为什么要叫仓储层?是不是三层的DAL换个名字而已?毕竟大家都是对数据库的操作嘛,而且我用EF还有必要用仓储么?是不是可以省掉这一层?ABP框架的持久化究竟有什么问题?

在回答这些问题之前,我们要先了解一些模型分层的概念。

DO

几乎每位程序员在刚入门时都以三层架构(3-tier architecture)为基础,界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)首次让我们在架构级别了解高内聚低耦合的概念。其中对数据访问层也即是DAL的定义是——屏蔽对“数据文件”的操作,这里的数据文件指的是数据库以及其它持久化实现。

扩展阅读:持久化不一定是数据库,直接写个excel甚至txt也算持久化。例如rabbitmq或者redis就是通过对文件的读写来实现。追根溯源,其实数据库也是如此,例如SQL server的mdf文件。

在最简单的三层中,我们一般有一个models层或者entities层贯通三层用于数据的传递(为了便于描述后文统一叫entities层)。在界面层,entity用于显示数据或者作为view object的转换源;在业务逻辑层,和业务代码组合起来完成业务逻辑的实现;在数据访问层则作为被持久化的对象保存到数据库里。

不管怎么分,即便是后来的MVC,在很长一段时间的绝大部分项目里其实都以这种逻辑来分层,但到了DDD中就遇到了问题,因为DDD的核心模型是DO,而且DO是充血模型。

扩展阅读:所谓充血模型,实际上就是包含了业务逻辑的对象。回想一下面向对象的定义,对象是包含“状态”(属性)以及“行为”(方法/函数)的,其实这很容易理解——贫血模型实际上就是数据的容器,它不是“活的”对象,在任何地方都可以对其进行修改,这实际上违反了高内聚的原则。打个比方:我们现在有个程序员的对象,他要减肥,那么我们要通过调用“健身”这个方法将其Weight属性逐渐降下来,而不是直接在外面set他的体重。

贫血模型大行其道有其历史原因,最早的EJB就是将业务对象分为属性和业务方法两部分,然后spring延续了下来,但spring他爹也说:这实际上是一种面向过程的做法。在这里我们暂时不展开充血模型和贫血模型的讨论。

在DDD的模型划分中,最核心的部分就是DO(Domain Object领域对象)。DO是依据不变性划分出来的业务对象,其属性是public get protected set的,只能通过DO本身的方法来进行操作。举个简单的不严谨的例子,例如520网购,你在帮女盆友清空购物车的时候一般生成一个订单对象,其包含了子订单(例如口红、神仙水),我们大概地将DO设计如下:

pubic class Order
{
public string OrderId {public get; protected set;}
public Status Status {public get; protected set;}
public DateTime CreateTime {public get; protected set;} = Datetime.Now;
public DateTime? PayTime {public get; protected set;}
public DateTime? CommitTime {public get; protected set;}
public List<OrderItem> Items {public get; protected set;} public void Pay(decimal money)
{
if(Items?.Count == 0)
throw new exception ("请选择商品。");
var totalPrice = Items.Sum(item => item.UnitPrice * item Quantity);
if(money < totalPrice)
throw new exception ("余额不足。")
PayTime = DateTime.Now;
Status = OrderStatus.已支付;
} public void Commit()
{
if(Status != OrderStatus.已支付)
throw new exception ("请先支付。");
CommitTime = DateTime.Now;
Status = OrderStatus.已确认;
}
} public class OrderItem
{
public Guid Id {public get; protected set;}
public string Sku {public get; protected set;}
public decimal UnitPrice {public get; protected set;}
public int Quantity {public get; protected set;}
} public enum OrderStatus
{
待支付,
已支付,
已确认,
.....
}

在这个例子中我们可以条件反射般地想到,RDB中有两张表Order和OrderItem,这两张表是一对多的关系。假如我们用的是DAL,那么可能有个OrderDal和OrderItemDal来将数据分别持久化到RDB中,更严谨些的话还会将它们放到一个事务里执行。当然也可以有个泛型的DBHelper将Order和OrderItem分别持久化。

在这里DAL接收的参数是Order和OrderItem,它封装了对RDB的操作,让我们可以用更友好的方式来使用RDB。但这里我们要思考一个问题:所谓持久化,我们要持久化的是什么?

PO(Persistent Object/持久化对象)

实际上我们持久化的并不是DO,而是DO的“状态”。例如你吃饭时被人偷拍,照片会将你吃饭时的样子记录下来,但吃饭这种“行为”本身无法持久化,如上面的Pay和Commit两个方法。另外DO的一个原则是“原子性”,也就是说拿就整个拿,存就整个存,如果是用仓储层来持久化,则只会有一个OrderRepository,将整个Order作为参数丢进去,仓储层内则将Order和OrderItem的状态部分转为OrderPo和OrderItemPo两种贫血对象,并用之持久化。

重点:只有聚合才有仓储

到了这里我们有了一个初步的概念:DAL是对数据文件操作的封装,不管是否实现了泛型与表的转换,它都是以数据文件为中心,在使用的时候其实我们是以一种面向数据库编程的思维来进行操作;仓储层则是反过来为领域层服务,领域层需要什么它才提供什么,屏蔽掉底层持久化的具体实现。

我们已经用了ORM(EF)了,还有必要用仓储层么?

其实还是要的,上一篇随笔《关于ORM的浴室沉思》说明了ORM的概念,但遗憾的是现在所有ORM实际上都没有彻底解决阻抗失配的问题。

另外DO是原子性的,因此只有聚合才有仓储,上述例子OrderItem不属于聚合,它是没有仓储的。假如直接用EF的话代码是可以直接访问到DbContext<OrderItem>,这就对代码造成了隐患。

而且直接使用EF的话,所有的业务代码将会对EF造成高度耦合,这会造成潜在的技术风险。假如我们使用的是仓储层,到时候只需要在仓储层捣鼓就行。实际上DDD项目中,ORM反而不是必要的东西。

所以ABP的问题是?

ABP是一个很好的框架,土牛的技术水平也很高,但不能说ABP就是DDD的标准答案,例如其仓储层实际上并不是服务于DO而是PO。因此有种说法ABP不是DDD而是DDD LITE,这种说法有其原因。另外仓储层严谨地说不应该提供IQueryable,而应该是Command端需要什么的获取方法才提供,否则会无法进行单元测试。

至于Query端,怎么快怎么来,管它呢~

浴室沉思:聊聊DAL和Repository的更多相关文章

  1. 从Entity Framework的实现方式来看DDD中的repository仓储模式运用

    一:最普通的数据库操作 static void Main(string[] args) { using (SchoolDBEntities db = new SchoolDBEntities()) { ...

  2. DIV遮罩层传值

    今天费了很大的劲儿才搞定!下面贴出代码和总结: 1.首先是前台代码: <%@ Page Title="" Language="C#" MasterPage ...

  3. 重温ASP.NET WebAPI(二)进阶

    重温ASP.NET WebAPI(二)进阶   介绍 本文为个人对WebApi的回顾无参考价值. 本文内容: Rest和UnitOfWork 创建WebAPi的流程 IOC-Unity的使用 MEF ...

  4. [切图仔救赎]炒冷饭--在线手撸vue2响应式原理

    --图片来源vue2.6正式版本(代号:超时空要塞)发布时,尤雨溪推送配图. 前言 其实这个冷饭我并不想炒,毕竟vue3马上都要出来.我还在这里炒冷饭,那明显就是搞事情. 起因: 作为切图仔搬砖汪,长 ...

  5. Asp.Net Core + Dapper + Repository 模式 + TDD 学习笔记

    0x00 前言 之前一直使用的是 EF ,做了一个简单的小项目后发现 EF 的表现并不是很好,就比如联表查询,因为现在的 EF Core 也没有啥好用的分析工具,所以也不知道该怎么写 Linq 生成出 ...

  6. 初探领域驱动设计(2)Repository在DDD中的应用

    概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...

  7. Repository 仓储,你的归宿究竟在哪?(三)-SELECT 某某某。。。

    写在前面 首先,本篇博文主要包含两个主题: 领域服务中使用仓储 SELECT 某某某(有点晕?请看下面.) 上一篇:Repository 仓储,你的归宿究竟在哪?(二)-这样的应用层代码,你能接受吗? ...

  8. ASP.NET MVC5+EF6+EasyUI 后台管理系统(58)-DAL层重构

    系列目录 前言:这是对本文系统一次重要的革新,很久就想要重构数据访问层了,数据访问层重复代码太多.主要集中增删该查每个模块都有,所以本次是为封装相同接口方法 如果你想了解怎么重构普通的接口DAL层请查 ...

  9. 帮助对@Repository注解的理解

    定义(来自Martin Fowler的<企业应用架构模式>): Mediates between the domain and data mapping layers using a co ...

随机推荐

  1. iOS学习——内存泄漏检查及原因分析

    项目的代码很多,前两天老大突然跟我说项目中某一个ViewController的dealloc()方法没有被调用,存在内存泄漏问题,需要排查原因,解决内存泄漏问题.由于刚加入项目组不久,对出问题的模块的 ...

  2. 创建一个可用的简单的SpringMVC项目,图文并茂

    转载麻烦注明下来源:http://www.cnblogs.com/silentdoer/articles/7134332.html,谢谢. 最近在自学SpringMVC,百度了很多资料都是比较老的,而 ...

  3. 初探XRebel

    一.什么是XRebel? 1.介绍 XRebel 是不间断运行在 web 应用的交互式分析器.可以看到网页上的每一个操作在前端以及服务端.数据库.网络传输都花费多少时间,当发现问题会在浏览器中显示警告 ...

  4. KD树小结

    很久之前我就想过怎么快速在二维平面上查找一个区域的信息,思考许久无果,只能想到几种优秀一点的暴力. Kd树就是干上面那件事的. 别的不多说,赶紧把自己的理解写下来,免得凉了. KD树的组成 以维护k维 ...

  5. window下mysql数据备份

    今天我有个朋友让我帮他在windowServer服务器上备份一下mysql的数据库,于是花了一天的时间完成了一个每天定时备份数据库的功能,小编在这里为大家记录一下: 首先对于mysql命令行的导入导出 ...

  6. 关于《Web接口开发与自动化测试--基于Python语言》

    关于封面logo 首先,你会被书封上面logo吸引,这么炫酷?双蛇杖?嗯,这是Requests的新logo. 旧的logo是一只乌龟. 新logo是双蛇杖: 看到新logo我首先想到的是 火爆全网页游 ...

  7. 从一个word文件中读取所有的表格和标题(1)

    首先讲需求: 从word文件中读表格里的数据,然后插入数据库中.word文件中的表格是带有标题的,把标题读出来,进行匹配数据库. 需求分析: word2007底层是以xml文件存储的,所以分析xml的 ...

  8. Linux(CentOS6.5)下编译Popt报错”GNU gettext is required. The latest version”(gettext已经编译安装,但是没有安装在默认目录)的解决方案

    本文地址http://comexchan.cnblogs.com/,作者Comex Chan,尊重知识产权,转载请注明出处,谢谢!   背景: 编译popt的时候出现下述报错. 直接vi查看confi ...

  9. 在linux环境下编译运行OpenCV程序的两种方法

    原来以为在Ubuntu下安装好了OpenCV之后,自己写个简单的程序应该很容易吧,但是呢,就是为了编译一个简单的显示图片的程序我都快被弄崩溃了. 在谷歌和上StackOverFlow查看相关问题解答之 ...

  10. K:java中的序列化与反序列化

    Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?以下内容将围绕这些问题进行展开讨论. Java序列化与反序列化 简单来说Java序列化是指把Java对象转 ...