前言
領域驅動設計,其實已經是一個很古老的概念了,但它的復雜度依舊讓學習的人頭疼不已。
互聯網關於領域驅動的文章有很多,每一篇寫的都很好,理解領域驅動設計的人都看的懂。
不過,這些文章對於那些初學者而言,還是如同天書一樣。
買本驅動領域的書來看?別逗了,這可不是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
----------------------------------------------------------------------------------------------------
注:此文章為原創,任何形式的轉載都請聯系作者獲得授權並注明出處!
若您覺得這篇文章還不錯,請點擊下方的【推薦】,非常感謝!