领域驱动设计实践上篇(转)

http://www.cnblogs.com/idoudou/p/Domain-driven-design-Part1.html

一、前言

领域驱动设计的概念最早是由著名的建模专家Eric Evans在2004年发表的著名书籍 Domain-Driven Design –Tackling Complexity in the Heart of Software(中文译名:领域驱动设计 2006年3月清华出版社译本,或称 Domain Driven-Design architecture [Evans DDD])。园子里有很多人早已将其实践并应用,关于其要素、特点不再赘述,很多人在技术选型时想用它但又怕驾驭不了它,无非是没有真的动手去实践过,我自己也是如此,从两年开始关注DDD,dax.net的DDD系列收获甚多,无奈没有机会实践,仅仅作为个人的研究项目断断续续在写,中途回老家过了半年的标准“乡村生活”,那段时间早上早起溜小孩,晚上哄睡小孩写代码,算是实现了“只写感兴趣的代码”这事,只是半年其乐融融的生活反倒让我觉得危机四伏,于是今年年初就又回待了3年多的上海。要指出的是DDD并不是万金油,没有必要为了用而用,合适才是王道,DDD的核心是领域建模思想,你可以用最新最潮的语言和框架去实践,但核心本质“领域(业务理解)”并不会随之变化,而学习最新最潮的技术也需要时间成本,况且以有涯之人生去追随无涯的技术变迁也是一件非常痛苦的事情。

二、经典分层架构

对原图用PPT稍微加工了下

表现层:负责向用户展现信息及解释用户命令,跟传统三层里的表现层意思差不多,表现层与应用层之间是通过数据传输对象(DTO)进行交互的,数据传输对象是没有行为的POCO对象,它的目的只是为了对领域对象进行数据封装,实现层与层之间的数据传递。为何不能直接将领域对象用于数据传递?因为领域对象更注重领域,而DTO更注重数据。不仅如此,由于“富领域模型”的特点,这样做会直接将领域对象的行为暴露给表现层。

应用层:该层不包含任何领域逻辑,但它会对任务进行协调,并可以维护应用程序的状态,因此,它更注重流程性的东西。在某些领域驱动设计的实践中,也会将其称为“工作流层”。应用层是领域驱动中最有争议的一个层次,也会有很多人对其职责感到模糊不清。图中的应用一和应用二在我们项目里实际上是用WebApi实现的Web服务,可以说这一层其实是没有的,或者说是与web服务合并了,但web服务其实应该是表现层的东西。

领域层:包含了业务所涉及的领域对象(实体、值对象)、领域服务以及它们之间的关系。这部分内容的具体表现形式就是领域模型(Domain Model)。领域驱动设计提倡富领域模型,即尽量将业务逻辑归属到领域对象上,实在无法归属的部分则以领域服务的形式进行定义。将数据持久托管给基础设施层。

基础设施层:该层专为其它各层提供技术框架支持。注意,这部分内容不会涉及任何业务知识。众所周知的数据访问的内容,也被放在了该层当中,因为数据的读写是业务无关的。

三、聚合及聚合根(Aggregate,Aggregate Root)

聚合通过定义对象之间清晰的所属关系和边界来实现领域模型的内聚,并避免了错综复杂的难以维护的对象关系网的形成。

每个聚合有一个根和一个边界,边界定义了一个聚合内部有哪些实体或值对象,根是聚合内的某个实体。

聚合内部的对象之间可以相互引用,但是聚合外部如果要访问聚合内部的对象时,必须通过聚合根开始导航,绝对不能绕过聚合根直接访问聚合内的对象,也就是说聚合根是外部可以保持 对它的引用的唯一元素。

聚合根负责与外部其他对象打交道并维护自己内部的业务规则。

根据Eric Evans《领域驱动设计》一书中的例子,一辆车包含四个轮子,轮子离开“车”就毫无意义,此时这个联合体就是聚合,而“车”就是聚合根(Aggregate Root)。

