【我們一起寫框架】領域驅動設計的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

----------------------------------------------------------------------------------------------------

注:此文章為原創,任何形式的轉載都請聯系作者獲得授權並注明出處!
若您覺得這篇文章還不錯,請點擊下方的推薦】,非常感謝!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM