Repository已经不是什么新鲜概念了。DDD模型自2004年提出,发展至今已经16年了。但是不少企业却无法实施,其原因也很简单:DDD是基于需求的,而很多并不理解需求;DDD是容易实现的,而很多设计者并不会编程。这种情况就有一些两头不讨好,而如果有办法结合统一的话,则会非常好用。

学习Repository的过程中,最先要进行的是思想的转变。在过往的编程过程中,大家往往将目光聚焦在CRUD中,导致每个程序员首先想的是我如何使用SQL实现我所要的目标。而在DDD过程中,实践者应当将目光聚焦中功能上,首先将需求分解为若干个功能,然后再将功能进行组合。

举例而言,某系统拥有组织机构和用户功能。组织机构树的每个部门下属若干个职位,每个职位都有用户担任,每个用户可以出任多个部门的多个职位。这时,系统设计师告诉你这里要有以下功能:

部门管理(部门的CRUD,部门下职位的CRUD,职位与部门的CRUD)

用户管理(用户的CRUD)

讲到这里,很容易看出,这里其实有四张表,也就是部门,职位,用户和用户职位关联表。表关系也容易理出,部门与职位1对多,职位与用户多对多。

如果你使用的是DAO思想(或者说分层思想),那么需求分析做到这一步也就结束了。你可以直接通过上述内容整理出你需要实现的接口,即每张表的CRUD。然后在前端实现一个界面,每个界面调用相关的接口程序也就写完了。比如,其中一个接口可能是这样的:

[Route(“~/api/SetPosition”)]

public void SetPosition(Guid userId, Guid positionId);

那么,现在问题来了。需求发生了一个变更,来了一个全新的需求,客户说我现在需求每个部门的更改必须通过流程进行。即当部门信息发生变更时,必须层层审核,最后才通过后,才能在更新数据。这个审核过程甚至包含了一部分关键职位的人员变化。

这时,那个坑人的系统设计师又站出来了,给了你一系列功能变化表:

部门修改申请(部门修改申请CRUD,部门申请审核,部门申请同步到部门表,部门申请同步到职位表)

看上去,这个设计很美好“自顶向下逐步细化”分解的也非常舒服。但是,你仔细研究一下就发现这里有两个巨大的坑:

1、新建部门修改申请。在部门修改申请时,试问是否要将以前的部门数据复制到这张申请表中?如果你不复制,那了不得了,全部门所有手动数据全部要用户自行输入此表,那恐怕最终用户会和你闹的不可开交——这什么垃圾软件?!而如果你打算实现他,那我告诉你,这张可怕的部门表里,字段不多,100个(呵呵)。

2、如果你说功能1其实是必须实现的新功能,和设计关系不大。那么你再观察,将部门申请同步到部门这个功能。他绝对可以细分为“修改部门”和“修改职位”两个子功能,而这两个子功能其实是之前的接口就实现的。那么,你是否为之前的接口留下了复用性?仔细看看之前接口的实现代码,你就会悲剧的发现,70%的可能性那个接口是无法复用的,因为查询代码其实不太一样。

那这只是我随手说的一个需求变更,如果有更多的需求变化呢?那么虽然代码还是能够复用一部分,设计空间释放也不会太麻烦。但是,仔细评判你的代码和设计,就会发现原来优雅而简洁的可复用设计的复用性越来越低,原来整齐而易读的代码的可读性越来越差。这就是人间悲剧。

而这时,Repository的思想从天而降,他也许能够为你可怜的代码带来一些让你惊喜的变更。如果使用DDD的思想设计上述内容,首先你需要确定领域。显而易见的,这里的领域可以这样划分:

用户领域:添加用户,删除用户,修改用户,修改用户的职位,移除用户的职位

部门领域:添加部门,删除部门,修改部门,查询部门下的职位,查询部门下的用户

职位领域:添加职位,修改职位,删除职位,查询职位下的用户,将用户添加到职位中,将用户从职位中移除

注:这里,如果是我写代码,我很可能会把“部门领域”和“职位领域”合并。这个并无不可,因为两者其实没有那么明显的边界。

