領域驅動設計在愛奇藝打賞業務的實踐

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"領域驅動設計(Domain-Driven Design,以下簡稱DDD)思潮的形成要追述到30幾年前,17年前,Eirc Evans定義了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"領域驅動設計","attrs":{}},{"type":"text","text":"的概念。DDD一直爲傳統行業的軟件工程師提供軟件設計的方法論,但是在互聯網行業卻使用很少。直到近幾年,DDD在互聯網行業被重新認識,火了起來。究其原因有兩點:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"互聯網的行業的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"業務越來越複雜","attrs":{}},{"type":"text","text":",面臨傳統行業軟件同樣的問題;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"微服務的流行","attrs":{}},{"type":"text","text":"帶火了DDD,來解決微服務拆分問題。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文主要對第一點“","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"解決軟件複雜性之道","attrs":{}},{"type":"text","text":"”進行講解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"價值","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"詳細講解之前,我們先給出DDD爲打賞業務帶來的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"價值","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"會員業務部門在打賞業務進行了DDD實踐後,效率有顯著提升:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新需求接入開發成本節約","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"20%","attrs":{}},{"type":"text","text":";","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"更換底層中間件開發成本節約","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"20%","attrs":{}},{"type":"text","text":";","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"項目熟悉成本節約","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"30%","attrs":{}},{"type":"text","text":"(對DDD有基本瞭解爲前提);","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單測開發成本指數級","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"降低","attrs":{}},{"type":"text","text":";","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上線風險、成本","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"降低","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"瞭解了DDD流行的背景及業務價值後,下面我們對","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"DDD是什麼、有哪些優勢、項目中如何實踐","attrs":{}},{"type":"text","text":",以及幾個關鍵問題進行敘述。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"領域驅動設計是什麼","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"討論領域驅動設計是什麼之前,我們先看下面一段代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7b/7b9999082795ac44e4d52b97a583f88e.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是一個打賞接口定義,單看這個接口是沒有問題的,用戶基於活動,選擇明星,選擇道具進行打賞。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"業務邏輯上沒問題,但我們會發現一些代碼的壞味道。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼編譯後方法的參數名會丟失。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c1/c18ce2ef77e048c5d39695a6a82367fd.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打賞業務邏輯摻雜了很多與業務核心邏輯無關的前置校驗邏輯,影響代碼可讀性。全部邏輯堆疊在一個方法中,增加了測試用例編寫複雜度。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於以上代碼,問題的根本原因是我們對業務的領域沒有明確的劃分,只是實現了一個操作流程,是需求的直接實現,缺乏領域抽象,方法的參數定義缺少業務領域含義。對於活動校驗本質是活動的一些屬性判斷,活動是否有效是活動自身的屬性決定的。可以抽象出活動校驗類ActivityValidate,或在實體中增加validate方法,還可以更近一步,將校驗邏輯直接放在活動的構造方法中,這樣既達到了校驗的目的,也避免了漏校驗。對於單測的編寫,如果我們能將一個大邏輯拆分爲多邏輯單元,無疑會大大減少用例數量。優化後的代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/25/254871957559a956a3289b9ff3f12fcc.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於活動的校驗,採用構造函數校驗,因此打賞方法中無需再校驗活動。活動的校驗放到前置構造函數,減少了測試用例數量。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回到最初的問題,什麼是領域驅動設計?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1、領域驅動設計基於領域建模而非數據建模","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面例子中,代碼重構前activity實體只有基本的屬性和get/set方法,即“失血模型”,從而導致activity這個領域對象退化爲數據對象,只用作orm組件的crud,失血模型在項目代碼中隨處可見。究其原因,跟對象-關係映射(ORM,比如hibernate)持久化機制的流行是有直接關係的,使用ORM將每個類映射到一張數據表,通過實體對象完成crud,久而久之實體成爲了orm框架的專用名詞,即喪失了領域能力。進行項目設計時,我們應該從業務領域角度出發思考問題,而不應該從數據庫角度,我們將在戰略設計部分詳細討論。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2、滿足六邊形架構設計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"六邊形架構在後文進行詳細介紹,洋蔥架構、乾淨架構與六邊形架構類似。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"滿足以上兩點,並對DDD的一些概念進行映射實踐,那麼你的系統已經符合DDD了。總結,DDD不是一套全新的特殊架構,是任何項目代碼經過重構,滿足高可維護性、高可擴展性、高可測試性、代碼結構清晰之後必將達到的終點。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"DDD打賞業務實踐","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1、打賞業務簡介","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"觀看視頻時,選擇明星、禮物進行打賞;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打賞後屏幕有氣泡提示;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打賞數據在排行榜進行顯示;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"累計一定的打賞獲得某種獎勵。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2、戰略設計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提到戰略設計,不得不提的是戰略設計的幾個核心概念:領域、子域、限界上下文、架構分層。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"領域:","attrs":{}},{"type":"text","text":"從廣義上講,領域即是一個組織所做的事情以及其中包含的一切。每個公司或組織都有它自己的業務範圍和做事方式,這個業務範圍以及其在其中所進行的活動便是領域。當你爲某個公司開發軟件時,你面對的便是這個公司的領域。這個領域對於你來說應該是明確的,因爲你在這個領域中工作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於打賞這種業務,打賞本身便是領域,即打賞領域。無論你的打賞對象是一位主播、一部電影或者一篇博文,又無論你的打賞道具是RMB、虛擬幣、火箭等等,打賞都是這個領域的核心。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"子域:","attrs":{}},{"type":"text","text":"對於領域模型包含“領域”這個詞,我們可能會認爲整個業務系統創建一個單一的、內聚的、功能全能式的模型。然後這並不是我們使用DDD的目標。正好相反,在DDD中,一個領域被分成若干小的域,這些若干的小的域即子域。事實上,在開發一個領域模型時,我們關注的通常只是這個業務系統的某個方面,對領域的拆分將有助於我們成功。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"限界上下文:","attrs":{}},{"type":"text","text":"一個由顯示邊界限定的特殊職責。領域模型存在於邊界之內,在邊界內,每一個模型概念,包括它的屬性和操作,都具有特殊的含義。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打賞系統搭建之初,需求比較簡單,隨着業務發展,需求越來越複雜,迭代及領域拆分過程如下:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"運營及產品需求非常簡單,只要實現免費打賞並在界面實現打賞氣泡,基於此,只有一個領域;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過一階段的試水,活動效果很好,需要能同時支持多場打賞活動,增加活動支撐子域;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需求方又有了新想法,用戶完成一定打賞後給用戶發放一些獎品,引入獎勵子域;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了提升用戶參與感,增加排行功能,引入排行子域。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最終領域劃分如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/43/433cc6715ecfa90c8853e0cc01f83812.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打賞核心子域:完成打賞操作。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通知子域:實現界面氣泡通知能力。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獎勵子域:獎勵策略匹配,獎勵發放。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"排行子域:完成排行功能。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"活動子域:活動、明星、道具管理。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用戶子域:完成用戶查詢、校驗等通用能力。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"領域的拆分過程並沒有上面描述的那麼順利,經歷了很多推翻重來的過程,正是經歷了這些過程,我們對領域的理解才能更深入,更符合領域建模。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"架構分層:","attrs":{}},{"type":"text","text":"分層架構的一個重要原則是——每層只能與位於其下方的層發生聚合。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/84/840700b7e5b88e2103f78e0b2cdd2caa.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了實現接口的定義與實現解耦,接口定義在領域層,實現定義在基礎設施層。但這樣違背了由頂至底的單項依賴原則。爲了解決這個問題,我們此處採用依賴倒置原則,依賴倒置原則內容——","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"高層模塊不應該依賴於低層模塊,兩者都應該依賴於抽象。抽象不應該依賴於細節,細節應該依賴於抽象。根據此原則,結構調整如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c9/c98541fece5e2e1a99f239e3404db033.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們將基礎設施層放在所有層的最上方,這樣它可以實現所有其它層定義的接口。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當我們在分層架構中採用依賴倒置原則時,我們可能會發現,事實上已經不存在分層的概念了。無論是高層還是底層,它們都只依賴於抽象,好像把整個分層推平了一樣。推平之後,客戶通過“平等”的方式與系統交互。加入新的客戶也只是不同的輸入、輸出,以及不同的展現形式而已,這既是我們即將瞭解的另一個架構,六邊形架構。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8c/8ca475562a76511b6990f9e235933929.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"六邊形架構","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我們的代碼中,有很多直接的外部依賴和實現細節。如mybatis的mapper類、httpclient注入、rocketmq的監聽、緩存的直接操作等等。這樣的實現有兩個比較明顯問題,一是當底層更換基礎組件時對業務邏輯有直接影響,更換代碼改動量及測試範圍大大增加。二是不利於功能的複用,如果其他業務有類似邏輯,做不到直接移植複用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2005年Alistair Cockburn提出了六邊形架構,又被稱爲端口和適配器架構。觀察上圖我們發現,對於核心的應用程序和領域模型來說,其他的底層依賴或實現都可以抽象爲輸入和輸出兩類。組織關係變爲了一個二維的內外關係,而不是上下結構。每個io與應用程序之前均有適配器完成隔離工作,每個最外圍的邊都是一個端口。基於六邊形架構設計的系統是DDD追求的最終形態。六邊形架構的實踐在“DDD的優勢”部分進行講解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先給出基於六邊形架構實踐後,項目模塊結構:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/25/25e978c89d6cb18fa0e157b1dc0c9996.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ba/badbb9709dfb12ffc6eaa882df0988c5.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3、戰術設計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過戰略設計後,領域已經有了清晰的邊界,下面我們聊下戰術設計。首先對DDD的幾個基本概念進行業務映射。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"實體:","attrs":{}},{"type":"text","text":"由屬性和行爲組成,具有唯一標識。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在設計系統時,我們趨向於將重點放在數據上,而不是領域上。對於DDD開發者來說也是如此,因爲軟件開發中,數據庫依然佔據着主導地位。首先考慮的是數據的屬性和關聯關係,而不是富有行爲的領域概念。這樣做的結果是將數據模型直接反映在對象模型上,導致實體只包含get/set方法,這不是DDD的做法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只有get/set實體需配合service使用,內聚性、可維護性,以及複用遷移成本均明顯高於DDD的做法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3a/3a798bf122095f078c8e3f0301765d51.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/93/93149cbc5eb72f88f9957c063910d96f.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"值對象:","attrs":{}},{"type":"text","text":"沒有唯一標識,具有可度量或可描述,並滿足不變性的對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們應該儘量使用值對象來建模而不是實體對象,因爲相比實體,我們可以非常容易地對值對象進行創建、測試、使用、優化和維護。         ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/58/5820f5e9254fdb26b68547cf50b17ba9.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/50/505ed72318b4264f689d1163fd760517.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於第一種實現,用戶必須知道需要同時使用amount和currency,並且應該知道如何使用這兩個屬性,原因在於這兩個屬性並沒有組成一個概念整體。對於PropName值對象的定義,可以帶來一些擴展性,如需要對道具名稱做大小寫轉換,此操作可以在PropName的內部實現,對外name的邏輯泄漏到Prop中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"領域服務:","attrs":{}},{"type":"text","text":"領域服務表示一個無狀態的操作,它用於實現特定於某個領域的任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當某個操作不適合放在聚合和值對象上時,最好的方式便是使用領域服務了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如“用戶認證”,一種方式是我們可以簡單地將認證操作放在實體上。對於這種設計,存在兩個問題。首先,用戶類需要知道某些認證細節,其次,這種方法也不能顯示的表達通用語言。這裏我們詢問的是一個User“是否被認證了”,而沒有表達出“認證”這個過程。在有可能的情況下,我們應該儘量使用建模術語直接地表達出交流語言。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"領域事件:","attrs":{}},{"type":"text","text":"領域專家所關心的發生在領域中的一些事件。我們通常將領域事件用於維護事件的一致性,這樣可以消除兩階段提交(全局事務)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"聚合:","attrs":{}},{"type":"text","text":"聚合是一組相關對象的組合,作爲一個整體被外界訪問,聚合根是這個聚合的根節點。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"聚合是一個非常重要的概念,核心領域往往都是用聚合來表達。其次,聚合在技術上也有非常高的價值,可以指導詳細設計。聚合由根實體、實體、值對象組成。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"工廠:","attrs":{}},{"type":"text","text":"工廠提供一個創建對象的接口,該接口封裝了所有創建對象的複雜操作過程,同時,它並不需要客戶去引用實際被創建的對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/82/82e77396578d69cec9543c03e3e18a4d.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於上面例子中方法參入爲什麼傳入資源庫對象,我們將在下面六邊形架構部分講解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"DDD的優勢","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"應用DDD的系統符合六邊形架構,我們實現了以下目標:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獨立於框架:架構不應該依賴某個外部的庫或者框架,不應該被框架的結構所束縛;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獨立於UI:前臺展示的樣式可能會隨時發生變化,但是底層架構不應該隨之而變化;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獨立於底層數據源:軟件架構不應該因爲不同的底層數據存儲而產生巨大改變;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獨立於外部依賴:無論外部依賴如何變更、升級,業務的核心邏輯不應隨之而大幅變化。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實現以上幾個目標,六邊形架構(洋蔥架構、乾淨架構與此類似)是個不錯的選擇,下面結合打賞具體業務實現,講解如何實現以上目標。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先給出項目某個模塊的代碼包結構:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/46/46c976bb5ea1179f10ba57b4e1c5df2c.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"資源庫:","attrs":{}},{"type":"text","text":"對於資源庫,我們的實踐是資源庫作爲業務與數據的隔離層,屏蔽底層數據表細節,同時完成PO與DO的轉化。DO與PO的轉化帶來的好處是領域層不會直接依賴底層實現,便於後續更換底層實現或功能遷移。資源庫接口定義在領域層,接口實現在基礎設施層。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b7/b73c746dfd727b20d2ca4ace4c646dd4.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/76/7644032cb6d1fbd8059306269134d2f7.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"RPC:","attrs":{}},{"type":"text","text":"RPC部分的結構拆分與資源庫類似,區別是以領域服務的存在。接口定義放在領域層,具體實現在基礎設施層。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f2/f27690262f83247331bdb911f47e69c3.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/65/65ceb93089c1c7eb104f394857cb95f4.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在遵循六邊形架構的大原則下,其他邊的拆分也變得清晰簡單了,此處不再贅述。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"幾個關鍵問題","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1、事務","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上文中“聚合”章節,我們描述了事務,一個事務內只操作一個聚合實例。如果發現一次事務內邏輯過多,可以考慮剝離出獨立的聚合,採用最終一致性。基於這個基礎,最適合聲明事務的層是應用層。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2、查詢","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CQRS 在 DDD 中是一種常常被提及的模式,它的用途在於將領域模型與查詢功能進行分離,讓一些複雜的查詢擺脫領域模型的限制,以更爲簡單的 DTO 形式展現查詢結果。同時分離了不同的數據存儲結構,讓開發者按照查詢的功能與要求更加自由的選擇數據存儲引擎,CQRS的具體實踐可以自行查找資料。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3、框架無關","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於六邊形架構設計,已經做到了與底層實現、框架、中間件無關。但還有一個最大的框架依賴spring,我們的做法是領域內使用的spring bean通過傳參方式,實現領域層框架解耦。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4、成本","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"成本是我們實踐DDD時需要考慮的一個很重要的問題,學習成本、改造成本、兼容成本等等都是需要特別關注的。在動手實踐之前,建議優先評估好成本。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結束語","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DDD不是一套全新的特殊架構,是應對軟件複雜性的一套方法論。是面向領域建模,基於六邊形架構,項目代碼經過重構,滿足高可維護性、高可擴展性、高可測試性、代碼結構清晰之後必將達到的終點。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DDD缺少權威性的實踐指導和代碼約束,因此應用過程中會碰到很多問題,愛奇藝會員團隊通過幾個月的實踐積累了一定的經驗,歡迎交流。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/5lhqLGnRg3QcSJIKhpVZaQ","title":""},"content":[{"type":"text","text":"領域驅動設計在愛奇藝打賞業務的實踐","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章