領域驅動在本地生活的實踐

本次分享的主題主要是關注兩個方面,當我們面對一個業務的時候,一方面怎麼使用領域驅動去指導去做整體的架構的設計,怎麼進行微服務的建設 (排除基礎設施等的應用架構);另外一方面是結合現有的軟件設計生態去實現系統,在軟件實現上不在強調聚合根、實體、倉儲服務等,而是強調邊界、狀態和基本的設計原則 (開閉原則、單一職責原則、依賴倒置原則、接口分離原則、迪米特法則等)。

從業務到戰略設計

哈囉本地生活的業務主要是售賣電子卡券,但是要解決的業務問題基本上涵蓋整個電商,本身具有一定的複雜性,本次主要選用本地生活的業務和大家討論一下。

業務分析階段

這個過程不同的人可能會有不同的介入方法,總的目的就是摸清我們要做的系統最終要提供什麼功能。在這個過程中可以從幾個核心的 W 入手,Who(誰)、Why(爲什麼要這麼做)、What(要做什麼)、How(怎麼做) 去拆解問題。

先看一下下面這張圖:

圖片

這裏通過角色整理了一下現在系統的用例,可以得到一個大致的業務邊界,比如說 BD 這一塊可以明顯的感覺到它是特定的人處理特定的問題,並且處理的問題的方法自己就可以完成,不需要依賴其它的東西,跟其它的系統耦合比較低,我們可以首先把它拿出去。

通過上面這張圖可以看到不同的人處理不同的業務,更大程度上趨於產品視角,更關注的是解決的不同問題。當我們把用例進行一定程度的歸納就得到了問題空間 (下圖整體對齊商家、消費者、BD、供貨商、渠道商、業務運營的需求),這個過程中需要和產品不斷的討論,因爲它是一類業務問題的集合,要儘量貼近我們業務發展的需要。

圖片

爲什麼要強調問題空間

一個問題空間往往代表了一類需要解決的業務問題。業務空間的劃分可以在一定程度上指導我們團隊的劃分,讓業務關聯性最強的成員形成一個小組,能夠解決大的協同問題,並且更好得爲目標服務。

假設我們的業務團隊按照營銷域 (現在的中臺) 來劃分,那麼這個小組的成員更多的是關注怎麼去發券,怎麼去做營銷活動,會更加關注能力本身,而不是更大程度上的用戶增長或者交易促成。對於中臺的建設如果說這種基礎的營銷、交易、商戶等能力是 V1.0,那麼提供業務問題整體的如商家入駐、用戶增長等解決方案應該就是 V2.0,中臺不同能力的建設是根據公司不同階段來決定的,但是對於我們業務來講,我們再按照業務方的交易、商戶、等中臺的方式來劃分可能就不太合適了。

所以在一定程度上問題空間可以給我們的團隊劃分提供一定的參考價值,讓我們的團隊更加關注業務問題,讓我們的成員更好的跟產品進行協同,而不是僅僅關注某一領域。

從問題空間到系統架構

當問題空間明確了,我們要做的業務基本上能夠達成共識。現在要做的就是進行系統架構的規劃,對應我們就是怎麼進行微服務的建設。我們看上圖交易促成中有下單,在供貨商對接中也有下單,雖然分屬不同的問題空間,但是有時卻需要共享相同的功能;再比如在做用戶增長的時候,我們可能會想到通過一些營銷手段去拉新,而在交易促成中也需要營銷。

所以問題空間離指導系統的落地還是有一定的差距,這裏就需要我們分析不同業務實現的業務流程,最終指導我們進行微服務的落地,用 DDD 的行話講就是問題空間到解決方案的落地。

下面給一個之前做渠道商的事件圖,這裏就是結合我們已有的的業務知識做一個邊界的抽象,這裏需要一定的電商業務積累,當然這個過程我們不一定是第一次劃分就會合理,而是可以先假設,然後在分析其它業務流程的時候看能否吻合,然後看是否需要抽象出新的上下文和形成新的邊界。

圖片

當我們分析完不同業務流程的,可以得到一個對於應用架構的一個大致的認識,那麼基於對上下文的梳理,可以形成一個戰略設計圖,如下:

圖片

在通常情況下一個上下文可以映射一個微服務存在,指導我們的系統建設,但是也一定不要固守成規,系統的發展是需要經歷一個過程的,我們不妨把它當做我們後期可以知道我們進一步拆分的手段。我們在實際落地的時候,可能還需要考慮穩定性等因素,如考慮到開單作爲最核心保障的,可能會以一個獨立應用來存在,在落地上與訂單管理服務拆分開;另外有些架構方式可能會抽象出事件中心去處理事件,以應對消息量 QPS 上萬等很大的場景;還有可能會建設文件服務、任務等,以應對可能是 IO 密集型、CPU 密集等;甚至在系統沒有那麼複雜的時候一個上線文作爲一個包也是 OK 的,符合當下的架構纔是最好的架構嘛。

軟件設計實現

使用領域驅動,更應關注核心

