從壹開始微服務 [ DDD ] 之四 ║讓你明白DDD的小故事 & EFCore初探


緣起

哈嘍大家好喲,今天又到了老張的周二四放送時間了,當然中間還有不定期的更新(因為個人看papi醬看多了),這個主要是針對小伙伴提出的問題和優秀解決方案而寫的,經過上周兩篇DDD領域驅動設計的試水,我發現一個問題,這個DDD的水是真的深啊~或者來說就是這個思想的轉變是不舒服的,好多小伙伴就說有點兒轉不過來,當然我也是,一直站在原地追着影子跑,當然這個系列我會一直堅持下去的,大家如果感覺我寫的沒有誤人子弟或者感覺看着還有點兒意思,請不要着急,多多評論,我雖然沒有更新,但是也一直在線,提出來的問題可以一起討論,周末的時候,我又和“李大爺”一起從非專業的角度,從領域專家的角度思考了下DDD領域驅動設計的思想,感覺還有點兒領悟的,這里給大家分享下,如果你現在還對為什么使用DDD,或者還有DDD就像是一個三層架構或者MVC架構的想法的話,看完這一篇應該就能稍微的明白了。

很開森的是上周的問題大家評論很好,也上了24小時評論榜單,希望大家都可以多評論評論😀,真的很精彩,大家可以再去看看《二 ║ DDD入門 & 項目結構粗搭建》,評論席的內容的含金量,甚至都超過了我的正文內容,而且也能滿足老張小小的虛榮感,今天呢,我就先在本文的上半篇重點說一下大家最最最熱心的兩個問題,然后再繼續推進咱們的項目代碼,就主要從以下三個大塊鋪開來說:

1、DDD的意義到底在哪里?為什么很難理解?

2、為什么要使用倉儲,EFCore不就是一個倉儲么?

3、限界上下文如何定義呢?包含了平時遇到的哪些東西?

 

悄悄說:經過周末的討論,我發現上次咱們新建的那個關於 Customer 領域對象不好舉例子,問答程序說起來也不是很順口,所以我已經修改成了 Student 模型,然后我也想到了一個領域——教務系統,這個大家一定是熟悉的不能再熟悉了,每個小伙伴都是經過上學的噩夢里過來的(哈哈學霸就另說了),以后咱們就用這個教務領域來展開說明,大家也能都在一條思路上,而且也不會花心思去考慮問答系統這個不熟悉的領域。

 

零、今天要完成紫色的部分

 

一、舉一個DDD的小栗子 —— 大意義

關於DDD的使用,網上已經有很多的栗子了,無論是各種粘貼復制的教科書,還是自我的一些心得,基本已經說完了,不過我每次讀的時候,心里都是有點兒抗拒,一直都沒辦法看懂,今天我就決定用另一個辦法,來和大家好好說一下這個DDD領域驅動設計的意義到底在哪里。這個時候請你自己先想一想,如果使用DDD會有哪些好處,如果說看完了我寫的,感覺有共鳴,那很不錯,要是感覺我寫的認為不對,歡迎評論席留下你的意見喲,開源嘛,不能讓我自己發表看法的,也讓我的博客可以多在大家的面前展現下哈哈。

故事就從這里開始:咱們有一個學校,就叫從壹大學(我瞎起的名字哈哈),我們從壹大學要開發一套教務系統,這個系統涵蓋了學校的方方面面,從德智體美勞都有,其中就有一個管理后台,任何人都可以登錄進去,學習查看自己的信息和成績等,老師可以選擇課程或者修改自己班級的學生的個人信息的,現在就說其中的一個小栗子 —— 班主任更改學生的手機號。我們就用普通的寫法,就是我們平時在寫或者現在在用的流程來設計這個小方法。

請注意:當前系統就是一個 領域,里邊會有很多 子領域,這個大家應該都能懂。

 

1、后台管理,修改學生的手機號

這個方法邏輯很簡單,就是把學生的手機號更新一下就行,平時咱們一定是咣咣把數據庫建好,然后新建實體類,然后就開始寫這樣的一批方法了,話不多說,直接看看怎么寫(這是偽代碼):