在这个设计中,可以看到其实有些功能是重复的,比如说“修改用户的职位”和“将用户添加到职位中”。但是,在领域设计中,我却将其认为是两个不同的功能,因为他们的主体不一样。对前者而言,我先查出用户,函数的参数是“用户ID”和“职位名称”,这里使用出字符串的职位名称,即意味着对于用户领域来说,他不需要认识“职位”这个类。对于后者而言,我先查出职位,函数参数是“职位”和“一个或者多个用户ID”。这意味着,对于职位领域来说,他也不需要认识用户这个类。

这里可以看到,领域之间,耦合度很低。其实达到了最小知识原则所要求的内容。但是,实现过程中,可能会有这样的疑问,将职位添加到用户过程中,难道你不需要判断用户是否存在吗?当然,判断还是要判断的,但是我完全可以不认识用户这个类。通过将“用户职位关系表”中的“用户ID”字段与用户表中的“ID”字段做出外键关系,完全可以让数据帮我保证数据有效性。我只需要做一个简单的异常处理即可。

另外,耦合度低不等于不能耦合,在这里查询一次用户表,我认为也没有突破什么界限,所以完全没有问题。

在设计完领域后,需要再设计边界,也就是说由哪些类将这些功能全部暴露给外界。这时可以这么设计:

部门类:添加职位,修改职位,删除职位,查询职位下的用户,将用户添加到职位中,将用户从职位中移除

用户类:查询我所在的部门和职位

用户服务类:用户的CRUD

部门服务类:部门的CRUD

这里,实际是将部门当作了职位的聚合。这只是我随手写的设计,没有实践过也不知道有没有什么问题。但我想大致应当是正确的。这时,我就将所有功能都通过这几个类暴露在外界。在考虑这些内容的情况下,再来上述需求时,问题就明确了,他需要新建一个领域:部门修改申请。

部门修改申请:通过部门新建修改申请,通过旧的修改申请新建修改申请,审核修改申请,将修改申请同步到部门中,将修改申请同步到职位中。

现在再来看之前的两个大坑。问题1其实是规避不了。因为这个就是新功能,规避的唯一办法就是加钱,钱到位了功能也就到位了。而问题2确实就简单了,因为你可以直接调用暴露在“修改职位功能”将申请表中的用户给到对应职位,也可以通过调用“修改部门功能”直接将部门信息反向同步,而不需要考虑代码是否优雅,因为这里就是调用一个函数,并不存在优雅与否的问题。

再到以后,如果再有新功能,哪怕你还是需要释放设计空间。但你在重构的时候,已经整理过的功能就不需要整理第二遍。你只需要交被释放出的设计空间全部放回领域中,重构的工作量大大减少。而这,就是我所看重的DDD的核心优势。

针对到实现层面,之前那些乱七八糟的领域功能,其实就是Repository,他的出现自然而又简单。你所需要的只是简单的变化一下自己的思想,多写几十行代码,仅此而已。

最后,稍稍总结一下。完成以上内容的核心和关键其实并不是你对DDD了解多少。而真正有效的是你对需求了解多少,你认为需求有多少内容可能发生变化。对需求把握才是软件设计的核心。任何设计思想,设计模式都基于对需求的理解。我个人对软件思想的重要理解:

不基于需求任何想法都空谈,不理解需求任何代码都是胡说,不把握变化任何设计都是假想。

与君共勉。