如下图,所有对象的联合体称之为聚合,Users则为聚合根。

   通俗的说,领域模型需要根据领域概念分成多个聚合,每个聚合都有一个实体作为“聚合根”,领域对象从无到有的创建,以及CRUD操作都应该作用在聚合根上,而不是单独的某个实体。那当你的代码需要直接对聚合内部的实体进行CRUD操作时,就说明你的模型设计已经存在问题了。

四、仓储(数据的持久化)

模型的已经有了,接下来该考虑怎么持久化的问题了。

上面有说过,领域驱动设计的核心建立正确的领域模型,它并不关心用什么技术去持久化这些模型,这也是DDD的一贯宗旨“领域模型与技术架构分离”,但我们的领域层并不是只包含了模型而已,还包含了领域服务和事件等,或者说持久化模型的发起是从领域层开始的,因此只要满足不混入任何技术实现且仅发起调用即可,此时较好的方式就是将持久化和类似技术实现如发短信等这些功能接口定义在领域层,把具体实现托管给基础设施层。此时领域层只调用接口而对其技术实现一无所知,程序运行时将具体实现通过IOC方式注入即可,这样能保证我们的领域层的一个纯净,而领域模型的纯净程度便成为了衡量系统架构优劣的一项指标。

以持久化为例,首先定义接口,顺便说一句,虽然建议将功能接口定义在领域层,但把接口也定义在基础设施层也没有问题,甚至于基础设施层足够强健的话可以使用Nuget单独维护其版本供不同项目引用。

此处在基础设施层定义接口。

  1. using System;
  2. using System.Collections.Generic;
  3. namespace DDD.Infrastructure
  4. {
  5. public interface IRepository<TEntity> where TEntity : BaseEntity, IAggregateRoot
  6. {
  7. void Add(TEntity entity);
  8. void Update(TEntity entity);
  9. void Remove(TEntity entity);
  10. TEntity GetByKey(int key);
  11. IEnumerable<TEntity> Get(Func<TEntity, bool> where);
  12. }
  13. }

EF实现

  1. using System.Data.Entity;
  2. using System.Linq;
  3. using System.Threading.Tasks;
  4. namespace DDD.Infrastructure.Database
  5. {
  6. public class Repository<TEntity, TContext> : IRepository<TEntity>
  7. where TEntity : BaseEntity, IAggregateRoot
  8. where TContext : DbContext
  9. {
  10. private readonly DbSet<TEntity> db;
  11. private readonly TContext context;
  12. public Repository(TContext context)
  13. {
  14. this.context = context;
  15. this.db = context.Set<TEntity>();
  16. }
  17. public void Add(TEntity entity)
  18. {
  19. db.Add(entity);
  20. Commit();
  21. }
  22. public void Update(TEntity entity)
  23. {
  24. var dbSet = context.Set<TEntity>();
  25. var entry = context.Entry(entity);
  26. dbSet.Attach(entity);
  27. entry.State = EntityState.Modified;
  28. Commit();
  29. }
  30. public void Remove(TEntity entity)
  31. {
  32. db.Remove(entity);
  33. Commit();
  34. }
  35. public TEntity GetByKey(int key)
  36. {
  37. return db.Find(key);
  38. }
  39. public IQueryable<TEntity> Get()
  40. {
  41. return db;
  42. }
  43. public IQueryable<TEntity> ReadonlyGet()
  44. {
  45. return db.AsNoTracking();
  46. }
  47. private void Commit()
  48. {
  49. context.SaveChanges();
  50. }
  51. #if NET45
  52. public async Task AddAsync(TEntity entity)
  53. {
  54. db.Add(entity);
  55. await CommitAsync();
  56. }
  57. public async Task UpdateAsync(TEntity entity)
  58. {
  59. var dbSet = context.Set<TEntity>();
  60. var entry = context.Entry(entity);
  61. dbSet.Attach(entity);
  62. entry.State = EntityState.Modified;
  63. await CommitAsync();
  64. }
  65. public async Task RemoveAsync(TEntity entity)
  66. {
  67. db.Remove(entity);
  68. await CommitAsync();
  69. }
  70. public async Task<TEntity> GetByKeyAsync(int key)
  71. {
  72. return await db.FindAsync(key);
  73. }
  74. private async Task CommitAsync()
  75. {
  76. await context.SaveChangesAsync();
  77. }
  78. #endif
  79. }
  80. }