/// <summary>
/// 后台修改學生手機號方法
/// </summary>
/// <param name="NewPhoNumber"></param>
/// <param name="StudentId"></param>
/// <param name="TeacherId"></param>
public void UpdateStudentPhone(string newPhoNumber,int studentId,int teacherId)
{
 
    //核心1:連數據,獲取學生信息,然后做修改,再保存數據庫。

  

}

 

這個方法特別正確,而且是核心算法,簡單來看,已經滿足我們的需求了,但是卻不是完整的,為什么呢,因為只要是管理系統涉及到的一定是有權限問題,然后我們就很開始和DBA討論增加權限功能。

請注意:這里說到的修改手機號的方法,就是我們之后要說到的領域事件,學生就是我們的領域模型,當然這里邊還有聚合根,值對象等等,都從這些概念中提煉出來。

2、為我們的系統增加一個剛需

剛需就是指必須使用到的一些功能,是僅此於核心功能的下一等級,如果按照我們之前的方法,我們就很自然的修改了下我們的方法。

故事:領導說,上邊的方法好是好,但是必須增加一個功能強大的權限系統,不僅能學生自己登錄修改,還可以老師,教務處等等多方修改,還不能沖突,嗯。

/// <summary>
/// 后台修改學生手機號方法
/// </summary>
/// <param name="NewPhoNumber"></param>
/// <param name="StudentId"></param>
/// <param name="TeacherId"></param>
public void UpdateStudentPhone(string newPhoNumber,int studentId,int teacherId)
{
    //重要2:首先要判斷當然 Teacher 是否有權限(比如只有班主任可以修改本班) //注意這個時候已經把 Teacher 這個對象,給悄悄的引進來了。 //------------------------------------------------------------

    //核心:連數據,獲取學生信息,然后做修改,再保存數據庫。

  


}

這個時候你一定會說我們可以使用JWT這種呀,當然你說的對,是因為咱們上一個系列里說到這個了,這個也有設計思想在里邊,今天咱們就暫時先用平時咱們用到的上邊這個方法,集成到一起來說明,只不過這個時候我們發現我們的的領域里,不僅僅多了 Teacher 這個其他模型,而且還多了與主方法無關,或者說不是核心的事件。

這個時候,我們在某些特定的方法里,已經完成權限,我們很開心,然后交給學校驗收,發現很好,然后就上線了,故事的第一篇就這么結束了,你會想,難道還有第二篇么,沒錯!事務總是源源不斷的的進來的,請耐心往下看。

請注意:這個權限問題就是 切面AOP 編程問題,以前已經說到了,這個時候你能想到JWT,說明很不錯了,當然還可以用Id4等。

 

3、給系統增加一個事件痕跡存儲

這個不知道你是否能明白,這個說白了就是操作日志,當然你可以和錯誤日志呀,接口訪問日志一起聯想,我感覺也是可以的,不過我更喜歡把它放在事件上,而不是日志這種數據上。

故事:經過一年的使用,系統安靜平穩,沒有bug,一切正常,但是有一天,學生小李自己換了一個手機號,然后就去系統修改,竟然發現自己的個人信息已經被修改了(是班主任改的),小李很神奇這件事,然后就去查,當然是沒有記錄的,這個時候反饋給技術部門,領導結合着其他同學的意見,決定增加一個痕跡歷史記錄頁,將痕跡跟蹤提上了日程。我們就這么開發了。

/// <summary>
/// 后台修改學生手機號方法
/// </summary>
/// <param name="NewPhoNumber"></param>
/// <param name="StudentId"></param>
/// <param name="TeacherId"></param>
public void UpdateStudentPhone(string newPhoNumber,int studentId,int teacherId)
{
    //重要:首先要判斷當然 Teacher 是否有權限(比如只有班主任可以修改本班)
    //注意這個時候已經把 Teacher 這個對象,給悄悄的引進來了。

    //------------------------------------------------------------

    //核心:連數據,或者學生信息,然后做修改,再保存數據庫。

    //------------------------------------------------------------

    //協同3:痕跡跟蹤(你可以叫操作日志),獲取當然用戶信息,和老師信息,連同更新前后的信息,一起保存到數據庫,甚至是不同的數據庫地址。 //注意,這個是一個突發的,項目上線后的需求


}

 
這個時候你可能會說,這個項目太假了,不會發生這樣的事情,這些問題都應該在項目開發的時候討論出來,並解決掉,真的是這樣的么,這樣的事情多么常見呀,我們平時開發的時候,就算是一個特別成熟的領域,也會在項目上線后,增加刪除很多東西,這個只是一個個例,大家聯想下平時的工作即可。