提供了一個很好的實現方式,大家按照這個模板來寫能夠保證風格一致,而且能夠對我們的代碼有一定的控制力,所以從整體實施上來講,在保障我們項目質量上,確實是一個比較不錯的方式。對於深諳領域驅動這一套的同學可能在實現時會得心應手,但是對於剛接觸的同學在前期上手的時候確實也會有很多疑問,這種理論儲備的複雜性,也是軟件設計的複雜性。另外更多的時候是希望是自己寫的代碼,對於後來的那些不熟悉領域驅動的同學,在接手後就能很好的理解我們的應用結構,我們的編碼方式和習慣,這樣軟件設計的穩定性和持續性會更強。

基於此希望能在領域驅動這套大框架之下,關注其中更加核心的設計思想或者原則,讓它給我們提供最核心的參考標準,讓實踐過程來的更加簡單。

1. 內聚是一個很好的標準

談到這個,可能有些同學會想到領域驅動的幾層邊界,當應用 (系統) 這個大的邊界確定了以後,DDD 的思想主要在分層和下面的聚合 / 實體這兩層邊界。這裏給出的分層架構會和大家書裏看到的有差別,另外後文會弱化聚合和實體。

2. 按需分層

下圖是我們現在使用的一種分層方式,對於 Depenency 和 Infrastructure 這兩個分層,比較好理解,一個是所有外部依賴的入口,可以很方便的知道我們依賴外部哪些應用,另外一個是我們訪問基礎設施 (如存儲) 做一個收口。後期可以很方便的幫我們梳理系統依賴項,同時也可以把 Dependency 看成一個防腐層。

圖片

這個分層實際上大家編碼的時候最容易產生疑問的是這個邏輯該是放在應用層還是領域層?這裏不給標準答案,我說如果你分析這個是完成這個功能必不可少的功能你就放在領域層,要不然你就放在應用層,即使現階段分析錯了也沒有關係,關鍵是你的邏輯要寫的有條理,當後期你發現另外一個應用層入口也需要調用這個領域方法,而且也需要這個功能的時候,那麼你自然就會想到這個是不是應該下沉到領域層了。

3. 弱化戰術的形式,簡化編程

領域驅動中戰術一詞主要是在指導我們怎麼進行落地,這裏所說的弱化是說盡量避免引入戰術本身給系統實現帶來的複雜性,讓別人接手的時候能夠無差別的理解我們的設計方式和思想。

實踐中我們可以弱化聚合根和實體,弱化聚合根和實體並不是說我們不在思考系統中的邏輯關係,而是說我們不一定需要書中標準的去建具體的實現類。聚合根和實體的分析本身是系統邏輯關係的分析一種體現,在工作過程中相反我們不僅要分析,而且最好能夠和產品對齊,我們需要通過它的 Id 來分析上下游的關聯關係和狀態扭轉。這種邏輯分析,能幫助我們分析整個業務上下游是否能夠串起來,是業務能否夠實現的關鍵。

當我們弱化聚合根和實體,在我們項目中的一種實踐如下圖,不在使用傳統的方式將一個聚合根和一個實體跟一個 Id 相對應,而是一個 Spring 接管的 Singleton 對象,更好的與當下的 spring 技術棧相結合,核心思想是一個包代表一個聚合,其中 Aggregate 主要作爲一個狀態變化的入口,對多個 Manager 的行爲具有控制力,同時 Manager 能夠直接給上游透出數據查詢等能力。

圖片

4. 多思考設計原則

當我們將實體等的初始化問題交給 Spring 來處理,我們也不在用 Factory 去創建他們了,其實 Factory 是一種很好的封裝實現,我們在代碼實現的時候不應該侷限於官方對於領域驅動的實踐,相反更應該主動去思考設計原則,如用接口去屏蔽細節形成邊界,類的功能做到聚焦,形成單一職責等。

關注分治和抽象

1. 分治

分治本身是分而治之,也就是上文說的邊界,關注領域邊界、系統邊界、分層邊界、包 (聚合) 邊界,幫助我們把複雜的問題簡單化,同時也能更好的遵循單一原則讓系統更加穩定,同時也有利於某一領域的持續深耕。因爲其複雜度更多的是在前期的分析階段,也是提升自己架構思維一種很不錯的方式,所以比較推薦。

2. 抽象

一個大家都知道的詞彙,我們應該更好的將它落實到我們的軟件實現中去。在實踐過程中比較推薦的兩種切實可行的方式,一個是流程抽象,一個是概念抽象。

流程抽象:當我們寫業務的時候是否能夠提煉出流程中核心的 1、2、3、4 步,然後用方法替代它,編碼過程中推薦一邊編碼一邊重構,以防止爲了抽象出流程不知道怎麼寫了。

概念抽象:比如像營銷中有加價購、滿減、優惠券等,這個時候上游更希望的看到的是一個營銷活動的接口,那這個時候就是對於概念的抽象,底層怎麼把這一個個能力積木組合起來上游不在關心,增加擴展性的同時也提高了系統的穩定性。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Zn1iEjE8yb1VfvS_8uhvcg