干货 | DDD实战:基于洋葱模型的分层代码架构设计
點(diǎn)擊上方“中興開發(fā)者社區(qū)”,關(guān)注我們
每天讀一篇一線開發(fā)者原創(chuàng)好文
▎作者簡(jiǎn)介
作者馮丹是一名非常有激情的一線程序員,喜歡java強(qiáng)大的面向?qū)ο竽芰?#xff0c;scala簡(jiǎn)潔的函數(shù)式編程范式以及Akka這種優(yōu)秀的響應(yīng)式編程框架。今天的文章可以讓讀者了解DDD落地的一種具體的措施。
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)DDD(Domain Driven Design)的主旨思想就是不再把需求分析和代碼實(shí)現(xiàn)分解為兩個(gè)獨(dú)立的過(guò)程,代碼即方案,這對(duì)于代碼的設(shè)計(jì)提出了更高的要求。要求即使是非開發(fā)人員也能非常容易的了解到他想要了解到的東西。
這就要求我們的代碼必須是分層設(shè)計(jì)的,層次逐層遞進(jìn),若要了解大概流程,則通過(guò)閱讀userInterface層和Application層的代碼就能夠知道整個(gè)業(yè)務(wù)的流程是怎么樣的,并不需要知道業(yè)務(wù)邏輯具體是怎么實(shí)現(xiàn)的,并不需要知道數(shù)據(jù)庫(kù)到底使用的是mysql還是cassandra。
另一方面我們想要管理不確定性,擁抱變化,同時(shí)不會(huì)因?yàn)橥獠款l繁變化而導(dǎo)致我們的核心業(yè)務(wù)也跟著頻繁的改動(dòng),這對(duì)于我們的代碼有提出了另一個(gè)要求:必須要有一個(gè)穩(wěn)定的核心,它不依賴任務(wù)外部的東西?!笫[模型應(yīng)運(yùn)而生。
本次實(shí)踐緊貼業(yè)務(wù)(輸出即為最近迭代開發(fā)的系統(tǒng)監(jiān)控微服務(wù)),通過(guò)基于洋蔥模型的代碼分層設(shè)計(jì),讓代碼清晰易懂,而且不會(huì)再出現(xiàn)這樣的對(duì)話了:
前端開發(fā):“這個(gè)接口里面的字段我要改一下,你也跟著改一下吧”
后端開發(fā):“這個(gè)字段不能改啊,你改了,我要改好大一串代碼”
代碼分層理念:
基于上圖的原理再結(jié)合我們具體業(yè)務(wù),我們把代碼按照如下目錄進(jìn)行拆分:
為了和外部交互我們需要一個(gè)存放和外部交互的接口的包,我們把它叫做“api”或者“apiserver”
為了使我們的核心模型不隨著外部接口的變化而變化,我們需要一個(gè)存放外部接口相關(guān)的數(shù)據(jù)模型的包,我們把它叫做“dto”(data transaction object 數(shù)據(jù)傳輸對(duì)象),這個(gè)包中可以包含把dto對(duì)象轉(zhuǎn)為model對(duì)象的方法。這樣做的好處是dto向model依賴,而不是model依賴dto, 這樣的話如果外部模型發(fā)生了變化,我們修改dto包中的代碼就已經(jīng)足夠了,model并不感知這個(gè)變化。
為了更加靈活的應(yīng)對(duì)外部的變化,我們需要區(qū)分核心業(yè)務(wù)和非核心業(yè)務(wù),把變化頻繁的業(yè)務(wù)歸到非核心業(yè)務(wù)層中,放非核心業(yè)務(wù)的包把它叫做“app”,通常app層就是通過(guò)調(diào)用不同的核心業(yè)務(wù)層開出來(lái)的各種方法來(lái)實(shí)現(xiàn)業(yè)務(wù),同時(shí)把核心業(yè)務(wù)層的模型轉(zhuǎn)換為外部接口需要的模型。
為了使我們的業(yè)務(wù)邏輯盡量穩(wěn)定,我們需要一個(gè)不依賴任務(wù)外部包(或者說(shuō)外部實(shí)現(xiàn))的核心業(yè)務(wù)包,我們把它叫做“model”,我們把本屬于domain層的東西model、repository、領(lǐng)域服務(wù)、領(lǐng)域事件等都放在這個(gè)包中,這么做的好處是和其他的包是同一抽象層次,想看核心業(yè)務(wù)打開model包就足夠了。本身model中的業(yè)務(wù)需要的持久化等功能是基礎(chǔ)設(shè)施層提供的,也就是說(shuō)model需要依賴基礎(chǔ)設(shè)施層,但是我們?yōu)榱俗宮odel層足夠的穩(wěn)定,我們需要用依賴注入的方式讓依賴倒置,讓provider依賴model層。model提供了可供編排的和領(lǐng)域核心模型強(qiáng)相關(guān)的各種服務(wù),model層中都是核心業(yè)務(wù)的直接表達(dá),如果需要用到基礎(chǔ)設(shè)施則全部使用抽象代替,具體的基礎(chǔ)設(shè)施在外部(通常是main函數(shù))注入。
為了使我們的業(yè)務(wù)系統(tǒng)有操作數(shù)據(jù)庫(kù)、文件系統(tǒng)或者其他第三方軟件的能力,我們還需要一個(gè)存放和這些基礎(chǔ)設(shè)施強(qiáng)相關(guān)的包,我們把它叫做“provider”,provider包中理論上就是一些獨(dú)立的基礎(chǔ)設(shè)施操作類,他們依賴于model,把model層的數(shù)據(jù)持久化,或者是發(fā)送給kafka等。通常各個(gè)單獨(dú)的provider實(shí)例在系統(tǒng)上電的時(shí)候注入到model層中,model層用一個(gè)公共的父類類型變量來(lái)接收這個(gè)provider實(shí)例,這樣就做到了依賴倒置。如果業(yè)務(wù)發(fā)生變化,動(dòng)態(tài)或者靜態(tài)地修改model層中這個(gè)公共父類類型的變量對(duì)于的值就行了,通常這個(gè)注入的動(dòng)作也不會(huì)放在model層中,所以model是穩(wěn)定的。
收益:
把代碼結(jié)構(gòu)按照上述原則進(jìn)行拆分之后邏輯變得更加清晰了,比如想要看一個(gè)rest接口是個(gè)什么流程,只需要打開api包查看url是什么,然后打開dto包查看接口中的數(shù)據(jù)模型是什么樣的,再打開app包查看具體的業(yè)務(wù)流程即可。 至于具體實(shí)現(xiàn)關(guān)心它們的人才需要進(jìn)一步了解。也就不會(huì)出現(xiàn)這種情況了:想要了解某個(gè)業(yè)務(wù)流程,把整個(gè)工程代碼都翻遍了,都沒理清楚。
比如原來(lái)使用的數(shù)據(jù)庫(kù)是mysql,現(xiàn)在想要替換成cassandra,只需要實(shí)現(xiàn)一套cassandra操作的provider,然后在main函數(shù)把這個(gè)provider注入到model層即可。model層毫無(wú)感知。
比如前端要求改一個(gè)接口字段,只需要在dto中把修改相應(yīng)字段,然后映射到相同的model模型字段中即可,model層也毫無(wú)感知。
比如前端一個(gè)查詢數(shù)據(jù)的接口,原本只返回了部分?jǐn)?shù)據(jù),現(xiàn)在想要讓這個(gè)接口返回全量數(shù)據(jù),只需要在app包中重新編排這個(gè)接口的邏輯,先獲取partI再獲取partII即可(假定model中已經(jīng)有了獲取partI和partII的服務(wù))。model層也是毫無(wú)感知。
通常情況下model是穩(wěn)定的,除非真的是核心業(yè)務(wù)發(fā)生變化才需要去動(dòng)model的東西。
所以變化并不可怕,因?yàn)槲覀兊拇a又靈活又穩(wěn)定。
下圖是真實(shí)的基于這個(gè)理念開發(fā)的監(jiān)控微服務(wù)的包和包依賴關(guān)系圖:
其中大致分了兩條線,左邊一條線:描述了從外部rest接口到核心領(lǐng)域模型model的依賴
右邊一條線描述了基礎(chǔ)設(shè)施對(duì)于model的依賴,基礎(chǔ)設(shè)施在main函數(shù)中注入到model中。
作者的其他文章
干貨|JVM內(nèi)存模型和常規(guī)問(wèn)題定位手段
總結(jié)
以上是生活随笔為你收集整理的干货 | DDD实战:基于洋葱模型的分层代码架构设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: torch.mul、matmul、mm、
- 下一篇: canvas下雪效果(原生js)