一、背景
在與很多微信朋友讨論 DDD 落地的時候也給出了一些自己的見解,但是卻很少有機會親手嘗試下如何解決一些現實場景,是以借着 DDDinAction 的項目通過權限平台的代碼實戰将自己的思路一點點落地和完善。
當然也有很多人希望找到一個 DDD 實戰項目代碼,是以在疊代了一些 codeMaker 的功能特性之後便把 infosys-plat 的 infosys-auth 權限平台來真正落地一把,看一下用 DDD 的方式如何寫出更好的代碼。
二、權限平台需求
2.1 總體需求清單
- 建構統一權限平台對接全公司的權限應用場景
- 基于 RBAC 實作權限模型
- 提供 web 管理平台的 API 接口,支援 dubbo,springboot api,提供 SDK 的 jar 包接入鑒權
- 對角色,權限,使用者,系統菜單等統一管理
- 第一版本實作核心業務模型的 CURD,部分導入導出功能
- 打通審批流平台實作自動權限審批
- 支援細化的資料權限規則應用,如某使用者隻能通路某個清單的最近三個月的資料
- 臨時授權,比如某使用者給另外一個使用者開通臨時授權,到期收回
- 權限操作記錄日志審計元件接入
- 權限風險審計
2.2 系統參與者
角色 | 使用功能 | 說明 |
權限平台超級管理者 | 權限平台所有功能 | 可能需要單獨的表存儲超級管理者配置 |
業務線管理者 | 配置角色和相關系統菜單資源 | 由超級管理者配置 |
各個接入系統使用者 | 權限驗證和管控 | SDK接入 |
2.3 業務流程
在本項目的示範案例中着重找了兩個比較重要的業務流程來看一下業務時序圖
2.3.1 權限建構流程
2.3.2 使用者鑒權流程
三、權限模型
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.1 領域服務文檔
這個文檔展現了用DDD與不用DDD的最大的差別,因為面向上下文面向聚合來建構服務接口的話相當于對底層資料表和相關服務做了整體的抽象和歸納,是以看上去不會是一個表一個接口一個服務類的那種。由于對不同的場景做了專門的區分,同時使用了依賴倒置的方式讓應用層不會去跨層調用基礎設施層,整體上可以做到可擴充,靈活性高,維護和修改也會變得比較輕松。
4.2 領域能力表格
上下文 | 對應子產品 | 能力說明 |
使用者組 | user | 對某一類使用者進行分組管理 |
角色組 | role | 對某一類角色進行分組管理 |
角色 | role | 維護角色及其關聯的角色-使用者關聯關系 |
系統 | system | 維護系統及其菜單按鈕的相關資源 |
資料字段 | config | 提供資料權限的相關的中繼資料資訊管理維護 |
權限 | authority | 提供統一權限資源的抽象能力,解耦角色和具體資源 |
行政權限 | authority | 提供行政相關權限資源的配置關聯 |
資料權限 | authority | 提供資料相關權限資源的配置關聯 |
系統權限 | authority | 提供系統相關權限資源的配置關聯 |
五、權限平台 Cola 應用架構
5.1 權限應用架構
5.2 防腐層模式的兩種實踐
- 在領域層對下遊依賴接口進行一次接口方法封裝算是領域網關的一個實作方式,比如對緩存操作的依賴,對下遊其他接口的依賴封裝,傳回和請求的對象都算在領域中,隻是需要與領域核心心模型區分開。
- 第二種是整個領域依賴的業務性下遊服務,比如使用者中心的使用者接口,部門接口等。這種事在基礎設施層的 acl 包中建構依賴的下遊接口方法,内部實作調用下遊接口邏輯,并按領域對象進行傳回,請求對象可以由領域内對象 BO 轉換為 DTO。
以上兩種方式各有優缺點,如果在領域層進行封裝那應用層可以在某種程度上跨國領域層直接通路下遊接口。如果在基礎設施層建構的話可能需要考慮對象的轉換,以及領域内對下遊方法的業務處理。
5.3 CQRS 模式應用
在本工程裡面對核心的業務子產品做了讀寫分離,提供讀和寫兩套接口,同時在領域層也對讀作了專門的分離。在 app 層通過 command+executor 的方式對權限相關業務流程作專門的應用層處理,比如給角色授權等等。
5.4 CQE 模式應用
在領域層中對 bo 下的各個資料業務實體作了專門的分類,比如 BO, EVENT, MsgBody。是以在應用層通過 command+executor 的方式控制業務應用的時候會通過應用層的 CMD 對象來轉換 Evevnt。這樣的一個好處是可以做異步化。
代碼示範案例如下:
這裡需要多說一點的是關于事件的應用其實有好幾種,下面看一下:
領域内事件(在領域服務内部産生的事件)
應用層事件(應用層的一些事件也跟事務有關),帶事務處理特性的事件和其他監聽事件
事件延伸的消息(比如因為某事件需要發送消息,或者接收消息)
關于上面的一些事件有時候會因為業務特性采用同步操作,有時候則會使用異步來實作,但是需要注意的是由于事件的應用場景有很多,過多使用可能會造成一定的複雜度,無法保證整體業務的連續性,畢竟代碼是要給人看的。一個可行的方式就是對不同的事件做專門的區分,同時将事件與事件處理器盡量顯示的關聯起來做動态配置化路由。
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 服務依賴說明
對接審批流接口實作自動權限審批能力
對接使用者中心擷取使用者和部門資料
對接省市縣資料服務
六、代碼項目說明
6.1 項目首頁
在這裡統一說明一下,我實戰 DDD 的代碼基本上都在這個項目裡:https://gitee.com/codergit.com/dddin-action 本次疊代釋出的工程内容有兩個:
infosys-plat 工程下的 infosys-auth 平台代碼
youpinshop 工程下的 stock-simple-demo(扣庫存的各種視角示範案例)
6.2 項目内容
提供各個業務對象的基本增删改查
提供最小中間件依賴的環境,友善啟動項目(理論上隻依賴資料庫),內建 cache,mq 也比較友善,内部預設對這些實作了空内容,少量開發即可實作完整版本
提供 dubbo 接口和 spring boot 接口實作
提供領域模型文檔,領域服務文檔,DDL 文檔
在項目代碼中詳細增加處理各個場景的說明注釋
七、總結
總體底座代碼由天畫-codeMaker 支援,直接填充業務方法内容即可。
提供 auth-common 包,不需要再依賴 coderman-utils,整體依賴閉環。
對不同場景做針對性理論說明,輸出不同處理方案來控制項目複雜度,對最近的 DDD 理論學習做代碼實戰。
在實作的過程中也有很多困惑,是以也請教了一些資深大佬,同時根據自己的了解在項目中建構一些可行的方法思路。
文章來源:https://mp.weixin.qq.com/s?__biz=MzI3MzEzMDI1OQ==&mid=2651835225&idx=1&sn=e18c2f4f21e70fdc9aaa0b941adeb448&chksm=f0dc9d25c7ab1433b9c6eb802be36998121afaed17253ba599d1d2e972a1db8ba7767044095e&scene=21#wechat_redirect