前言
领域驱动设计, 其实已经是一个很古老的概念了, 但它的复杂度依旧让学习的人头疼不已.
互联网关于领域驱动的文章有很多, 每一篇写的都很好, 理解领域驱动设计的人都看的懂.
不过, 这些文章对于那些初学者而言, 还是如同天书一样.
买本驱动领域的书来看? 别逗了, 这可不是 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
----------------------------------------------------------------------------------------------------
来源: https://www.cnblogs.com/kiba/p/9953739.html