這個時候如果我們還采用這個方法,你會發現要修改很多地方,如果說我們只有幾十個方法還行,我們就粘貼復制十分鍾就行,但是我們項目有十幾個用戶故事,每一個故事又有十幾個到幾十個不等的用例流,你想想,如果我們繼續保持這個架構,我們到底應該怎么開發,可能你會想到,還有權限管理的那個AOP思想,寫一個切面,可是真的可行么,我們現在不僅僅要獲取數據前和數據后兩塊,還有用戶等信息,切面我感覺是很有困難的,當然你也好好思考思考。

這個時候你會發現,咱們平時開發的普通的框架已經支撐不住了,或者是已經很困難了,一套系統改起來已經過去很久了,而且不一定都會修改正確,如果一個地方出錯,當前方法就受影響,一致性更別說了,試想下,如果我們開發一個在線答題系統,就因為記錄下日志或者什么的,導致結果沒有保存好,學生是會瘋的。第二篇就這么結束了,也許你的耐心已經消磨一半了,也許我們以為一起安靜的時候,第三個故事又開始了。

請注意:這個事件痕跡記錄就涉及到了 事件驅動 和 事件源 相關問題,以后會說到。

 

4、再增加一個站內通知業務

 故事:我們從壹大學新換了一個PM,嗯,在數據安全性,原子性的同時,更注重大家信息的一致性 —— 任何人修改都需要給當前操作人,被操作人,管理員或者教務處發站內消息通知,這個時候你會崩潰到哭的。

/// <summary>
/// 后台修改學生手機號方法
/// </summary>
/// <param name="NewPhoNumber"></param>
/// <param name="StudentId"></param>
/// <param name="TeacherId"></param>
public void UpdateStudentPhone(string newPhoNumber,int studentId,int teacherId)
{
    //重要:首先要判斷當然 Teacher 是否有權限(比如只有班主任可以修改本班)
    //注意這個時候已經把 Teacher 這個對象,給悄悄的引進來了。

    //------------------------------------------------------------

    //核心:連數據,或者學生信息,然后做修改,再保存數據庫。

    //------------------------------------------------------------

    //協同:痕跡跟蹤(你可以叫操作日志),獲取當然用戶信息,和老師信息,連同更新前后的信息,一起保存到數據庫,甚至是不同的數據庫地址。
    //注意,這個是一個突發的,項目上線后的需求

    //------------------------------------------------------------

    //協同4:消息通知,把消息同時發給指定的所有人。


}

這個時候我就不具體說了,相信都已經離職了吧,可是這種情況就是每天都在發生。

請注意:上邊咱們這個偽代碼所寫的,就是DDD的 通用領域語言,也可以叫 戰略設計

 

5、DDD領域驅動設計就能很好的解決

上邊的這個問題不知道是否能讓你了解下軟件開發中的痛點在哪里,二十年前 Eric Evans 就發現了,並提出了領域驅動設計的思想,就是通過將一個領域進行划分成不同的子領域,各個子領域之間通過限界上下文進行分隔,在每一個限界上下文中,有領域模型,領域事件,聚合,值對象等等,各個上下文互不沖突,互有聯系,保證內部的一致性,這些以后會說到。

如果你對上下文不是很明白,你可以暫時把它理解成子領域,領域的概念是從戰略設計來說的,上下文這些是從戰術設計上來說的。

具體的請參考我的上一篇文章《三 ║ 簡單說說:領域、子域、限界上下文

你也許會問,那我們如何通過DDD領域驅動設計來寫上邊的修改手機號這個方法呢,這里簡單畫一下,只是說一個大概意思,切分領域以后,每一個領域之間互不聯系,有效的避免了牽一發而動全身的問題,而且我們可以很方便進行擴展,自定義擴展上下文,當然如果你想在教學子領域下新增一個年級表,那就不用新建上下文了,直接在改學習上下文中操作即可,具體的代碼如何實現,咱們以后會慢慢說到。

 

