前言:又有差不多半个月没写点什么了,感觉这样很对不起自己似的。今天看到一篇博文里面写道:越是忙人越有时间写博客。呵呵,似乎有点道理,博主为了证明自己也是忙人,这不就来学习下DDD这么一个听上去高大上的东西。前面介绍了下MEF和AOP的相关知识,后面打算分享Automapper、仓储模式、WCF等东西的,可是每次准备动手写点什么的时候,就被要写的Demo难住了,比如仓储模式,使用过它的朋友应该知道,如果你的项目不是按照DDD的架构而引入仓储的设计,那么会让它变得很“鸡肋”,用不好就会十分痛苦,之前看过这篇 博客园的大牛们,被你们害惨了,Entity Framework从来都不需要去写Repository设计模式 文章的朋友应该还记得,不止是该文章的作者,很多园友在评论里面也提到了使用它的不爽。博主的项目中也遇到类似的问题,虽然引入了仓储模式,但是由于没有架构好,把仓储的接口和实现统一放在了数据访问层,导致到后面代码越写越难维护,完全感觉不到仓储带来的好处。所以博主觉得单纯分享仓储模式很容易使读者陷入“为了模式而模式”的误区,再加上最近一段时间在看《领域驱动设计:软件核心复杂性应对之道.Eric.Eva》这本书和博客园大牛dax.net的DDD系列文章,所以打算分享一个Demo来说明仓储模式、Automapper、WCF等知识点。

DDD领域驱动设计初探系列文章:

一、领域驱动设计基本概念

根据《领域驱动设计:软件核心复杂性应对之道.Eric.Eva》书中的观点,领域模型是软件项目的公共语言的核心,是领域专家和开发人员共同遵守的通用语言规则,那么在DDD里面,建模的重要性不用多说,所以要想更好理解领域驱动设计,理解领域模型的划分和建立就变得相当必要。首先来看看DDD里面几个比较重要的概念:

1、领域模型:领域模型与数据模型不同,它表述的是领域中各个类及其之间的关系。从领域驱动设计的角度看,数据库只不过是存储实体的一个外部机制,是属于技术层面的东西。数据模型主要用于描述领域模型对象的持久化方式,应该是先有领域模型,才有数据模型,领域模型需要通过某种映射而产生相应的数据模型,从这点来说,最新的EF的Code First就是一个很好的体现。领域模型对象分为实体、值对象和服务。

2、实体:在领域驱动设计里面,实体是模型中需要区分个体的对象。这里的实体和EntityFramework里面的实体不是一个概念,EF的实体为数据实体,不包含对象的行为。就博主的理解,DDD概念里面的实体就是包括实体数据(EF的Model)和行为的结合体

3、值对象:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。相比实体而言,值对象仅仅是比实体少了一个标识。值对象的设计比较存在争议,我们暂且记住值对象和实体的区别:(1)实体拥有唯一标识,而值对象没有;(2)实体允许变化,而值对象不允许变化;(3)判断两个实体相等的方法是判断实体的标识相等,而判断两个值对象相等的标准是值对象内部所有属性值相等;

4、聚合(以及聚合根):聚合表示一组领域对象(包括实体和值对象),用来表述一个完整的领域概念。而每个聚合都有一个根实体,这个根实体又叫做聚合根。举个简单的例子,一个电脑包含硬盘、CPU、内存条等,这一个组合就是一个聚合,而电脑就是这个组合的聚合根。博主觉得关于聚合的划分学问还是挺大的,需要在实践中慢慢积累。同一个实体,在不同的聚合中,它可能是聚合根,也可能不是,需要根据实际的业务决定。聚合根是聚合所表述的领域概念的主体,外部对象需要访问聚合内的实体时,只能通过聚合根进行访问,而不能直接访问

5、领域服务:博主的理解,领域模型主张富领域模式,也就是说把领域逻辑尽量写在领域实体里面,也就是常说的“充血模式”,而对于业务逻辑,最好是以服务的形式提供。至于领域逻辑和业务逻辑的界定,这个要根据实际情况来定。总之,领域服务是用来处理那些领域模型里面不好定义或者某些可变逻辑的的时候才会用到。待验证!

6、工厂、仓储等概念留在Demo里面说明。

二、领域驱动设计开始之旅

1、项目分层

领域驱动设计将软件系统分为四层:基础结构层、领域层、应用层和表现层。来看看书中的分层:

其实在dax.net的系列中这张图更能说明这种架构

2、项目架构

博主打算用权限系统的案例说明的领域驱动设计的项目架构。项目严格按照表现层、应用层、领域层、基础设施层来划分。

表现层:MVC的Web项目,负责UI呈现。

应用层:WCF服务,负责协调领域层的调用,向UI层提供需要的接口。

领域层:定义领域实体和领域逻辑。

