这是一个由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. 《软件测试的艺术(原书第2版)》【PDF】下载

    <软件测试的艺术(原书第2版)>[PDF]下载链接: https://u253469.ctfile.com/fs/253469-231196343 内容简介 本书以一次自评价测试开篇,从软 ...

  2. 对Java中多态,封装,继承的认识(重要)

                                                            一.Java面向对象编程有三大特性:封装,继承,多态 在了解多态之前我觉得应该先了解一下 ...

  3. 【java】Date与String之间的转换及Calendar类:java.text.SimpleDateFormat、public Date parse(String source) throws ParseException和public final String format(Date date)

    package 日期日历类; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util. ...

  4. 二叉树Bynary_Tree(2):二叉树的递归遍历

    7/18/2017 正在码,理解完就写博 7/21/2017 终于码完,借助链表实现的队列撸一个二叉树对于我来说有难度,撸了两天没有成果,我最后选用的是数组实现的循环队列去撸一个二叉树 正文如下: 以 ...

  5. iOS 面试题、知识点 之一

    最近面试,发现这些题个人遇到的几率大一些,与大家分享一下,分三文给大家: 当然Xcode新版本与之前一版本的区别,以及iOS新特性是必要了解的吧. Xcode8 和iOS 10 在之前文章有发过,感兴 ...

  6. 首次在C#程序中用log4net

    众所周知log4net是一个很强大的日志管理库,我自己也用了下,这里作下记录: 首先新建一个项目Log4NetTest,然后将log4net.dll程序集添加引用至Log4NetTest. 然后在Lo ...

  7. Handler的解析和使用

    1.handler为android中多线程间通信的一种机制, @1android中只允许在UI线程(主线程)操作或改变UI,其他线程不能操作UI. @2其他线程有刷新UI的需要,所以得告诉UI线程,这 ...

  8. Udacity并行计算课程笔记-The GPU Hardware and Parallel Communication Patterns

    本小节笔记大纲: 1.Communication patterns gather,scatter,stencil,transpose 2.GPU hardware & Programming ...

  9. Android 再按一次退出程序三种办法

    在Xamarin android中双击返回键退出程序的第一种做法 思路就是当用户按下返回键的时间超过两秒就退出,根据Keycode.Back判断用户按下的是返回键,重写这个OnKeyDown Date ...

  10. js知识点记录

    1.for...in:用于遍历数组或对象的属性. for(var prop in obj){console.log(obj[prop])}; 该循环遍历对于对象属性是无序的,可能是因为obj本来就是无 ...