總結:這個時候你通過上邊的這個栗子,不知道你是否明白了,我們為什么要在大型的項目中,使用DDD領域設計,並配合這CQRS和事件驅動架構來搭建項目了,它所解決的就是我們在上邊的小故事中提到的隨着業務的發展,困難值呈現指數增長的趨勢了。

 

二、一個安靜的數據管理員 —— 倉儲

這里就簡單的說兩句為什么一直要使用倉儲,而不直接接通到 EFCore 上:

1、我們驅動設計的核心是什么,就是最大化的解決項目中出現的痛點,上邊的小故事就是一個栗子,隨着技術的更新,面向接口開發同時也變的特別重要,無論是方便重構,還是方便IoC,依賴注入等等,都需要一個倉儲接口來實現這個目的。

2、倉儲還有一個重要的特征就是分為倉儲定義部分和倉儲實現部分,在領域模型中我們定義倉儲的接口,而在基礎設施層實現具體的倉儲。

這樣做的原因是:由於倉儲背后的實現都是在和數據庫打交道,但是我們又不希望客戶(如應用層)把重點放在如何從數據庫獲取數據的問題上,因為這樣做會導致客戶(應用層)代碼很混亂,很可能會因此而忽略了領域模型的存在。所以我們需要提供一個簡單明了的接口,供客戶使用,確保客戶能以最簡單的方式獲取領域對象,從而可以讓它專心的不會被什么數據訪問代碼打擾的情況下協調領域對象完成業務邏輯。這種通過接口來隔離封裝變化的做法其實很常見,我們需要什么數據直接拿就行了,而不去管具體的操作邏輯。

3、由於客戶面對的是抽象的接口並不是具體的實現,所以我們可以隨時替換倉儲的真實實現,這很有助於我們做單元測試。

 

總結:現在隨着開發,越來越發現接口的好處,不僅僅是一個持久化層需要一層接口,小到一個緩存類,或者日志類,我們都需要一個接口的實現,就比如現在我就很喜歡用依賴注入的方式來開發,這樣可以極大的減少依賴,還有增大代碼的可讀性。

 

三、建立我們第一個限界上下文

限界上下文已經說的很明白了,是從戰術技術上來解釋說明戰略中的領域概念,你想一下,我們如何在代碼中直接體現領域的概念?當然沒辦法,領域是一個通過語言,領域專家和技術人員都能看懂的一套邏輯,而代碼中的上下文才是實實在在的通過技術來實現。

大家可以在回頭看看上邊的那個故事栗子,下邊都一個“請注意”三個字,里邊就是我們上下文中所包含的部分內容,其實限界上下文並沒有想象中的那么復雜,我們只需要理解成是一個虛擬的邊界,把不屬於這個子領域的內容踢出去,對外解耦,但是內部通過聚合的。

0、在基礎設施層下新建一個 appsetting.json 配置文件

用於我們的特定的數據庫連接,當然我們可以公用 api 層的配置文件,這里單獨拿出來,用於配合着下邊的EFCore,進行注冊。

{
  "ConnectionStrings": {
    "DefaultConnection": "server=.;uid=sa;pwd=123;database=EDU"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

 

1、新建系統核心上下文

在Christ3D.Infrastruct.Data 基礎設施數據層新建 Context 文件夾,以后在基礎設施層的上下文都在這里新建,比如事件存儲上下文(上文中存儲事件痕跡的子領域),

然后新建教務領域中的核心子領域——學習領域上下文,StudyContext.cs,這個時候你就不用問我,為啥在教務系統領域中,學習領域是核心子領域了吧。

  /// <summary>
    /// 定義核心子領域——學習上下文
    /// </summary>
    public class StudyContext : DbContext
    {
        public DbSet<Student> Students { get; set; }

        /// <summary>
        /// 重寫自定義Map配置
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfiguration(new StudentMap());
                        
            base.OnModelCreating(modelBuilder);
        }

        /// <summary>
        /// 重寫連接數據庫
        /// </summary>
        /// <param name="optionsBuilder"></param>
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            // 從 appsetting.json 中獲取配置信息
            var config = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();

            // 定義要使用的數據庫
            optionsBuilder.UseSqlServer(config.GetConnectionString("DefaultConnection"));
        }
    }