基础设施层:一些通用的技术,比如AOP、MEF注入、通用的工具类、DTO模型层,这里为什么要有一个DTO模型层,DTO是用于UI展现用的纯数据Model,它不包含实体行为,是一种贫血的模型。

整个项目的调用方式严格按照DDD设计来进行,UI层通过WCF服务调用应用层的WCF接口,WCF服务通过仓储调用领域层里面的接口,基础设施层贯穿其他各层,在需要的项目中都可以引用基础设施层里面的内库。

3、代码示例

接下来,博主就根据自己的理解,从零开始使用这种架构写一个简单的权限管理系统。由于是领域驱动设计,所以,文章的重点会放在领域层,项目使用了EF的Model First来进行,先设计实体,后生成数据库。

3.1 首先来看看表结构

根据博友要求,这里说明一下表之间的映射关系:

1表示TB_DEPARTMENT表的主键DEPARTMENT_ID作为TB_USERS表的外键;

2表示TB_USERS表的主键USER_ID作为TB_USERROLE表的外键;

3表示TB_ROLE表的主键ROLE_ID作为TB_USERROLE表的外键;

4表示TB_ROLE表的主键ROLE_ID作为TB_MENUROLE表的外键

5表示TB_MENU表的主键MENU_ID作为TB_MENUROLE表的外键

首先建好对应的表实体,然后根据模型生成数据库

将生成的sql语句执行后就可以得到对应的表结构。

3.2 聚合的划分

在领域层里面我们新建一个BaseModel,里面有三个类

这三个类IEntity、IAggregateRoot、AggregateRoot分别定义了实体的接口、聚合根的接口、聚合根的抽象实现类。

    //用作泛型约束,表示继承自该接口的为领域实体
public interface IEntity
{ }
    /// <summary>
/// 聚合根接口,用作泛型约束,约束领域实体为聚合根,表示实现了该接口的为聚合根实例,由于聚合根也是领域实体的一种,所以也要实现IEntity接口
/// </summary>
public interface IAggregateRoot:IEntity
{ }
   /// <summary>
/// 聚合根的抽象实现类,定义聚合根的公共属性和行为
/// </summary>
public abstract class AggregateRoot:IAggregateRoot
{ }

这里定义接口的作用是定义实体和聚合根的泛型约束,抽象类用来定义聚合根的公共行为,目前为止,这些接口和类里面都是空的,后面会根据项目的需求一步一步往里面加入逻辑。

在EF里面由edmx文件会生成实体的属性,前面说到领域模型主张充血模式,所以要在EF的实体model里面加入实体的行为,为了不改变EF生成实体的代码,我们使用partial类来定义实体的行为。我们来看Model文件夹下面的代码

    public partial class TB_DEPARTMENT: AggregateRoot
{
public override string ToString()
{
return base.ToString();
}
}
    public partial class TB_MENU : AggregateRoot
{ }
    /// <summary>
/// 由于不会直接操作此表,所以TB_MENUROLE实体不必作为聚合根,只是作为领域实体即可
/// </summary>
public partial class TB_MENUROLE:IEntity
{
}
    public partial class TB_ROLE:AggregateRoot
{
}
    public partial class TB_USERROLE:IEntity
{
}
    public partial class TB_USERS:AggregateRoot
{
}

我们看到,这些实体,只有TB_MENUROLE和TB_USERROLE不是聚合根,其他实体都是聚合根。我这里大概划分为4个聚合:

聚合1:TB_DEPARTMENT实体

聚合2:TB_MENU、TB_MENUROLE、TB_ROLE这3个为一个聚合,聚合根是TB_MENU。

聚合3:TB_USERS、TB_USERROLE、TB_DEPARTMENT、TB_ROLE这4个为一个聚合,聚合根是TB_USERS。

聚合4:TB_ROLE、TB_USERS、TB_USERROLE、TB_MENUROLE、TB_MENU这5个表为一个聚合,聚合根是TB_ROLE。

可能这样分会有一定的问题,后续出现再慢慢纠正。

到这里,聚合的划分基本完了,至于为什么要做这么一些约束和设计,因为仓储只能对聚合根做操作,下篇讲仓储的时候会详细说明。

