設計模式之美 · 實戰篇
實戰篇
領域驅動設計(Domain Driven Design,簡稱 DDD)
領域驅動設計,主要是用來指導如何解耦業務系統,劃分業務模組,定義業務領域模型及其互動。
做好領域驅動設計的關鍵是,看你對自己所做業務的熟悉程度,而並不是對領域驅動設計這個概念本身的掌握程度。即便你對領域驅動搞得再清楚,但是對業務不熟悉,也並不一定能做出合理的領域設計。所以,不要把領域驅動設計當銀彈,不要花太多的時間去過度地研究它。
貧血模型
只包含資料,不包含業務邏輯的類,都是基於貧血模型設計的.
Q:為什麼不符合以上條件的類都是面向過程的程式設計風格? A:其破壞了面向物件的封裝特性,是一種典型的面向過程的程式設計風格. Q:為什麼破壞了面向物件的封裝特性? A:封裝特性,又叫作 資訊隱藏和資料訪問保護,當前類由於資料跟業務分離,當前類沒有實現資料訪問保護的功能,可以被service 類任意修改.
基於貧血模型的傳統開發模式
MVC 三層架構中的 M 表示 Model,V 表示 View,C 表示 Controller。它將整個專案分為三層:展示層、邏輯層、資料層。MVC 三層開發架構是一個比較籠統的分層方式,落實到具體的開發層面,很多專案也並不會 100% 遵從 MVC 固定的分層方式,而是會根據具體的專案需求,做適當的調整。比如,現在很多 Web 或者 App 專案都是前後端分離的,後端負責暴露介面給前端呼叫。這種情況下,我們一般就將後端專案分為 Repository 層、Service 層、Controller 層。其中,Repository 層負責資料訪問,Service 層負責業務邏輯,Controller 層負責暴露介面。當然,這只是其中一種分層和命名方式。不同的專案、不同的團隊,可能會對此有所調整。
為什麼基於貧血模型的傳統開發模式如此受歡迎?
基於貧血模型的傳統開發模式,將資料與業務邏輯分離,違反了 OOP 的封裝特性,實際上是一種面向過程的程式設計風格。但是,現在幾乎所有的 Web 專案,都是基於這種貧血模型的開發模式,甚至連 Java Spring 框架的官方 demo,都是按照這種開發模式來編寫的。
面向過程程式設計風格有種種弊端,比如,資料和操作分離之後,資料本身的操作就不受限制了。任何程式碼都可以隨意修改資料。既然基於貧血模型的這種傳統開發模式是面向過程程式設計風格的,那它又為什麼會被廣大程式設計師所接受呢?
關於這個問題,總結了下面三點原因。
第一點原因是,大部分情況下,我們開發的系統業務可能都比較簡單,簡單到就是基於 SQL 的 CRUD 操作,所以,我們根本不需要動腦子精心設計充血模型,貧血模型就足以應付這種簡單業務的開發工作。除此之外,因為業務比較簡單,即便我們使用充血模型,那模型本身包含的業務邏輯也並不會很多,設計出來的領域模型也會比較單薄,跟貧血模型差不多,沒有太大意義。
第二點原因是,充血模型的設計要比貧血模型更加有難度。因為充血模型是一種面向物件的程式設計風格。我們從一開始就要設計好針對資料要暴露哪些操作,定義哪些業務邏輯。而不是像貧血模型那樣,我們只需要定義資料,之後有什麼功能開發需求,我們就在 Service 層定義什麼操作,不需要事先做太多設計。
第三點原因是,思維已固化,轉型有成本。基於貧血模型的傳統開發模式經歷了這麼多年,已經深得人心、習以為常。你隨便問一個旁邊的大齡同事,基本上他過往參與的所有 Web 專案應該都是基於這個開發模式的,而且也沒有出過啥大問題。如果轉向用充血模型、領域驅動設計,那勢必有一定的學習成本、轉型成本。很多人在沒有遇到開發痛點的情況下,是不願意做這件事情的。
基於貧血模型的傳統開發模式,是典型的面向過程的程式設計風格。相反,基於充血模型的 DDD 開發模式,是典型的面向物件的程式設計風格。
實戰
:基於充血模型的DDD開發一個虛擬錢包系統
基於充血模型的 DDD 開發模式跟基於貧血模型的傳統開發模式相比,主要區別在 Service 層。在基於充血模型的開發模式下,我們將部分原來在 Service 類中的業務邏輯移動到了一個充血的 Domain 領域模型中,讓 Service 類的實現依賴這個 Domain 類。
在基於充血模型的 DDD 開發模式下,Service 類並不會完全移除,而是負責一些不適合放在 Domain 類中的功能。比如,負責與 Repository 層打交道、跨領域模型的業務聚合功能、冪等事務等非功能性的工作。
基於充血模型的 DDD 開發模式跟基於貧血模型的傳統開發模式相比,Controller 層和 Repository 層的程式碼基本上相同。這是因為,Repository 層的 Entity 生命週期有限,Controller 層的 VO 只是單純作為一種 DTO。兩部分的業務邏輯都不會太複雜。業務邏輯主要集中在 Service 層。所以,Repository 層和 Controller 層繼續沿用貧血模型的設計思路是沒有問題的。
理解DDD:
DDD第一原則:將資料和操作結合。(貧血模型將資料和操作分離,違反OOP的原則。)
DDD第二原則:界限上下文。這是將“單一指責”應用於我們的領域模型。
DDD is nothing more than OOP applied to business models. DDD其實就是把OOP應用於業務模型。
實戰
:對介面鑑權這樣一個功能開發做面向物件分析
面對需求不明確、過於模糊、籠統、不夠具體細化的情況需要不斷地分析需求,不斷的提出問題-解決問題。
介面鑑權:
1、加密:加密方式和演算法確定下來,類似於使用者名稱、AppId、密碼、時間戳、訪問地址等
2、解析:加密的內容進行比對
最終形成如下解決方案:
呼叫方進行介面請求的時候,將 URL、AppID、密碼、時間戳拼接在一起,通過加密演算法生成 token,並且將 token、AppID、時間戳拼接在 URL 中,一併傳送到微服務端。
微服務端在接收到呼叫方的介面請求之後,從請求中拆解出 token、AppID、時間戳。
微服務端首先檢查傳遞過來的時間戳跟當前時間,是否在 token 失效時間視窗內。如果已經超過失效時間,那就算介面呼叫鑑權失敗,拒絕介面呼叫請求。
如果 token 驗證沒有過期失效,微服務端再從自己的儲存中,取出 AppID 對應的密碼,通過同樣的 token 生成演算法,生成另外一個 token,與呼叫方傳遞過來的 token 進行匹配;如果一致,則鑑權成功,允許介面呼叫,否則就拒絕介面呼叫。
實踐:
-
劃分職責進而識別出有哪些類;
根據需求描述,我們把其中涉及的功能點,一個一個羅列出來,然後再去看哪些功能點職責相近,操作同樣的屬性,可否歸為同一個類。
-
定義類及其屬性和方法;
我們識別出需求描述中的動詞,作為候選的方法,再進一步過濾篩選出真正的方法,把功能點中涉及的名詞,作為候選屬性,然後同樣再進行過濾篩選。
-
定義類與類之間的互動關係;
UML 統一建模語言中定義了六種類之間的關係。它們分別是:泛化、實現、關聯、聚合、組合、依賴。我們從更加貼近程式設計的角度,對類與類之間的關係做了調整,保留四個關係:泛化、實現、組合、依賴。
-
將類組裝起來並提供執行入口。
我們要將所有的類組裝在一起,提供一個執行入口。這個入口可能是一個 main() 函式,也可能是一組給外部用的 API 介面。通過這個入口,我們能觸發整個程式碼跑起來。
1、先寫介面auth(String url) ,這裡先定義好請求的格式,例如“xxx?AppID=123&Token=aaa&TimeStamp=123123”
2、ApiRequest類,解析請求
3、AuthToken類,判斷是否過期、token匹配、校驗token
4、CredentialStorate類,獲取AppID對應的password