1. 程式人生 > >乾貨 | DDD實戰:基於洋蔥模型的分層程式碼架構設計

乾貨 | DDD實戰:基於洋蔥模型的分層程式碼架構設計

點選上方“中興開發者社群”,關注我們

每天讀一篇一線開發者原創好文640?wx_fmt=png&wxfrom=5&wx_lazy=1

▎作者簡介

作者馮丹是一名非常有激情的一執行緒序員,喜歡java強大的面向物件能力,scala簡潔的函數語言程式設計正規化以及Akka這種優秀的響應式程式設計框架。今天的文章可以讓讀者瞭解DDD落地的一種具體的措施。

領域驅動設計DDD(Domain Driven Design)的主旨思想就是不再把需求分析和程式碼實現分解為兩個獨立的過程,程式碼即方案,這對於程式碼的設計提出了更高的要求。要求即使是非開發人員也能非常容易的瞭解到他想要了解到的東西。

這就要求我們的程式碼必須是分層設計的,層次逐層遞進,若要了解大概流程,則通過閱讀userInterface層和Application層的程式碼就能夠知道整個業務的流程是怎麼樣的,並不需要知道業務邏輯具體是怎麼實現的,並不需要知道資料庫到底使用的是mysql還是cassandra。

另一方面我們想要管理不確定性,擁抱變化,同時不會因為外部頻繁變化而導致我們的核心業務也跟著頻繁的改動,這對於我們的程式碼有提出了另一個要求:必須要有一個穩定的核心,它不依賴任務外部的東西。——洋蔥模型應運而生。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

本次實踐緊貼業務(輸出即為最近迭代開發的系統監控微服務),通過基於洋蔥模型的程式碼分層設計,讓程式碼清晰易懂,而且不會再出現這樣的對話了:

前端開發:“這個接口裡面的欄位我要改一下,你也跟著改一下吧”

後端開發:“這個欄位不能改啊,你改了,我要改好大一串程式碼”

程式碼分層理念:

基於上圖的原理再結合我們具體業務,我們把程式碼按照如下目錄進行拆分:

為了和外部互動我們需要一個存放和外部互動的介面的包,我們把它叫做“api”或者“apiserver”

為了使我們的核心模型不隨著外部介面的變化而變化,我們需要一個存放外部介面相關的資料模型的包,我們把它叫做“dto”(data transaction object 資料傳輸物件),這個包中可以包含把dto物件轉為model物件的方法。這樣做的好處是dto向model依賴,而不是model依賴dto, 這樣的話如果外部模型發生了變化,我們修改dto包中的程式碼就已經足夠了,model並不感知這個變化。

為了更加靈活的應對外部的變化,我們需要區分核心業務和非核心業務,把變化頻繁的業務歸到非核心業務層中,放非核心業務的包把它叫做“app”,通常app層就是通過呼叫不同的核心業務層開出來的各種方法來實現業務,同時把核心業務層的模型轉換為外部介面需要的模型。

為了使我們的業務邏輯儘量穩定,我們需要一個不依賴任務外部包(或者說外部實現)的核心業務包,我們把它叫做“model”,我們把本屬於domain層的東西model、repository、領域服務、領域事件等都放在這個包中,這麼做的好處是和其他的包是同一抽象層次,想看核心業務開啟model包就足夠了。本身model中的業務需要的持久化等功能是基礎設施層提供的,也就是說model需要依賴基礎設施層,但是我們為了讓model層足夠的穩定,我們需要用依賴注入的方式讓依賴倒置,讓provider依賴model層。model提供了可供編排的和領域核心模型強相關的各種服務,model層中都是核心業務的直接表達,如果需要用到基礎設施則全部使用抽象代替,具體的基礎設施在外部(通常是main函式)注入。

為了使我們的業務系統有操作資料庫、檔案系統或者其他第三方軟體的能力,我們還需要一個存放和這些基礎設施強相關的包,我們把它叫做“provider”,provider包中理論上就是一些獨立的基礎設施操作類,他們依賴於model,把model層的資料持久化,或者是傳送給kafka等。通常各個單獨的provider例項在系統上電的時候注入到model層中,model層用一個公共的父類型別變數來接收這個provider例項,這樣就做到了依賴倒置。如果業務發生變化,動態或者靜態地修改model層中這個公共父類型別的變數對於的值就行了,通常這個注入的動作也不會放在model層中,所以model是穩定的。

收益:

把程式碼結構按照上述原則進行拆分之後邏輯變得更加清晰了,比如想要看一個rest介面是個什麼流程,只需要開啟api包檢視url是什麼,然後開啟dto包檢視介面中的資料模型是什麼樣的,再開啟app包檢視具體的業務流程即可。 至於具體實現關心它們的人才需要進一步瞭解。也就不會出現這種情況了:想要了解某個業務流程,把整個工程程式碼都翻遍了,都沒理清楚。

比如原來使用的資料庫是mysql,現在想要替換成cassandra,只需要實現一套cassandra操作的provider,然後在main函式把這個provider注入到model層即可。model層毫無感知。

比如前端要求改一個介面欄位,只需要在dto中把修改相應欄位,然後對映到相同的model模型欄位中即可,model層也毫無感知。

比如前端一個查詢資料的介面,原本只返回了部分資料,現在想要讓這個介面返回全量資料,只需要在app包中重新編排這個介面的邏輯,先獲取partI再獲取partII即可(假定model中已經有了獲取partI和partII的服務)。model層也是毫無感知。

通常情況下model是穩定的,除非真的是核心業務發生變化才需要去動model的東西。

所以變化並不可怕,因為我們的程式碼又靈活又穩定。

下圖是真實的基於這個理念開發的監控微服務的包和包依賴關係圖:

0?wx_fmt=png

0?wx_fmt=png

其中大致分了兩條線,左邊一條線:描述了從外部rest介面到核心領域模型model的依賴

0?wx_fmt=png

右邊一條線描述了基礎設施對於model的依賴,基礎設施在main函式中注入到model中。

0?wx_fmt=png

作者的其他文章

640?wx_fmt=jpeg