DDD許可權平臺建模與實戰(附程式碼)

語言: CN / TW / HK

神帥的架構實戰 

Java 資深開發工程師

讀完需要

9

速讀僅需 3 分鐘

1

背景

在與很多微信朋友討論 DDD 落地的時候也給出了一些自己的見解,但是卻很少有機會親手嘗試下如何解決一些現實場景,因此藉著 DDDinAction 的專案通過許可權平臺的程式碼實戰將自己的思路一點點落地和完善。

當然也有很多人希望找到一個 DDD 實戰專案程式碼,因此在迭代了一些 codeMaker 的功能特性之後便把 infosys-plat 的 infosys-auth 許可權平臺來真正落地一把,看一下用 DDD 的方式如何寫出更好的程式碼。

2

許可權平臺需求

2.1

總體需求列表

1. 構建統一許可權平臺對接全公司的許可權應用場景

2. 基於 RBAC 實現許可權模型

3. 提供 web 管理平臺的 API 介面,支援 dubbo,springboot api,提供 SDK 的 jar 包接入鑑權

4. 對角色,許可權,使用者,系統選單等統一管理

5. 第一版本實現核心業務模型的 CURD,部分匯入匯出功能

特色功能實戰(待實現):

6. 打通審批流平臺實現自動許可權審批

7. 支援細化的資料許可權規則應用,如某使用者只能訪問某個列表的最近三個月的資料

8. 臨時授權,比如某使用者給另外一個使用者開通臨時授權,到期收回

9. 許可權操作記錄日誌審計元件接入

10. 許可權風險審計

2.2

系統參與者

角色

使用功能

說明

許可權平臺超級管理員

許可權平臺所有功能

可能需要單獨的表儲存超級管理員配置

業務線管理員

配置角色和相關係統選單資源

由超級管理員配置

各個接入系統使用者

許可權驗證和管控

SDK接入

2.3

業務流程

在本專案的演示案例中著重找了兩個比較重要的業務流程來看一下業務時序圖

1. 許可權構建流程

2. 使用者鑑權流程

3

許可權模型

3.1

許可權上下文分析

3.2

許可權領域模型文件

這裡需要說明的是本專案不會有太多的建模過程,對相應的場景不做過多的識別,儘量保障一篇文章可以講完整個實戰內容,所以會直接的給出相關領域建模文件。當然如果你看程式碼有些疑問的話也歡迎交流討論。

3.3

實體劃分

實體名稱

實體描述

實體行為

AuthorityBO

許可權模型

禁用,啟用,判斷是否具有某項型別的許可權資源

DataAuthorityBO

資料許可權模型

SystemAuthorityBO

系統許可權模型

構建系統許可權

AdminAuthorityBO

行政許可權模型

UserAuthAggregateBO

使用者許可權角度的聚合模型

RoleAuthAggregateBO

角色許可權角度的聚合模型

UserGroupBO

使用者組模型

判斷使用者組是否關聯了指定角色

RoleBO

角色模型

禁用,啟用,關聯使用者列表

RoleGroupBO

角色組模型

RoleUserBO

角色使用者關聯模型

SystemBO

系統模型

新增模組

ModuleBO

選單模組模型

新增按鈕

MenuBO

按鈕模型

3.4

值物件劃分

值物件

值物件說明

備註

DataColumnBO

資料欄位模型

有增刪改查等,但是看上去更像配置類物件,在這裡對配置物件統一稱作值物件

AuthorityTypeEnum

許可權型別

列舉物件,在列舉類中,統一為值物件

UserBO

使用者物件

使用者物件在使用者服務中算是業務物件,但是在許可權系統裡只是依賴這個物件實現模型和業務的完整性,使用者狀態等並不歸許可權管,所以這裡可以看作是值物件,但是是在domain.support包下,註明是支撐域下的物件。

DepartmentBO

部門物件

與使用者物件一樣

Address

省市縣物件

這個物件算是複合物件,但是隻是許可權聚合的一部分,並沒有實際行為和狀態,所以可以看作是值物件。

這裡需要說明的是在本專案演示中並沒有過多的對值物件的表達做演示,比如對 userId 則用 UserID 物件表示,當然也有一些大佬的演示文章會這麼做。在本專案中就是對值物件採用最基本的資料型別進行表達,儘量不增加物件的遍歷和依賴深度。

在實踐過程中我們可能無法過多的對值物件能產生的一些規則進行關注,而不是簡單的對識別出來的一些屬性做物件封裝,這無疑會讓整個業務物件變得有點支離破碎,同時增加理解業務的難度,實踐起來也會帶著 DDD 的一些概念被桎梏。後面有機會就具體聊一下這方面的內容。

所以大部分場景下的值物件其實不需要完全使用物件包裝,在我看來如果你意識到這是值物件或者你能劃分出來某個物件或者資料結構是值物件就行了。用基本型別封裝並不意味著我們無法表達他的規則和業務場景,所以不必過分在領域層特別標明這是個值物件,比如 XXVO 這樣的,但是需要注意的是要通過文件來表達哪些物件是值物件。