此处用EF作为演示具体实现,没有使用UnitOfWork来同步上下文,但这都不是重点,特别指出的是为了表现出基础设施层应该具备一定的兼容性,该实现默认采用EF的同步的实现,当环境支持异步接口时则可选择调用异步方法,代码内#if NET45作为条件编译符号指示是否启用。这样一来非4.5框架引用只能使用同步方法,4.5及以上只需要在项目属性->生成->条件编译符号内写入NET45并保存即可启用异步方法。

五、CQRS体系结构

CQRS全称为Command Query Responsibility Segregation,命令查询职责分离。

有人曾经说系统无非两种行为:命令和查询。可以简单粗暴的理解为任何写操作都是命令,其余皆为查询。

这与软件设计的思想“读写分离”不谋而合,因为当命令和查询被分离的时候,我们将会有更多的机会去把握整个事情的细节,以我公司的项目为例,因我们采用WebApi实现的web服务作为边界,我们可以采用将所有写服务部署为一个站点,所有读服务作为一个站点,写服务的调用都以命令形式(Command),读服务还是传统实现,此时有几个好处:

1:服务实现读写分离。

2:读服务因其无状态性,根据硬件情况可以水平拓展做负载均衡实现分布式。

3:不会因大量读请求影响到写服务。

4:读服务和写服务可以采用不同技术架构,细致优化性能,比如我们采用的方式是读服务用WebApi实现,写服务用ServiceStack实现。

引用一张dax.net的示例图

CQRS还包含几个重要概念,如事件溯源(Event Sourcing)、快照(Snapshots)以及事件存储(Event Store),本想都讲讲,但不知不觉吃完饭到现在都深夜了,明天早上还得带小孩出去,所以放在下篇结合代码示例讲解,相信会直观一点,也可以直接参考netfocusdax.net的系列博客,这两位在github上的项目也是非常具有参考价值的。

六、相关引用

http://www.cnblogs.com/daxnet

http://www.cnblogs.com/netfocus

http://www.infoq.com/domain-driven-design

时间: 2024-10-16 02:07:50

领域驱动设计实践上篇(转)的相关文章

领域驱动设计实践上篇

一.前言 领域驱动设计的概念最早是由著名的建模专家Eric Evans在2004年发表的著名书籍 Domain-Driven Design –Tackling Complexity in the Heart of Software(中文译名:领域驱动设计 2006年3月清华出版社译本,或称 Domain Driven-Design architecture [Evans DDD]).园子里有很多人早已将其实践并应用,关于其要素.特点不再赘述,很多人在技术选型时想用它但又怕驾驭不了它,无非是没有真

EntityFramework之领域驱动设计实践

EntityFramework之领域驱动设计实践 - 前言 EntityFramework之领域驱动设计实践 (一):从DataTable到EntityObject EntityFramework之领域驱动设计实践 (二):分层架构 EntityFramework之领域驱动设计实践 (三):案例:一个简易的销售系统 EntityFramework之领域驱动设计实践 (四):存储过程 - 领域驱动的反模式 EntityFramework之领域驱动设计实践 (五):聚合 EntityFramewor

(转)EntityFramework之领域驱动设计实践

EntityFramework之领域驱动设计实践 - 前言 EntityFramework之领域驱动设计实践 (一):从DataTable到EntityObject EntityFramework之领域驱动设计实践 (二):分层架构 EntityFramework之领域驱动设计实践 (三):案例:一个简易的销售系统 EntityFramework之领域驱动设计实践 (四):存储过程 - 领域驱动的反模式 EntityFramework之领域驱动设计实践 (五):聚合 EntityFramewor

