【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇
前言
领域驱动设计,其实已经是一个很古老的概念了,但它的复杂度依旧让学习的人头疼不已。
互联网关于领域驱动的文章有很多,每一篇写的都很好,理解领域驱动设计的人都看的懂。
不过,这些文章对于那些初学者而言,还是如同天书一样。
买本驱动领域的书来看?别逗了,这可不是C#语法入门,哪里有书能写明白的。
想学会领域驱动设计,只有一途——实践,不断的实践。
领域驱动设计是什么?
领域驱动设计就是我们俗称的DDD,英文全拼是Domain-Driven Design。
我认为,理解领域驱动设计的第一步是,顾名思义;所以,让我们先直白的通过名字来解释看看。
领域驱动设计:用业务领域来做模块分割,以领域为核心思想设计框架,用设计好的领域来驱动系统实现。
如何?这样是不是就好理解了。
其实,领域驱动设计,和我们之前常用的模型驱动设计很相似。其核心区别,也就是一个聚合的概念。
虽然,现在看来,CodeFirst中的聚合太普遍了,但早在十几年前,聚合可是一个让我们头疼的难题,因为那个时代还没有CodeFirst这么便捷的框架。
什么?你不知道聚合是什么?
别担心,我们在后续实现框架的地方,结合代码把这些聚合啦,值对象啦,等等名词一一讲解。
其实,以现在的技术框架的成熟度,聚合这种东西,不理解也就不理解了,无所谓的。
领域驱动设计的意义
虽然,我不想把领域驱动设计搞的那么神秘,但,事实上,领域驱动设计确实挺难学的。
虽然,我们有了CodeFirst这样优秀的框架,但那只是针对使用者,而对设计者而言,CodeFirst并没有减少设计逻辑。所以,想学会领域驱动设计,还是要有一点耐心,并花一点时间,付诸于实践。
虽然,领域驱动设计很复杂,但,我认为它是值得我们付出时间和心血学习的。
因为,驱动领域设计是技术思维的一个分水岭,学会了这种技术思维后,会对框架设计的理解更上一个台阶。
那么,让我们一起做一个领域驱动的框架,在实践中领会这门技艺吧。
领域驱动设计的实现
我们即将编写的框架是基于Entity Framework的,所以越熟悉Entity Framework越好,如果你不熟悉EF,那也没关系,因为我们是从头一步一步编写的。
下面让我们一起编写框架吧。
首先,我们创建项目如下:
接下来我们把相关的DLL放到KibaDDD程序集下待用。
然后我们编写核心代码程序集Repository。
首先为Repository程序集引入外部DLL[EntityFramework,EntityFramework.Extended,EntityFramework.SqlServer,CodeFirstStoredProcs],同时,再为程序集引入Utility程序集。
然后我们开始设计Repository程序集的布局。
如上图所示,我们建立了Repository程序集的布局,布局中的文件夹及文件作用如下:
TableMapping文件夹:用于存储数据表的映射关系。
TableModel文件夹:用于存储数据表模型。
TableRepository文件夹:用于操作数据表。
DateBaseContext文件:管理数据库的核心文件。
RepositoryStatic文件:存储静态的DateBaseContext对象,供其他程序集调用,实现线程内,使用同一个DateBaseContext对象,减少内存开销。
Repository的实现
TableModel
TableModel中我们建立了一个表——Kiba_User,代码如下:
public partial class Kiba_User
{
[Key]
public int UserId { get; set; }
[Required]
[StringLength(50)]
public string UserName { get; set; }
[StringLength(200)]
public string UserNickName { get; set;
[StringLength(100)]
public string Password { get; set; }
public int? Age { get; set; }
public int? Sex { get; set; }
[StringLength(500)]
public string Remark { get; set; }
}
代码很简单,就是把数据表和其字段转换成了类和属性,我们可以把这个类暂时理解为表的数据模型。
TableMapping
TableMapping中我们建立Kiba_User的数据模型表与数据库表的映射关系,代码如下所示:
public class Kiba_UserMap : EntityTypeConfiguration<Kiba_User>
{
public Kiba_UserMap()
{
this.Property(e => e.UserName)
.IsUnicode(false);
this.Property(e => e.UserNickName)
.IsUnicode(false);
this.Property(e => e.Password)
.IsUnicode(false);
this.Property(e => e.Remark)
.IsUnicode(false);
}
}
从代码中我们可以发现,映射只对部分字符串类型的属性进行了映射,而其他属性,并没有做映射处理。
原因是这样的,没有显示映射处理的属性,会默认映射到同名的数据表字段上;所以这里节省了一些代码量。
DateBaseContext文件
表的数据模型和映射我们已经编写完了,并且,我们还编写了仓储用来对表进行操作;但,这样还不能让数据库和代码模型关联到一起。
我们还需要编写DateBaseContext文件,通过DateBaseContext文件编写,我们就可以把表模型和表映射与数据库关联了。
DateBaseContext文件的代码如下所示:
public partial class DateBaseContext : DbContext
{
public DateBaseContext()
: base("name=DateBaseContext")
{
this.Configuration.ValidateOnSaveEnabled = true;//保存时验证
this.Configuration.AutoDetectChangesEnabled = true;//跟踪变化
this.Configuration.LazyLoadingEnabled = true;//懒惰加载
this.Configuration.ProxyCreationEnabled = true;//代理创建数据库
}
#region Table List
public virtual DbSet<Kiba_User> Kiba_User { get; set; }
#endregion
protected override void OnModelCreating(DbModelBuilder modelBuilde
{
modelBuilder.Configurations.Add(new Kiba_UserMap());
}
}
代码很简单,下面我们一起来解读下DateBaseContext文件里的代码。
首先是DateBaseContext继承了DbContext类;DbContext可以理解为微软提供的,专门来管理数据库和代码之间的关系的类。
然后再构造函数DateBaseContext()里,可以看到,我们在构造函数中做了几项基础配置,代码中已经做了相应的注释。
其中this.Configuration.ProxyCreationEnabled属性,我们重点讲一下。
当ProxyCreationEnabled属性设置为True时,我们一旦运行系统,系统会自动的,数据模型同步到数据库,并且会创建一个__MigrationHistory表,来记录同步的内容。
PS:【虽然,在领域驱动设计的理念中,是先有表的数据模型,然后在建立表结构。但,这只是理念,我们运用的时候,先建立表在建立数据模型也是可以的。我这里只是为了简单的实现,所以将ProxyCreationEnabled设置为了True】
接下来,我们定义了一个public virtual DbSet<Kiba_User> Kiba_User { get; set; }属性。
Kiba_User 这个属性,我们可以把他理解为,数据库表在代码世界的代理,如果我们想对数据库表内容进行查询和修改,只要对这个代理进行修改,就会自动同步到数据库了。
然后我们重写了OnModelCreating方法,在OnModelCreating里,把我们刚刚建立的映射关系添加了进去,这样数据库的表,就被我们立体的加载到了代码世界。
TableRepository
TableRepository中主要是应用DateBaseContext来对表进行增删改查的处理,理论上TableRepository是修改数据库的唯一入口;
我们首先,先看下BaseRepository类;代码如下:
public class BaseRepository
{
public DateBaseContext Database
{
get
{
var context = RepositoryStatic.DateBaseContext as DateBaseContext; if (context == null)
{
context = new DateBaseContext();
RepositoryStatic.DateBaseContext = context;
} return context;
}
}
public int SaveChanges()
{
int i = 0;
int saveCount = 0;
bool saveFailed;
do
{
saveFailed = false; try
{
saveCount++;
i = Database.SaveChanges();
Logger.Debug("SaveChanges Retrun:" + i); }
catch (DbUpdateConcurrencyException ex)
{
if (saveCount > 3)
{
throw new Exception("服务器繁忙,请稍后");
}
Logger.Error("DbUpdateConcurrencyException保存次数:" + saveCount, ex);
saveFailed = true;
try
{
ex.Entries.Single().Reload();
}
catch (Exception exReload)
{
Logger.Info("exReload保存失败");
throw exReload;
}
}
catch (DbUpdateException ex)
{
if (ex.Message.Contains("与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。"))
{
throw new Exception("服务器繁忙,请稍后");
}
else
{
throw ex;
}
}
catch (DbEntityValidationException dbEx)
{
Logger.Error(dbEx);
throw dbEx;
} catch (Exception ex)
{
Logger.Info("SaveChanges保存失败");
throw ex;
} } while (saveFailed);
return i;
}
}
这里我们主要定义一个属性Database和一个方法SaveChanges。
Database就是DateBaseContext类的实例,相当于代码世界的数据库。
SaveChanges就是调用Database的SaveChanges方法来保存数据的修改,当然,我们对该方法进行了一些封装,让他更饱满一些。
然后我们在一起看下表的独立仓储Kiba_UserRepo,代码如下:
public class Kiba_UserRepo : BaseRepository
{
public List<T> GetSelector<T>(Expression<Func<Kiba_User, T>> selector, Expression<Func<Kiba_User, bool>> where)
{
return Database.Kiba_User.Where(where).Select(selector).ToList();
}
public List<Kiba_User> GetWhere(Expression<Func<Kiba_User, bool>> where, int currentPage, int pageCount)
{
return Database.Kiba_User.Where(where).OrderByDescending(p => p.UserId).Skip((currentPage - 1) * pageCount).Take(pageCount).ToList();
}
public int GetWhereCount(Expression<Func<Kiba_User, bool>> where)
{
return Database.Kiba_User.Where(where).Count();
}
public Kiba_User Add(Kiba_User model)
{
var addModel = Database.Kiba_User.Add(model);
return addModel;
}
public Kiba_User Delete(Kiba_User model)
{
var delModel = Database.Kiba_User.Remove(model);
return delModel;
}
}
表仓储里的代码很简单,就是普通的LINQ增删改查。
----------------------------------------------------------------------------------------------------
到此,框架的基本雏形就已经编写完成了,接下来我们做一下简单调用,测试一下。
在KibaDDD项目建立测试类——TestRun;代码如下:
public class TestRun
{
public TestRun()
{
Kiba_UserRepo repo = new Kiba_UserRepo();
repo.Add(new Kiba_User() { UserName = "kiba518" });
repo.SaveChanges();
}
}
运行结果:
数据库无中生有的,为我们创建了表Kiba_User,并且数据也顺利的插入进了数据库表。
这样,我们的领域驱动框架就已经完成了雏形搭建,下一篇文章将进一步搭建,实现领域驱动独有的聚合。
----------------------------------------------------------------------------------------------------
框架代码已经传到Github上了,欢迎大家下载。
Github地址:https://github.com/kiba518/KibaDDD
----------------------------------------------------------------------------------------------------
注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!
若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!
【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇的更多相关文章
- .net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇
.net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 前言 .net core mvc和 .net mvc开发很相似,比如 视图-模型-控制器结构.所以. ...
- 实现领域驱动设计 - 使用ABP框架 - 什么是领域驱动设计?
前言: 最近看到ABP官网的一本电子书,感觉写的很好,翻译出来,一起学习下 (Implementing Domain Driven Design) https://abp.io/books DDD简介 ...
- 实现领域驱动设计 - 使用ABP框架 - 通用准则
在进入细节之前,让我们看看一些总体的 DDD 原则 数据库提供者 / ORM 无关性 领域和应用程序层应该与 ORM / 数据库提供程序 无关.它们应该只依赖于 Repository 接口,而 Rep ...
- 实现领域驱动设计 - 使用ABP框架 - 解决方案概览
.NET解决方案的分层 下图显示了使用ABP的 应用启动模板 创建的Visual Studio解决方案: 解决方案名称为问题跟踪,它由多个项目组成.通过考虑DDD原则以及开发和部署实践,该解决方案是分 ...
- 实现领域驱动设计 - 使用ABP框架 - 存储库
存储库 Repository 是一个类似于集合的接口,领域层和应用程序层使用它来访问数据持久性系统(数据库),以读写业务对象(通常是聚合) 常见的存储库原则是: 在领域层定义一个存储库接口(因为它被用 ...
- 实现领域驱动设计 - 使用ABP框架 - 创建实体
用例演示 - 创建实体 本节将演示一些示例用例并讨论可选场景. 创建实体 从实体/聚合根类创建对象是实体生命周期的第一步.聚合/聚合根规则和最佳实践部分建议为Entity类创建一个主构造函数,以保证创 ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(1)
好久没写 DDD 领域驱动设计相关的文章了,嘎嘎!!! 这几天在开发一个新的项目,虽然不是基于领域驱动设计的,但我想把 DDD 架构设计的一些东西运用在上面,但发现了很多问题,这些在之前的短消息项目中 ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(转)
http://www.cnblogs.com/xishuai/p/ddd-repository-iunitofwork-and-idbcontext.html 好久没写 DDD 领域驱动设计相关的文章 ...
- [转]领域驱动设计系列文章(2)——浅析VO、DTO、DO、PO的概念、区别和用处
原文地址:http://www.blogjava.net/johnnylzb/archive/2010/05/27/321968.html 上一篇文章作为一个引子,说明了领域驱动设计的优势,从本篇文章 ...
随机推荐
- 使用MongoDB存储集合的一些问题
这两天在工作中被Mongo集合存储给整得头大,当然也是我的认知太浅,所以下面我来分享下我所遇到的这个问题希望有大佬能给出更好的解决方案, 1.需求: 存储一个从前端接收未知数据类型的集合 例: 由于是 ...
- 银行卡号、电话号、身份证号 EditText 自定义格式的输入框
package com.yidian.AddSpaceEditText;import android.text.Editable;import android.text.InputFilter;imp ...
- Python_性能测试
使用pip安装Python扩展库memory_profiler from memory_profiler import profile @profile #修饰器 def isPrime(n): if ...
- day12 EL 表达式和国际化开发
day12 EL 表达式和国际化开发 1. EL(Expression Language) 表达式简介 1.1 执行运算 1.2 获取web开发常用对象(el 中定义了11个隐式对象) 1.3 使用 ...
- SqlSugar 盲点
1.读取数据库连接 private SqlSugarClient GetInstance() { string conmstring = System.Web.Configuration.WebCon ...
- springboot整合shiro后报java.lang.ClassCastExcepting异常
最近搭一个springboot的框架,整合了shiro和redis,由于平常习惯用热部署,所以自然的引入了热部署的包,但是引入后报如下错误: 没错,这个问题害我搞了一天,后来删除热部署,项目正常的跑飞 ...
- JFrame图形界面 ----鼠标消息
#开始 不管是什么GUI 按钮的存在都是必不可少的而且还会有很多奇怪的按钮 #代码 package window; import java.awt.Container; import java.awt ...
- 手动撸个Android雷达图(蜘蛛网图)RadarView
公司产品需要一个雷达图来展示各维度的比重,网上找了一波,学到不少,直接自己上手来撸一记 无图言虚空 简单分析一波,确定雷达图正几边形的--正五边形 int count=5,分为几个层数--4 层 in ...
- oracle中数据类型对应java类型
地址: http://otndnld.Oracle.co.jp/document/products/oracle10g/102/doc_cd/Java.102/B19275-03/datacc.htm ...
- Containerd 简介
我们可以把 docker 抽象为下图所示的结构(此图来自互联网): 从图中可以看出,docker 对容器的管理和操作基本都是通过 containerd 完成的. 那么,containerd 是什么呢? ...