DDD领域驱动设计初探(一):聚合的更多相关文章

  1. C#进阶系列——DDD领域驱动设计初探(一):聚合

    前言:又有差不多半个月没写点什么了,感觉这样很对不起自己似的.今天看到一篇博文里面写道:越是忙人越有时间写博客.呵呵,似乎有点道理,博主为了证明自己也是忙人,这不就来学习下DDD这么一个听上去高大上的 ...

  2. C#进阶系列——DDD领域驱动设计初探(二):仓储Repository(上)

    前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repository,为什么Repository会有这么大的争议,博主认为主要原 ...

  3. C#进阶系列——DDD领域驱动设计初探(三):仓储Repository(下)

    前言:上篇介绍了下仓储的代码架构示例以及简单分析了仓储了使用优势.本章还是继续来完善下仓储的设计.上章说了,仓储的最主要作用的分离领域层和具体的技术架构,使得领域层更加专注领域逻辑.那么涉及到具体的实 ...

  4. C#进阶系列——DDD领域驱动设计初探(四):WCF搭建

    前言:前面三篇分享了下DDD里面的两个主要特性:聚合和仓储.领域层的搭建基本完成,当然还涉及到领域事件和领域服务的部分,后面再项目搭建的过程中慢慢引入,博主的思路是先将整个架构走通,然后一步一步来添加 ...

  5. C#进阶系列——DDD领域驱动设计初探(五):AutoMapper使用

    前言:前篇搭建了下WCF的代码,就提到了DTO的概念,对于为什么要有这么一个DTO的对象,上章可能对于这点不太详尽,在此不厌其烦再来提提它的作用: 从安全上面考虑,领域Model都带有领域业务,让Cl ...

  6. C#进阶系列——DDD领域驱动设计初探(六):领域服务

    前言:之前一直在搭建项目架构的代码,有点偏离我们的主题(DDD)了,这篇我们继续来聊聊DDD里面另一个比较重要的知识点:领域服务.关于领域服务的使用,书中也介绍得比较晦涩,在此就根据博主自己的理解谈谈 ...

  7. C#进阶系列——DDD领域驱动设计初探(七):Web层的搭建

    前言:好久没更新博客了,每天被该死的业务缠身,今天正好一个模块完成了,继续来完善我们的代码.之前的六篇完成了领域层.应用层.以及基础结构层的部分代码,这篇打算搭建下UI层的代码. DDD领域驱动设计初 ...

  8. DDD领域驱动设计初探

    DDD领域驱动设计初探1 前言:又有差不多半个月没写点什么了,感觉这样很对不起自己似的.今天看到一篇博文里面写道:越是忙人越有时间写博客.呵呵,似乎有点道理,博主为了证明自己也是忙人,这不就来学习下D ...

  9. DDD领域驱动设计初探(七):Web层的搭建

    前言:好久没更新博客了,每天被该死的业务缠身,今天正好一个模块完成了,继续来完善我们的代码.之前的六篇完成了领域层.应用层.以及基础结构层的部分代码,这篇打算搭建下UI层的代码. DDD领域驱动设计初 ...

随机推荐

  1. 解决Acunetix 12中文汉化的方法

    最近下载一款测试软件acunetix,苦于满屏英文的苦恼,看不懂,于是乎就问度娘,结果度娘就是给中文破解包: 我是12版的,网上提供的都是11版的,没法用.怎么办呢?还好我是做测试的,平时做兼容性测试 ...

  2. [CSP-S模拟测试]:chess(数学)

    题目描述 $dirty$在一个棋盘上放起了棋子. 棋盘规格为$n\times m$,他希望任意一个$n\times n$的区域内都有$C$个棋子.$dirty$很快就放置好了一个满足条件的棋盘方案,但 ...

  3. 深入理解JVM虚拟机11:Java内存异常原理与实践

    本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutori ...

  4. 将String转化成Stream,将Stream转换成String, C# Stream 和 byte[] 之间的转换(文件流的应用)

    static void Main( string[] args ) { string str = "Testing 1-2-3"; //convert string 2 strea ...

  5. Delphi XE2 之 FireMonkey 入门(2)

    FireMonkey 的控件都是自己绘制的(而不是基于系统组件), 我想它们应该是基于一些基本图形; 就从基本图形开始吧. FMX.Objects 单元给出的类: TShape //基本图形的基类 T ...

  6. 阶段1 语言基础+高级_1-2 -面向对象和封装_15练习使用private关键字定义

    练习使用private关键字定义一个学生类.通过这个联系说明一种特殊情况 先定义了name个age分别再定义getter和setter的方法 boolean类型的getter方法不能叫做get开头的. ...

  7. free pascal

    https://freepascal.org/ free pascal OPEN SOURCE COMPILER FOR PASCAL AND OBJECT PASCAL GENERAL HomeNe ...

  8. [转载]OpenSSL中文手册之命令行详解(未完待续)

     声明:OpenSSL之命令行详解是根据卢队长发布在https://blog.csdn.net/as3luyuan123/article/details/16105475的系列文章整理修改而成,我自己 ...

  9. Altium Designer chapter8总结

    元件库操作中需要注意如下: (1)原理图库:主要分三部分来完成(绘制元件的符号轮廓.放置元件引脚.设计规则检查). (2)多子件原理图库:操作与原理图库基本相同,就是新建part. (3)PCB封装库 ...

  10. fastboot烧录镜像--VTS&GSI镜像替换

    fastboot简介 Android提供的原生工具,主要用于替换镜像. 源码在SDK工程中,/system/core/fastboot目录下 安卓分区&镜像 见链接分区和映像--google官 ...