DDD与Repository的更多相关文章

  1. DDD之:Repository仓储模式

    在DDD设计中大家都会使用Repository pattern来获取domain model所需要的数据. 1.什么事Repository? "A Repository mediates b ...

  2. DDD:Repository和UnitOfWork的生命周期问题

    UnitOfWork UnitOfWork是一种有状态的.用例级别的对象.如果不采用ORM是不会使用UnitOfWork模式的, Repository Repository是一种特殊的领域服务,因此是 ...

  3. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(2)

    上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(1)> 阅读目录: 抽离 IRepository 并改造 Reposi ...

  4. Repository、IUnitOfWork和IDbContext

    DDD 领域驱动设计-谈谈Repository.IUnitOfWork和IDbContext的实践 上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDb ...

  5. Repository、IUnitOfWork

    Repository.IUnitOfWork 在领域层和应用服务层之间的代码分布与实现 本来早就准备总结一下关于Repository.IUnitOfWork之间的联系以及在各层中的分布,直到看到田园里 ...

  6. DDD分层架构的进化

    .NET逻辑分层架构演示:DDD分层架构的进化 概述:   架构是高层的设计,如果设计和理解有误,必将在实现时带来各种问题.架构又是最稳定的,不会因为各种具体技术的依赖,如各种UI框架.ORM框架.I ...

  7. DDD理论学习系列(12)-- 仓储

    DDD理论学习系列--案例及目录 1. 引言 DDD中Repository这个单词,主要有两种翻译:资源库和仓储,本文取仓储之译. 说到仓储,我们肯定就想到了仓库,仓库一般用来存放货物,而仓库一般由仓 ...

  8. 【分享】几篇关于Repository 相关的讨论、提问、文章

    一.引入 最近在了解DDD,对于里面Repository 有点疑问和关注.闲来无事,去找了一些文章,来补补.在这里分享出来给大家.文章大多数都是英文的,见谅哈. 二.推荐列表 2.1 Filters ...

  9. DDD领域模型实现依赖注入(六)

    添加下订单的值对象: public partial class CustomerInfo:ValueObject { /// <summary> /// 下订单的值对象 /// </ ...

随机推荐

  1. Monster Audio 使用教程(三)多音轨录音、播放

    在工作站音轨上,把需要进行录音的音轨的录音按钮点亮,然后点击液晶屏旁边的[录音]按钮,开始录音  导出干声 如果希望录音后,导出干声(干声为录下的原始声音,不受效果器的作用),用其他宿主软件进行处理, ...

  2. 面试题四十二:连续子数组的最大和,要求时间复杂度为 n

    方法一:举例分析数组的规律,累加数组逐步保存最大值:累加中和<0,则遗弃前面的累加和:重新开始: int FindMaxArray(int [] A) {               if(A= ...

  3. .NetCore 登录(密码盐+随机数)

    一.理论部分 1.为什么要给密码加盐 我们在数据库中存入的密码一般不会是明文,都要通加MD5加密后存入,但是有些简单的密码加密后存入数据库也不安全,所有我们采用密码+盐再进行MD5加密存入数据库中. ...

  4. CSMA/CD ,现在的交换式以太网还用吗?谈全双工,半双工与CSMA/CD的关系

    我们知道:以太网访问控制用的是CSMA/CD,即载波侦听多点接入/ 冲突检测,是以广播的方式将数据发送到所有端口: 我们还知道:交换机能主动学习端口所接设备的MAC地址,在获知该端口的MAC 地址后, ...

  5. scrapy基本用法

    scrapy官方文档http://doc.scrapy.org/en/latest/ 一.scrapy安装 安装lxml:pip3 install lxml 安装wheel:pip3 install ...

  6. SpringBoot-JPA删除不成功,只执行了查询语句

    今天使用JPA自定义了一个删除方法deleteByUserIdAndCommentId发现并没有删除掉对应的数据,只执行了查询语句 Hibernate: select good0_.id as id1 ...

  7. 《python开发技术详解》|百度网盘免费下载|Python开发入门篇

    <python开发技术详解>|百度网盘免费下载|Python开发入门篇 提取码:2sby  内容简介 Python是目前最流行的动态脚本语言之一.本书共27章,由浅入深.全面系统地介绍了利 ...

  8. redis启动报错:The Windows version of Redis allocates a memory mapped heap for sharing with

    windows系统下通过cmd命令:redis-server.exe redis.windows.conf 启动redis报错,控制台报错如下: The Windows version of Redi ...

  9. PHP dechex() 函数

    实例 把十进制转换为十六进制: <?phpecho dechex("30") . "<br>";echo dechex("10&qu ...

  10. 6.28 NOI模拟赛 好题 状压dp 随机化

    算是一道比较新颖的题目 尽管好像是两年前的省选模拟赛题目.. 对于20%的分数 可以进行爆搜,对于另外20%的数据 因为k很小所以考虑上状压dp. 观察最后答案是一个连通块 从而可以发现这个连通块必然 ...