这是一个由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. nginx编译参数的内容

    最近公司安排我安装几台云服务器环境 采用nginx做反向代理: 查了一下官方文档,参数比较多,很多在上线后 可能才知道注意一下的. 编译安装nginx的话 需要安装一些前置组件: 1.gcc环境:用于 ...

  2. IT服务(运维)管理实施的几个要点--第一章 IT服务质量的标准

    子曰"干的最好就是个60分,稍有纰漏就是不及格" 谈一个事情,最先要谈的就是统一标准,又或者这个标准已经约定俗成,广泛认可,所以就可以略过.对于IT服务质量来说,确实有一个统一的标 ...

  3. 1.sass的安装,编译,还有风格

    1.安装sass 1.安装ruby 因为sass是用ruby语言写的,所以需要安装ruby环境 打开安装包去安装ruby,记住要勾选 下面选项来配置环境路径 [x] Add Ruby executab ...

  4. lesson - 7 课程笔记 vim

    vim :修改文件 模式: 默认进来是一般模式.i 编辑模式.esc 退出编辑 .shift+: 底行模式 参数: w: write/q:quit/! force 编辑模式:  /a:光标之后插入内容 ...

  5. 英雄联盟LOL用什么语言写的?

    是用openGL开发的 开发语言是c/c++ 客户端是一个.net的web界面

  6. Java 多线程笔记

    资料来源于网络,仅供参考学习.   1.A Java program ends when all its threads finish (more specifically, when all its ...

  7. 自动化测试辅助工具(Selenium IDE等)

    本随表目录 Selenium IDE安装和使用 FireBug安装和使用 FirePath安装和使用   Selenium IDE安装 方式一:打开Firefox-->添加组件-->搜索出 ...

  8. Oracle12c_安装2——安装篇

    安装分为图形安装,静默安装.推荐图形安装,出错率小,简洁明了. 1.安装vnc_server yum -y install vnc *vnc-server* 2.修改VNCServer主配置文件    ...

  9. vue监听scroll使用报错的解决办法

    错误说明:在切换路由以后,依旧在其他页面触发了scroll有关的函数, 错误原因:在spa项目中,window对象是不变的,所以每次使用后需要销毁. 解决办法:vue的生命周期destroyed中销毁 ...

  10. Asp.net IIS Express 无法启动 解决办法

    http://www.mamicode.com/info-detail-1893424.html 一 .其他项目都可以,就这么一个不行 用记事本或者其他什么文本编辑器,打开项目的.csproj文件,定 ...