從壹開始微服務 [ DDD ] 之三 ║ 簡單說說:領域、子域、限界上下文
前言
哈嘍大家好,DDD領域驅動設計系列又開始了,前天週二的那篇入門文章中,也收到了一定的效果(寫小說的除外),同時我也是倍感鴨梨,怎麼說呢,DDD領域驅動設計已經有十年曆史了,甚至更久,但是包括我在內的一批技術人員還是對其不是很明白,這幾天我也是日思夜想,怎樣才能說的明白,怎樣才能把這個高高在上的思想落在實踐上,可惜的是國內栗子比較少,國外文章比較少,只能硬啃了,所以更需要大家一起來討論,這裡要說一下,是一起討論推動,而不是內心去拒絕,而一直和多層架構做對比,這樣不僅不利於學習,也無法帶動我的積極性,所以,這裡懇請大家,多多評論,多多交流,比較我一個人很難扛得動這個DDD的大旗。
好啦,言歸正傳,上次咱們說到了《
這裡先給大家提一個問題,如果一個新的專案,比如一個小的問答系統交給你的手裡,PM 剛剛和你簡單的討論了下需求,下一步你打算做些什麼?
1、根據需求,立刻準備設計資料庫,建表,腦中模擬場景;
2、根據需求,立刻建立實體類(也就是model層),然後CodeFirst 生成資料庫;
3、找尋該領域專家(做過或者懂得類似產品的人),設計該問答領域下,有哪些子領域,製作限界上下文;
4、啥都沒有,直接網上找開源專案,下載下來看看;
老張說:這裡沒有正確與否的比較,只是一個習慣和優劣的分析,不用太在意,如果你比較好奇,那就往下看吧。
零、今天要完成綠色的部分
一、領域 —— 就是一個獨立專案
1、領域的概念
這個概念相信很多人已經很明白了,而且也聽到了無數遍,這裡就再簡單的說兩句:
領域(Domain)其實就是一個組織所要做的整個事情,已經這個事情下所包含的一切內容。這是一個範圍概念,而且是面向業務的(注意這裡不是面試技術的,更不是面向資料庫的持久化的),每個組織都有自己的人員、自己的工作業務範圍和做事方式,當你為該組織開發軟體的時候,你面對的就是這個組織的領域。
就比如之前我在一家旅遊公司進行開發工作,那我所進行的開發工作就是一個旅遊行業,我必須要很清晰旅遊行業的其中的領域知識,而且必須能和領域專家通過通用性語言進行溝通,這樣能保證我開發出來的是他們想要的,而不是我單純的從技術上實現,在領域設計上一塌糊塗。當然我們每天也都在做這樣的事情,也許你感覺很正常,那我再舉個例子:
我在開發其中一個目的地(旅遊景點)專案的時候,這是一個領域,後來在電商系統專案中,又是一個領域,但是在電商領域中,涉及到了景點領域的一些資料,那我如果不和領域專家溝通,有時候為了貪圖技術上的方便,甚至把兩個領域合併成一個,雖然都不大,合併以後大小也還可以,但是這樣卻完全打破了領域的這個概念,這個就是完全面向技術開發的,因為領域專家看不懂我這麼寫到底屬於什麼。
當然上邊的栗子有點兒牽強,咱們再說下以後我想做的一個基於DDD的問答專案,咱們先畫一個框。
就如圖所示,咱們首先定義一個邊界,至於裡邊有什麼東西,咱們接著往下看,這個很簡單。
2、如何定義一個領域
這個是更簡單的一個問題,在領域設計中,有兩個方法:戰略設計和戰術設計,其實我個人感覺可以定義兩步走,這兩個是有先後之分的,
戰略設計中定義了,一個領域就是一個問題空間,我們在業務中所遇到的所有的問題與挑戰;
在戰術設計中,一個領域就算一個解決問題空間,用來解決在問題空間的所有問題;
所以,其實一個領域就是一個我們建立的一個解決方案,一個專案,在我們的問答專案中,整個解決方案就是一個問答領域。
二、子領域 —— 具體的專案實現
1、子域 / 核心子領域 / 通用子領域
什麼是子域(SubDomain)呢?這個很好理解,就是在整個領域中,我們如何對其進行拆分,然後滿足我們的業務邏輯。一個子域可能是一個 dll ,一個名稱空間的形式存在。
我們定義好領域,並且劃分好限界後,就開始考慮如何進行實現,這裡大家想一想如何設計與劃分,這裡就說說我自己的之前的想法:
在我們的問答領域設計中,我們的思路一定是有客戶來 =》驗證是否有發問題的許可權 =》 然後釋出一個問題 =》
這僅僅是一個釋出問題的流程,也僅僅是一個顧客認證的過程,很簡單,我們一般會怎麼分子領域呢,可能會這麼分,這個就是 訊息釋出子領域,裡邊有我們的釋出模型,使用者模型,討論模型,日曆模型等等,大概就是這個樣子
因為我們會這麼想:“使用者和許可權這兩個模型,和我們的訊息子領域有何緊密的關係,你看,釋出+回覆+討論+日曆(指自己新建一個日曆功能,具體待定),這些模型肯定都需要使用者登陸認證吧,甚至有些是需要授權的,分在一個子領域有什麼不對麼?”,這樣的程式碼邏輯應該是這樣的
如果是你看到這裡,首先明白了什麼是子領域了吧,也知道如何劃分了,但是你感覺這個劃分對麼? 如果你感覺很正常,那就請往下看吧。
2、核心子領域 / 通用子領域 / 支撐子領域
我們再來分析一下,我們的問答領域中的有哪些內容,首先:肯定有訊息釋出子領域,這個也是上邊說到的,這個毋庸置疑,一個問答系統,訊息釋出是肯定的(這裡說明下:釋出問題,回答問題,討論問題等都屬於一個訊息的釋出,這個應該理解),而且這個子領域是缺少它不可的,這個就是我們的核心子領域。
再來看看,還有一些其他的,比如日誌記錄,資料操作痕跡記錄(哪個管理員修改了哪些資料),這些子領域貫穿著我們真個領域系統,被其他領域共用,我們稱之為 通用子領域,
當然,我們還有一些站內的即時訊息,wiki百科,通知提醒,活動跟蹤,等等,這些都不是我們的核心子域,因為沒有這些,我們依然可以進行問答,但是這些確是支撐著我們核心子域的相關功能,我們就把這些命名為 支撐子領域,這個時候你會問,這些支援子領域要不要再拆開,我個人表示沒有很大的必要。
最後我們再來看看我們上邊的使用者認證授權問題,在上邊我們把他們柔和到了訊息核心子域裡,但是這裡要說明,這兩者是沒有關係的:
為什麼沒有關係呢?誠然,我們的軟體是必須有使用者參與的,但是我們應該將不同的使用者種類區別對待,因為在不同的上下文(下邊會說到)中,他們的作用和任務是不一樣的,在訊息核心子域中,我們關注的是角色,不管他是誰或者有什麼許可權,如果我們有一天把許可權模型修改了,那我們的問答模型也一定要修改,你想想是不是,因為兩者業務邏輯已經耦合了!
這個時候我們應該明白,釋出資訊和“誰可以發,在什麼條件下發”其實沒有太大的關係,我的問答,只關心的是“有一個顧客釋出了一個問題”這樣就可以了,我們關心的是釋出訊息這個過程,而不能把使用者許可權涉及進來,這個時候我們應該把使用者許可權單拿出來一個子領域,就叫安全子領域。
3、隔離核心
其實上邊說的可能有點兒朦朧,但是我們應該都已經用到了,如果你看了我的上一個系列教程,你應該知道有一個JWT許可權驗證那一章節,很多人就是不很理解,是如何進行授權驗證的,其實採用的就是隔離內容,以前我們寫邏輯,就算直接在控制器裡,判斷當前使用者許可權,但是現在我們是通過一箇中間件,判斷 Token 所包含的使用者Role 是否有這個許可權,再進行下一步,只不過在DDD中,把這一塊單拿出來形成了一個安全子領域了,這個時候你應該明白了吧。
/// <summary> /// 刪除一個顧客資訊 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpPost, ActionName("Delete")] [Authorize(Policy = "CanRemoveCustomerData")] [Route("customer-management/remove-customer/{id:guid}")] [ValidateAntiForgeryToken] public IActionResult DeleteConfirmed(Guid id) { _customerAppService.Remove(id); if (!IsValidOperation()) return View(_customerAppService.GetById(id)); ViewBag.Sucesso = "Customer Removed!"; return RedirectToAction("Index"); }
這個時候,可能還不是很明白,為什麼好好的程式要拆分,這麼做的目的又是為了什麼,直接在需要用到的許可權的地方寫業務邏輯不就行了麼,這個往下看,咱們說說限界上下文。
三、限界上下文 —— 領域模型的邊界
1、限界上下文是顯示的,有語義的
限界上下文(Bounded Context)定義了每個模型的應用範圍,在每個Bounded Context中確保領域模型的一致性。不同的限界上下文中,領域模型可以不用保證一致性。通常我們根據團隊的組織、軟體系統的每個部分的用法及物理表現(如元件劃分,資料庫模式)來設定模型的邊界。
概念還是有點兒朦朧,那就舉例來說:
在電商系統中,銷售子域是核心域,商品子域和物流子域為支撐子域。在這三個子域中,都要和商品打交道。如果把商品抽象為Product物件的話,按我們一般的常規思路(拋開子域的劃分)來說,不管是商品銷售還是發貨,我們都可以共用同一個Product物件。
但在DDD中,在商品子域和銷售子域中,可以共享這個Product物件,但在物流子域,就有點大材小用。為什麼呢?因為畢竟物流子域關注的是商品的發貨處理和物流跟蹤。針對發貨流程而言,我只關心商品的數量、大小、重量等規格,而不必瞭解商品的價格等其他資訊。所以說物流子域應該關注的是貨物的發貨處理而不是商品。
那為什麼我們之前的開發思路會共用同一個Product物件呢?
答案很簡單,沒有進行領域的劃分。把整個專案一概而論,統一建模導致的結果。
在DDD的思想下,當劃分子域之後,每個子域都對應有各自的上下文。在銷售子域和商品子域所在的上下文語境中,商品就是商品,無二義性。在物流子域的上下文語境中,我們也可以說商品的發貨處理,但這時的商品就特指貨物了。確定了真實面目之後,我想我們也會不由自主的抽象一個新的Cargo物件來處理物流相關的業務。這也是DDD帶來的好處,讓我們更清晰的建模。
2、定義限界上下文
在我們上邊的子域定義中,我們出現了三個子域,我這裡同時也定義了三個限界上下文(這裡說下,兩者不是一對一的關係),總體來說,我們不應該按技術架構或者開發任務來建立限界上下文,應該按照語義的邊界來考慮。
我們的實踐是,考慮產品所講的通用語言,從中提取一些術語稱之為概念物件,尋找物件之間的聯絡;或者從需求裡提取一些動詞,觀察動詞和物件之間的關係;我們將緊耦合的各自圈在一起,觀察他們內在的聯絡,從而形成對應的界限上下文。形成之後,我們可以嘗試用語言來描述下界限上下文的職責,看它是否清晰、準確、簡潔和完整。簡言之,限界上下文應該從需求出發,按領域劃分。
3、上下文都包含哪些內容
一個限界上下文不是隻有領域模型,當然這個是必不可少的,它總體來說是一個系統,一個應用程式,或者一個業務服務,它裡邊會有實體,值物件,領域事件(一個個方法事件組成,比如使用者註冊,修改密碼,驗證資訊等等都是該上下文中的領域事件),在我們的身份和訪問上下文中,是這樣定義的
感覺寫到這裡還是沒有寫的很透徹,因為我們還沒有涉及到程式碼,可能通過程式碼的設計會比較好。
四、結語
本文主要是通過DDD領域設計的思想,來說明如何對一個專案進行細分的過程,這個再想想文章開頭提出的問題,是不是稍微有些感觸,只不過在沒有程式碼的講解下,一起總是很空洞,下次咱們直接通過基礎設施層中的上下文定義,來進一步瞭解領域設計的思想吧。