[转]EntityFramework之领域驱动设计实践

本文转自:http://www.cnblogs.com/daxnet/archive/2010/11/02/1867392.html Entity Framework之领域驱动设计实践 EntityFramework之领域驱动设计实践 - 前言 EntityFramework之领域驱动设计实践 (一):从DataTable到EntityObject EntityFramework之领域驱动设计实践 (二):分层架构 EntityFramework之领域驱动设计实践 (三):案例:一个简易的销售系

.NET领域驱动设计—实践(穿过迷雾走向光明)

阅读目录 开篇介绍 1.1示例介绍 (OnlineExamination在线考试系统介绍) 1.2分析.建模 (对真实业务进行分析.模型化) 1.2.1 用例分析 (提取系统的所有功能需求) 1.3系统设计.建模 (技术化业务模型) 1.3.1 枚举类型的使用 (别让枚举类型成为数值型对象) 1.3.2 基础数据.业务数据 (显示实体和隐式过程) 1.3.3 模型在数据库中的主外键关联问题 (面向对象模型与关系模型的天然抗阻) 1.3.4 角色.类型 (区分类型与面向对象概念) 1.3.5 名词

【DDD】领域驱动设计实践 —— 业务建模小招数

本文结合团队在ECO(社区服务系统)业务建模过程中的实践经验,总结得到一些DDD业务建模的小招数,不一定是完美的,但是对我们团队来说很有效用,希望能帮到其他人.后面会陆续将项目中业务建模的一些经典例子放上来,分享给大家. ECO系统是线上旧系统,它的建模过程有别于新系统的业务建模.由于背着历史包袱,ECO的建模过程不是那么纯粹,很容易受到旧代码的影响,陷入代码的细节中,初期举步维艰,靠着小步快跑的方式得到了一些雏形和方法论,后面越来越顺,效果还是不错的. 本文为[DDD]系列文章中的其中一篇,其

【DDD】领域驱动设计实践 —— 架构风格及架构实例

概述 DDD为复杂软件的设计提供了指导思想,其将易发生变化的业务核心域放置在限定上下文中,在确保核心域一致性和内聚性的基础上,DDD可以被多种语言和多种技术框架实现,具体的框架实现需要根据实际的业务场景和需求来制定. 核心的指导思路归纳为: 关注点放在domain上,将业务领域限定在同一上下文中 降低上下文之间的依赖,通过‘开发主机服务’(REST服务是其中的一种).‘消息模式’.‘事件驱动’等架构风格实现 遵循分层架构模式 架构风格 针对DDD的架构设计,<实现领域驱动设计>提到了几种架构风

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

本文是DDD框架实现讲解的第二篇,主要介绍了DDD的Application层的实现,详细讲解了service.assemble的职责和实现.文末附有github地址.相比于<领域驱动设计>原书中的航运系统例子,社交服务系统的业务场景对于大家更加熟悉,相信更好理解.本文是[DDD]系列文章的其中一篇,其他可参考:使用领域驱动设计思想实现业务系统 Application层 在DDD设计思想中,Application层主要职责为组装domain层各个组件及基础设施层的公共组件,完成具体的业务服务.A

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

目录 1. 框架实现图 2. 框架详述 3. 模块结构 正文 本文主要介绍了基于SpringMVC+mybatis对DDD思想的落地实现框架.本文为[DDD]系列文章中的其中一篇,其他内容可参考:使用领域驱动设计思想实现业务系统. 回到顶部 1. 框架实现图 该框架实现基本和DDD的指导思想契合,主要分为四层,且将关注点放在了domain层.下面将逐层介绍各个组件的职责. 回到顶部 2. 框架详述 User Interface层 门面层,对外以各种协议提供服务,该层需要明确定义支持的服务协议.契