3.5

聚合根識別

聚合根

聚合根描述

聚合作用

AuthorityBO

許可權,對整個角色對應的許可權做統一關聯處理,內部是資料許可權,行政許可權和系統許可權

角色可以關聯很多許可權資源,這些許可權資源目前識別了三類,後續可以自定義許可權資源,也就是說在這個聚合裡也有資源的這個物件,只是沒有特別關注到,算是隱喻。所以許可權約等於資源。

UserAuthAggregateBO

基於使用者角度的許可權聚合模型

由於是RBAC模型,所以我們無法直觀的看到使用者有哪些許可權,那麼這個許可權就相當於一個讀場景的快照聚合,寫仍然是通過角色管理許可權資源

RoleAuthAggregateBO

基於角色角度的許可權聚合模型

在進行讀的時候因有了整體的AuthorityBO做聚合管理了,那麼這個角色可能會對應多個AuthorityBO,所以需要通過角色維度來看這個角色有多少許可權資源。

SystemBO

基於系統選單維度的聚合模型

這個相當於一個省市縣的模型,重點是這個模型是動態變化的,而且許可權系統比較依賴他,但是維護在許可權系統是因為這是個核心的許可權資源,所以對許可權和系統選單本身來說系統就是一個整體的聚合物件,系統本身需要遮蔽內部選單和按鈕,外部依賴的則是系統及其內部的選單按鈕的業務標示做讀依賴。

4

許可權領域服務

4.1

領域服務文件

這個文件體現了用DDD與不用DDD的最大的區別,因為面向上下文面向聚合來構建服務介面的話相當於對底層資料表和相關服務做了整體的抽象和歸納,因此看上去不會是一個表一個介面一個服務類的那種。由於對不同的場景做了專門的區分,同時使用了依賴倒置的方式讓應用層不會去跨層呼叫基礎設施層,整體上可以做到可擴充套件,靈活性高,維護和修改也會變得比較輕鬆。

4.2

領域能力表格

上下文

對應模組

能力說明

使用者組

user

對某一類使用者進行分組管理

角色組

role

對某一類角色進行分組管理

角色

role

維護角色及其關聯的角色-使用者關聯關係

系統

system

維護系統及其選單按鈕的相關資源

資料欄位

config

提供資料許可權的相關的元資料資訊管理維護

許可權

authority

提供統一許可權資源的抽象能力,解耦角色和具體資源

行政許可權

authority

提供行政相關許可權資源的配置關聯

資料許可權

authority

提供資料相關許可權資源的配置關聯

系統許可權

authority

提供系統相關許可權資源的配置關聯

5

許可權平臺 Cola 應用架構

5.1

許可權應用架構

5.2

防腐層模式的兩種實踐

1. 在領域層對下游依賴介面進行一次介面方法封裝算是領域閘道器的一個實現方式,比如對快取操作的依賴,對下游其他介面的依賴封裝,返回和請求的物件都算在領域中,只是需要與領域核心心模型區分開。

2. 第二種是整個領域依賴的業務性下游服務,比如使用者中心的使用者介面,部門介面等。這種事在基礎設施層的 acl 包中構建依賴的下游介面方法,內部實現呼叫下游介面邏輯,並按領域物件進行返回,請求物件可以由領域內物件 BO 轉換為 DTO。

以上兩種方式各有優缺點,如果在領域層進行封裝那應用層可以在某種程度上跨國領域層直接訪問下游介面。如果在基礎設施層構建的話可能需要考慮物件的轉換,以及領域內對下游方法的業務處理。

5.3

CQRS 模式應用

在本工程裡面對核心的業務模組做了讀寫分離,提供讀和寫兩套介面,同時在領域層也對讀作了專門的分離。在 app 層通過 command+executor 的方式對許可權相關業務流程作專門的應用層處理,比如給角色授權等等。

5.4

CQE 模式應用

在領域層中對 bo 下的各個資料業務實體作了專門的分類,比如 BO, EVENT, MsgBody。因此在應用層通過 command+executor 的方式控制業務應用的時候會通過應用層的 CMD 物件來轉換 Evevnt。這樣的一個好處是可以做非同步化。

程式碼演示案例如下:

這裡需要多說一點的是關於事件的應用其實有好幾種,下面看一下:

  1. 領域內事件(在領域服務內部產生的事件)

  2. 應用層事件(應用層的一些事件也跟事務有關),帶事務處理特性的事件和其他監聽事件

  3. 事件延伸的訊息(比如因為某事件需要傳送訊息,或者接收訊息)

關於上面的一些事件有時候會因為業務特性採用同步操作,有時候則會使用非同步來實現,但是需要注意的是由於事件的應用場景有很多,過多使用可能會造成一定的複雜度,無法保證整體業務的連續性,畢竟程式碼是要給人看的。一個可行的方式就是對不同的事件做專門的區分,同時將事件與事件處理器儘量顯示的關聯起來做動態配置化路由。

5.5

規格模式應用