在這個上下文中,有領域模型 Student ,還有以后說到的聚合,領域事件(上文中的修改手機號)等。

以后大家在遷移數據庫的時候,可能會遇到問題,因為本項目有兩個上下文,大家可以指定其中的操作

 

 

 

2、引入我們的ORM框架 —— EFCore

這里邊有三個 Nuget 包,

Microsoft.EntityFrameworkCore//EFCore核心包
Microsoft.EntityFrameworkCore.SqlServer//EFCore的SqlServer輔助包
Microsoft.Extensions.Configuration.FileExtensions//appsetting文件擴展包
Microsoft.Extensions.Configuration.Json//appsetting 數據json讀取包

 

這里給大家說下,如果你不想通過nuget管理器來引入,因為比較麻煩,你可以直接對項目工程文件 Christ3D.Infrastruct.Data.csproj 進行編輯 ,保存好后,項目就直接引用了

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\Christ3D.Domain\Christ3D.Domain.csproj" />
  </ItemGroup>

  //就是下邊這一塊
  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.0-preview3-35497" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.2.0-preview3-35497" />
    <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.2.0-preview3-35497" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0-preview3-35497" />
  </ItemGroup>
  //就是上邊這些
  
</Project>

 

 

 

3、添加我們的實體Map

 Christ3D.Infrastruct.Data 基礎設施數據層新建 Mappings 文件夾,以后在基礎設施層的map文件都在這里建立,

然后新建學生實體map,StudentMap.cs

/// <summary>
    /// 學生map類
    /// </summary>
    public class StudentMap : IEntityTypeConfiguration<Student>
    {
        /// <summary>
        /// 實體屬性配置
        /// </summary>
        /// <param name="builder"></param>
        public void Configure(EntityTypeBuilder<Student> builder)
        {
            builder.Property(c => c.Id)
                .HasColumnName("Id");

            builder.Property(c => c.Name)
                .HasColumnType("varchar(100)")
                .HasMaxLength(100)
                .IsRequired();

            builder.Property(c => c.Email)
                .HasColumnType("varchar(100)")
                .HasMaxLength(11)
                .IsRequired();
        }
    }

 

4、用EFCore來完成基類倉儲實現類

 將我們剛剛創建好的上下文注入到基類倉儲中

 

/// <summary>
    /// 泛型倉儲,實現泛型倉儲接口
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        protected readonly StudyContext Db;
        protected readonly DbSet<TEntity> DbSet;

        public Repository(StudyContext context)
        {
            Db = context;
            DbSet = Db.Set<TEntity>();
        }

        public virtual void Add(TEntity obj)
        {
            DbSet.Add(obj);
        }

        public virtual TEntity GetById(Guid id)
        {
            return DbSet.Find(id);
        }

        public virtual IQueryable<TEntity> GetAll()
        {
            return DbSet;
        }

        public virtual void Update(TEntity obj)
        {
            DbSet.Update(obj);
        }

        public virtual void Remove(Guid id)
        {
            DbSet.Remove(DbSet.Find(id));
        }

        public int SaveChanges()
        {
            return Db.SaveChanges();
        }

        public void Dispose()
        {
            Db.Dispose();
            GC.SuppressFinalize(this);
        }
    }

 

 

5、完善實現應用層Service方法

這個時候我們知道,因為我們的應用層的模型的視圖模型 StudentViewModel ,但是我們的倉儲接口使用的是 Student 業務領域模型,這個時候該怎么辦呢,聰明的你一定會想到咱們在上一個系列中所說到的兩個知識點,1、DTO的Automapper,然后就是2、引用倉儲接口的 IoC 依賴注入,咱們今天就先簡單配置下 DTO。這兩個內容如果不是很清楚,可以翻翻咱們之前的系列教程內容。

 

1、在應用層,新建 AutoMapper 文件夾,我們以后的配置文件都放到這里,新建DomainToViewModelMappingProfile.cs

 /// <summary>
 /// 配置構造函數,用來創建關系映射
 /// </summary>
 public DomainToViewModelMappingProfile()
 {
     CreateMap<Student, StudentViewModel>();
 }

