【我們一起寫框架】領域驅動設計的CodeFirst框架(一)—序篇

来源:https://www.cnblogs.com/kiba/archive/2018/12/03/9953739.html
-Advertisement-
Play Games

前言 領域驅動設計,其實已經是一個很古老的概念了,但它的複雜度依舊讓學習的人頭疼不已。 互聯網關於領域驅動的文章有很多,每一篇寫的都很好,理解領域驅動設計的人都看的懂。 不過,這些文章對於那些初學者而言,還是如同天書一樣。 買本驅動領域的書來看?別逗了,這可不是C#語法入門,哪裡有書能寫明白的。 想 ...


前言

領域驅動設計,其實已經是一個很古老的概念了,但它的複雜度依舊讓學習的人頭疼不已。

互聯網關於領域驅動的文章有很多,每一篇寫的都很好,理解領域驅動設計的人都看的懂。

不過,這些文章對於那些初學者而言,還是如同天書一樣。

買本驅動領域的書來看?別逗了,這可不是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

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

註:此文章為原創,歡迎轉載,請在文章頁面明顯位置給出此文鏈接!
若您覺得這篇文章還不錯,請點擊下右下角的推薦】,非常感謝!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 協程: 協程,又稱微線程,纖程。英文名Coroutine。 可以在不同的函數間切換,而且切換的次數和時間都是由用戶自己確定的。 協程的幾種實現方式: (1)使用生成器yield實現。 如果不瞭解生成器可以閱讀我的另一篇文章:python生成器的簡單理解 下麵就來寫一個簡單的協程程式。 這段代碼的執行 ...
  • Java開發學習心得(二):Mybatis和Url路由 序號接上一篇 "Java開發學習心得(一):SSM環境搭建" 1.3 Mybatis MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google ...
  • 用JavaPOI導出Excel時,我們會考慮到Excel版本及數據量的問題。針對不同的Excel版本,要採用不同的工具類。HSSFWorkbook:是操作Excel2003以前(包括2003)的版本,擴展名是.xls;XSSFWorkbook:是操作Excel2007的版本,擴展名是.xlsx。用過... ...
  • 同步/非同步(描述網路通信模式,適用於請求 響應模型) 同步:發送方發送請求後,需要等待接收響應,結果占用並浪費了CPU資源 非同步:發送方發送請求後,不需要響應,可以繼續發送下一個請求,或者主動掛起線程並釋放CPU資源 阻塞/非阻塞(描述進程的函數方法調用方式) 阻塞:IO 調用會一直阻塞,直至調用結 ...
  • random 模塊 隨機:在某個範圍內取到每一個值的概率是相同的 練習:生成隨機驗證碼 # (1)4位數字的驗證碼 # 基礎版本 lis = '' for i in range(4): num = random.randint(0, 9) lis += str(num) print(lis) # 函 ...
  • 1.什麼是生成器 通過列表生成式,我們可以直接創建一個列表。但是,受到記憶體限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素占用的空間都白白浪費了。所以生成器就出現了,他彌補了直接生成大列表的不足,改成為定 ...
  • 案例 將一個 pdf 文件按要求分割為幾個部分。比如說一個pdf有20頁,分成5個pdf文件,每個pdf文件包含4頁。設計函數實現? Python代碼 函數講解 本函數是自己測試通過的函數,還有待優化。輸入參數有,將要分割的PDF文件,分割為幾個pdf文件,每個PDF文件頁數。 測試結果 原來只有一 ...
  • spring Aop的配置一定要配置在springmvc配置文件中 springMVC.xml1 <!-- AOP 註解方式 ;定義Aspect --> 2 <!-- 激活組件掃描功能,在包com.ly.aop.aspect及其子包下麵自動掃描通過註解配置的組件--> 3 <context:comp ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...