在使用規格模式之前我也專門回顧了下 eric 的書。在應用層的系統選單按鈕的查詢場景下做了一次嘗試。看上去效果不錯,在應用層的 SystemQueryFacadeImpl 中構建了幾個簡單的查詢介面,同時通過規格模式來判斷不同的查詢條件是否滿足,這樣的話對外介面數則變得非常少,同時對其他模組的讀邏輯可以做收攏。這裡看下程式碼案例:

內部通過規格路由即可將不同的查詢場景做收攏。但是需要說明的是在 eric 的書中對規格模式的應用是在領域層的,相當於在領域層對比較複雜的連表查詢或者統計查詢作了不同規格的處理。所以之前有群友說規格模式應用在哪一層,我的建議是應用層和基礎設施層都可以。當然如果是領域層的話,按照 eric 的書來實踐的話領域層可能就需要單獨的類來構建查詢 sql 和查詢物件了,所以不同的實踐你看到的 DDD 程式碼其實也不一樣。

5.6

獨立類模式應用

因為許可權模型比較複雜,所以這裡構建整個許可權資料的話肯定會不少,所以需要設計下快取相關的邏輯,因此在領域層中定義了不同業務模組的快取字首,在基礎設施層構建不同業務物件的快取服務類。由於應用層無法直接到基礎設施層引用快取服務類,所以為了保持整體分層架構的一致性,這裡在領域閘道器的包裡定義了一個 CacheServiceGataWay 介面。通過不同的業務物件標示來在介面實現方法內部進行路由呼叫。

具體程式碼則不截圖了,感興趣的話可以 down 下看看。

5.7

奧卡姆剃刀原理應用

在寫程式碼的時候一開始是採用的 codeMaker 生成的不同模組的程式碼類,但是實際應用中對於聚合的把控會讓一些生成的程式碼類變得非常尷尬,比如應用層的 facadeimpl 中的不同許可權型別下的介面實現,因為聚合的控制這些不同的許可權介面變得相對冗餘,因此通過@Deprecated 註解將其剃掉,相應的 CURD 走 Authority 聚合介面。

當然類似的情況也在 auth-adapter 中出現了,所以大家看程式碼的時候不用驚訝,這樣對比著看才有好壞之分。

5.8

DDD 嚴格分層架構

這裡因為採用了 Cola 架構,將 dubbo 介面的實現放在了應用層,所以看上去與 springboot 的介面實現依賴的領域層和應用層不是很協調,當然現實情況是不會存在一個專案裡有兩套不同風格的對外 API。那這裡我要重點說明的是在本專案中是不會有應用層跨過領域層實現呼叫基礎設施層介面的情況的。

當然在 auth-adapter 模組為了保障其本身作為使用者介面層的職責之後,本來是需要通過應用層來訪問領域層,但是在專案裡應用層是 dubbo 的實現層,所以在 auth-adapter 訪問了應用層和領域層。注意這裡訪問了應用層是因為在某些模組希望能引用用到 command+executor 類。如果沒有 dubbo 實現的話,那麼整體應用層將單獨為 auth-adapter 服務。不會存在這個特殊的跨層呼叫。

5.9

服務依賴說明

  1. 對接審批流介面實現自動許可權審批能力

  2. 對接使用者中心獲取使用者和部門資料

  3. 對接省市縣資料服務

6

程式碼專案說明

6.1

專案主頁

在這裡統一說明一下,我實戰 DDD 的程式碼基本上都在這個專案裡: http://gitee.com/codergit.com/dddin-action 本次迭代釋出的工程內容有兩個:

  1. infosys-plat 工程下的 infosys-auth 平臺程式碼

  2. youpinshop 工程下的 stock-simple-demo(扣庫存的各種視角演示案例)

6.2

專案內容

  1. 提供各個業務物件的基本增刪改查

  2. 提供最小中介軟體依賴的環境,方便啟動專案(理論上只依賴資料庫),整合 cache,mq 也比較方便,內部預設對這些實現了空內容,少量開發即可實現完整版本

  3. 提供 dubbo 介面和 spring boot 介面實現

  4. 提供領域模型文件,領域服務文件,DDL 文件

  5. 在專案程式碼中詳細增加處理各個場景的說明註釋

7

總結

7.1

總結

  1. 總體底座程式碼由天畫-codeMaker 支援,直接填充業務方法內容即可。

  2. 提供 auth-common 包,不需要再依賴 coderman-utils,整體依賴閉環。

  3. 對不同場景做針對性理論說明,輸出不同處理方案來控制專案複雜度,對最近的 DDD 理論學習做程式碼實戰。

  4. 在實現的過程中也有很多困惑,所以也請教了一些資深大佬,同時根據自己的理解在專案中構建一些可行的方法思路。

7.2

彩蛋(討論點)

  1. RoleUserBO 這種關聯關係物件算實體還是值物件?

  2. DO 模型對多表連線查詢,統計查詢的相容性有多少,是不是要單獨構建資料實體還是複用?

  3. mapstruct 如何解決不同資料型別之間的對映,比如 str->list ,父類屬性轉到子類屬性?