這些代碼你一定很熟悉的,這里就不多說了,如果一頭霧水請看我的第一個系列文章吧。

2、完成 StudentAppService.cs 的設計

namespace Christ3D.Application.Services
{
    /// <summary>
    /// StudentAppService 服務接口實現類,繼承 服務接口
    /// 通過 DTO 實現視圖模型和領域模型的關系處理
    /// 作為調度者,協調領域層和基礎層,
    /// 這里只是做一個面向用戶用例的服務接口,不包含業務規則或者知識
    /// </summary>
    public class StudentAppService : IStudentAppService
    {
        //注意這里是要IoC依賴注入的,還沒有實現
        private readonly IStudentRepository _StudentRepository;
        //用來進行DTO
        private readonly IMapper _mapper;

        public StudentAppService(
            IStudentRepository StudentRepository,
            IMapper mapper
            )
        {
            _StudentRepository = StudentRepository;
            _mapper = mapper;
        }

        public IEnumerable<StudentViewModel> GetAll()
        {

            //第一種寫法 Map
            return _mapper.Map<IEnumerable<StudentViewModel>>(_StudentRepository.GetAll());

            //第二種寫法 ProjectTo
            //return (_StudentRepository.GetAll()).ProjectTo<StudentViewModel>(_mapper.ConfigurationProvider); }
public StudentViewModel GetById(Guid id) { return _mapper.Map<StudentViewModel>(_StudentRepository.GetById(id)); } public void Register(StudentViewModel StudentViewModel) { //判斷是否為空等等 還沒有實現 _StudentRepository.Add(_mapper.Map<Student>(StudentViewModel)); } public void Update(StudentViewModel StudentViewModel) { _StudentRepository.Update(_mapper.Map<Student>(StudentViewModel)); } public void Remove(Guid id) { _StudentRepository.Remove(id); } public void Dispose() { GC.SuppressFinalize(this); } } }

 

6、思考:這樣就是DDD領域驅動設計了么

好啦,其實這個時候,我們的接口已經可以使用了,可能還有些注入呀,沒有實現,但是基本的邏輯就這么施行了,你一定看着很熟悉,無論是DTO還是IOC,無論是EFCore還是倉儲,一切都那么熟悉,但是這就是DDD領域驅動設計么,你一定要帶着這個問題好好想想。答案當然是否定的。 

到這里,我們的核心學習子領域的上下文的創建已經完成,請注意,這是上下文的定義創建完成,里邊的核心內容還沒有說到。

當然,我們在完成應用層的調用后,直接就可以用了,這個時候的你可能會發現,到目前為止,咱們還是一個普通的寫法,和我們上個系列是一樣的,沒有體現出哪里使用了領域驅動設計的思想,無非就是引用了EFCore和定義了一個上下文。

沒錯,你說的是對的,目前為止還沒有實現領域設計的核心,但是至少我們已經把領域給划分出來了,而且你如何看明白了上邊的我說的內容,也應該有一定的想法了,明天咱們就重點說說領域事件聚合的相關概念。 

 

四、結語

 今天重點重申了下DDD的意義,簡單說明了下倉儲的設計思想,然后也將我們的項目引入EFCore,並實現了接口等。這里我要說明三點,看看大家讀完這篇文章的心情屬於哪一種:

1、入門:如果你看到我上邊的小故事,還對為什么使用DDD而疑惑,那就請再仔細看看,好好想想。不要往下看,就看第一部分。

2、了解:如果你看懂了我說的第一部分的意思,並了解了使用領域驅動設計的意義,但是看下邊第三部分的代碼結構又好像和平時的多層設計很像,而又去和多層對比,那麻煩請結合我的Git代碼看看。

3、優秀:如果你明白了DDD的意義,並且很想了解我的架構到底是如何進行領域驅動的,恭喜你,已經成功了,剩下的時間我就會帶你去深入了解 中介者模式下的事件驅動——CQRS

 

 核心內容要來了,你准備好了么 【機智表情】

 

五、Github & Gitee

https://github.com/anjoy8/ChristDDD

https://gitee.com/laozhangIsPhi/ChristDDD 


免責聲明!

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



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