原文:https://herbertograca.com/2017/09/07/domain-driven-design/
這篇文章是軟件架構(gòu)編年史(譯)的一部分,這部編年史由一系列關(guān)于軟件架構(gòu)的文章組成勋眯。在這一系列文章中著拭,我將寫下我對(duì)軟件架構(gòu)的學(xué)習(xí)和思考额获,以及我是如何運(yùn)用這些知識(shí)的装畅。如果你閱讀了這個(gè)系列中之前的文章,本篇文章的的內(nèi)容將更有意義嗽元。
Eric Evans 于 2003 年出版了精采絕倫的《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):軟件核心復(fù)雜性應(yīng)對(duì)之道》敛纲,在書中他創(chuàng)造了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)方法。Eric Evans 的這本著作十分重要剂癌,現(xiàn)今許多我們認(rèn)為理所當(dāng)然的軟件開發(fā)概念都是在本書中被正式提出的淤翔。
我不可能在一篇博客中全面地回顧 DDD。和 DDD 相關(guān)的重要概念實(shí)在是太多了佩谷。幸好旁壮,這篇文章志不在此。而我要做的就是列出一些 DDD 概念谐檀,我認(rèn)為這些概念對(duì)我喜歡的代碼組織方式和我對(duì)架構(gòu)的看法而言更有意義:系統(tǒng)范圍內(nèi)構(gòu)成特性開發(fā)基礎(chǔ)的那些概念抡谐。
在這篇文章里,我將著重探討:
- 統(tǒng)一語言
- 分層
- 限界上下文
- 防腐層
- 共享內(nèi)核
- 通用子域
統(tǒng)一語言
在軟件開發(fā)中桐猬,圍繞著代碼的理解始終有一些問題麦撵,代碼是什么,它們干了什么溃肪,它們?nèi)绾巫龅降拿馕福鼈優(yōu)槭裁匆@么做...如果代碼中使用的術(shù)語和領(lǐng)域?qū)<沂褂玫男g(shù)語不一樣的話就更復(fù)雜了,例如惫撰,領(lǐng)域?qū)<艺f的是老用戶(elder user)而代碼說的卻是管理者(supervisor)羔沙,在討論應(yīng)用時(shí)這可能帶來很多的困擾。但是润绎,絕大多數(shù)的混淆都可以通過類和方法的正確命名來解決撬碟,正確的命名能讓它們表達(dá)對(duì)象在領(lǐng)域上下文中是什么以及方法在領(lǐng)域上下文中干了什么诞挨。
統(tǒng)一語言的主要思想是讓應(yīng)用能和業(yè)務(wù)相匹配。這是通過在業(yè)務(wù)與代碼中的技術(shù)之間采用共同的語言達(dá)成呢蛤。這門語言起源于公司的業(yè)務(wù)側(cè)-他們擁有需要實(shí)現(xiàn)的概念惶傻,語言中的術(shù)語由他們和公司的技術(shù)側(cè)通過協(xié)商來定義(意味著業(yè)務(wù)側(cè)也不能總是選到最好的命名),目標(biāo)是創(chuàng)造可以被業(yè)務(wù)其障、技術(shù)和代碼自身無歧義使用的共同術(shù)語银室,即統(tǒng)一語言。代碼励翼、類蜈敢、方法、屬性和模塊的命名必須和統(tǒng)一語言相匹配汽抚。必要的時(shí)候需要對(duì)代碼進(jìn)行重構(gòu)抓狭!
分層
之前的文章中我已經(jīng)談到過分層,但我覺得這里關(guān)鍵的是記住通過 DDD 識(shí)別的層次:
用戶界面
負(fù)責(zé)繪制用戶用來和應(yīng)用交互的屏幕界面并將用戶的輸入翻譯成應(yīng)用的命令造烁。值得注意的是“用戶”可以是人類也可以是連接我們 API 的其他應(yīng)用否过,它們和EBI架構(gòu)中的邊界對(duì)象完全對(duì)應(yīng)。應(yīng)用層
協(xié)調(diào)領(lǐng)域?qū)ο笸瓿捎脩粢蟮娜蝿?wù):用例惭蟋。它不包含業(yè)務(wù)邏輯苗桂。應(yīng)用層和EBI架構(gòu)中的交互器相對(duì)應(yīng),只有一點(diǎn)不同告组,交互器是和界面或?qū)嶓w無關(guān)的任意對(duì)象煤伟,而這里應(yīng)用層只包含和用例相關(guān)的對(duì)象。應(yīng)用服務(wù)屬于這一層木缝,它們是用例對(duì)資源庫便锨、領(lǐng)域模型、實(shí)體氨肌、值對(duì)象或是任何其它領(lǐng)域?qū)ο筮M(jìn)行編配的容器鸿秆。領(lǐng)域?qū)?br> 這個(gè)層次包含了所有的業(yè)務(wù)邏輯,如領(lǐng)域服務(wù)怎囚、實(shí)體卿叽、事件和其他包含業(yè)務(wù)邏輯的任意對(duì)象類型。顯然它和 EBI 架構(gòu)中的實(shí)體對(duì)象類型對(duì)應(yīng)恳守。這是系統(tǒng)的心臟考婴。領(lǐng)域服務(wù)擺包含的領(lǐng)域邏輯不太適合放到某個(gè)實(shí)體中,通常是為了完成某個(gè)領(lǐng)域操作而對(duì)多個(gè)實(shí)體進(jìn)行的編配催烘。
基礎(chǔ)設(shè)施
支持上述三個(gè)層次的技術(shù)能力沥阱,例如,持久化或者消息機(jī)制伊群。
限界上下文
在企業(yè)應(yīng)用中考杉,模型的規(guī)模和在代碼倉庫上工作的團(tuán)隊(duì)規(guī)模都增長(zhǎng)得很快策精。這會(huì)給我們帶來兩個(gè)問題:
- 開發(fā)者工作的代碼倉庫越大,認(rèn)知超載就越嚴(yán)重崇棠,代碼就越難理解咽袜,這會(huì)導(dǎo)致 BUG 的產(chǎn)生和錯(cuò)誤的判斷;
- 在同一個(gè)代碼倉庫上工作的開發(fā)者越多枕稀,就越難協(xié)作并達(dá)成共同的應(yīng)用領(lǐng)域和技術(shù)愿景询刹。
換句話說,我們面臨的問題太大了萎坷。
通常的解決方法就是把大問題切分成較小的問題凹联,“限界上下文”就是這樣干的。
一般來說哆档,兩個(gè)子系統(tǒng)一定服務(wù)于迥然不同的用戶群體蔽挠。——Eric Evans 2014, Domain-Driven Design Reference
限界上下文定義了模型中隔離出來的部分可以應(yīng)用的上下文瓜浸。這種隔離可以通過解耦技術(shù)邏輯象泵,分割代碼倉庫,分割數(shù)據(jù)庫 Schema 來達(dá)成斟叼,在團(tuán)隊(duì)組織方面也是一樣。和往常一樣春寿,限界上下文將拆分到何種程度取決于實(shí)際情況:我們的需求和可能性朗涩。
有趣的是,這不是一個(gè)全新的概念绑改。早在 1992 年谢床,Ivar Jacobson 在他的書中就有子系統(tǒng)的描述,比 Eric Evans 早了十一年厘线!
那時(shí)他就提出了一些關(guān)于這個(gè)主題的具體想法:
- 系統(tǒng)由若干子系統(tǒng)組成识腿,而它們各自又有各自的子系統(tǒng)。這個(gè)層級(jí)結(jié)構(gòu)的最底層就是分析對(duì)象造壮。于是子系統(tǒng)就成為了進(jìn)一步開發(fā)和維護(hù)系統(tǒng)的結(jié)構(gòu)方式渡讼。
- 子系統(tǒng)的任務(wù)就是把對(duì)象組合成包,達(dá)到降低復(fù)雜度的目的耳璧。
- 和功能的特定部分相關(guān)的全部對(duì)象都將被放在同一個(gè)子系統(tǒng)中成箫。
- 目標(biāo)是子系統(tǒng)內(nèi)的強(qiáng)功能性耦合和子系統(tǒng)間的弱耦合(現(xiàn)在被稱為高內(nèi)聚低耦合)
- [一個(gè)子系統(tǒng)]最好應(yīng)該只和一個(gè)角色耦合,因?yàn)樽兓ǔS梢粋€(gè)角色引發(fā)旨枯。
- [...]首先把控制對(duì)象放入子系統(tǒng)蹬昌,然后將強(qiáng)耦合的實(shí)體對(duì)象和界面對(duì)象放到同一個(gè)子系統(tǒng)中
-
擁有強(qiáng)相關(guān)功能耦合的所有對(duì)象都將被放入同一個(gè)子系統(tǒng)之中[...]
- 一個(gè)對(duì)象中的變化會(huì)導(dǎo)致其它對(duì)象中的變化嗎?(現(xiàn)在被稱作共同封閉原則——一起變化的類應(yīng)該放在同一個(gè)包中——由 Robert C. Martin 在他 1996 年的論文“Granularity
”中發(fā)布攀隔,比 Ivar Jacobson 的書晚了四年) - 它們是和同一個(gè)角色通信嗎皂贩?
- 這兩個(gè)對(duì)象都依賴第三個(gè)對(duì)象嗎栖榨?例如同一個(gè)界面對(duì)象或?qū)嶓w對(duì)象?
- 這個(gè)對(duì)象會(huì)執(zhí)行多個(gè)其它對(duì)象上的操作嗎明刷?(現(xiàn)在被稱作共同重用原則——一起被使用的類應(yīng)該放在同一個(gè)包中——由 Robert C. Martin 在他 1996 年的論文“Granularity
”中發(fā)布婴栽,比 Ivar Jacobson 的書晚了四年)
- 一個(gè)對(duì)象中的變化會(huì)導(dǎo)致其它對(duì)象中的變化嗎?(現(xiàn)在被稱作共同封閉原則——一起變化的類應(yīng)該放在同一個(gè)包中——由 Robert C. Martin 在他 1996 年的論文“Granularity
- 子系統(tǒng)劃分的另一個(gè)標(biāo)準(zhǔn)是不同子系統(tǒng)之間的通信應(yīng)該盡可能少(低耦合)
-
對(duì)大型項(xiàng)目來做,還有其它一些子系統(tǒng)劃分的標(biāo)準(zhǔn)遮精,例如:
- 不同的開發(fā)小組擁有不同的能力或者資源冯事,針對(duì)性地分配開發(fā)任務(wù)也許是值得的(這些小組還可能分布在不同的地點(diǎn))
- 在分布式環(huán)境中,每個(gè)邏輯節(jié)點(diǎn)需要的可能就是一個(gè)子系統(tǒng)(SOA格带、Web 服務(wù)以及微服務(wù))邪媳。
- 如果現(xiàn)存的產(chǎn)品可以在系統(tǒng)中使用,它可以被認(rèn)為是一個(gè)子系統(tǒng)(我們的系統(tǒng)所依賴的庫檬洞,例如ORM)
防腐層
防腐層基本就是兩個(gè)系統(tǒng)之間的中間件狸膏。它用來隔離兩個(gè)子系統(tǒng),讓它們都依賴防腐層而不是直接互相依賴添怔。這樣湾戳,如果我們重構(gòu)或者完全替換掉其中一個(gè)子系統(tǒng)時(shí),只需要更新防腐層广料,而不需要?jiǎng)悠渌淖酉到y(tǒng)砾脑。
在將一個(gè)新系統(tǒng)和遺留系統(tǒng)進(jìn)行集成時(shí)防腐層特別有用。為了不讓遺留的結(jié)構(gòu)限制我們?cè)O(shè)計(jì)新系統(tǒng)的想像力艾杏,我們會(huì)創(chuàng)建一個(gè)防腐層韧衣,將遺留子系統(tǒng)的 API 按照新的子系統(tǒng)的需要進(jìn)行適配。
它有三個(gè)主要關(guān)注點(diǎn):
- 按照客戶端子系統(tǒng)的需要對(duì)其它子系統(tǒng) API 進(jìn)行適配购桑;
- 對(duì)系統(tǒng)間傳遞的數(shù)據(jù)和命令進(jìn)行轉(zhuǎn)換畅铭;
- 根據(jù)需要建立單向或多向的通信。
當(dāng)我們無法控制全部子系統(tǒng)或某個(gè)子系統(tǒng)時(shí)勃蜘,使用這項(xiàng)技術(shù)的理由更加充分硕噩。但在我們能控制所有涉及的子系統(tǒng)時(shí),這項(xiàng)技術(shù)也有意義缭贡,盡管這些子系統(tǒng)設(shè)計(jì)良好只是擁有大相徑庭的模型炉擅,但是我們想要阻止一個(gè)模型對(duì)另一個(gè)模型的侵蝕(為了滿足一個(gè)子系統(tǒng)的需要而修改另一個(gè)子系統(tǒng))。
共享內(nèi)核
在某些情況下匀归,我們除了渴望完全隔離和解耦的組件之外坑资,在多個(gè)組件之間共享一些領(lǐng)域代碼也很有意義。
這會(huì)讓組件之間保持解耦穆端,盡管他們會(huì)和同一份代碼——共享內(nèi)核——耦合在一起袱贮。
例如,由一個(gè)組件觸發(fā)并由另外一個(gè)或多個(gè)組件監(jiān)聽的事件就是這樣的例子。但服務(wù)接口和事件實(shí)體也可能是這樣攒巍。
不過嗽仪,我們應(yīng)該限制共享內(nèi)核的大小,對(duì)它進(jìn)行修改時(shí)要小心翼翼柒莉,才不會(huì)毫不知情地破壞使用它的代碼闻坚。共享內(nèi)核中的代碼修改必須經(jīng)過其它使用它的團(tuán)隊(duì)的同意,這一點(diǎn)非常重要兢孝。
通用子域
子域是領(lǐng)域中非常獨(dú)立的一部分窿凤。通用子域不是特定于某個(gè)應(yīng)用的子域,它可以在任何類似的應(yīng)用中使用跨蟹。
例如雳殊,如果我們的應(yīng)用中有一部分是關(guān)于財(cái)務(wù)的,也許我們可以在應(yīng)用中使用現(xiàn)有的財(cái)務(wù)相關(guān)的庫窗轩。但是夯秃,無論以哪種方式實(shí)現(xiàn),哪怕我們沒有現(xiàn)成的庫可用而要自己構(gòu)建痢艺,如果這部分是通用子域仓洼,那么它就不是我們的核心業(yè)務(wù),它應(yīng)該被當(dāng)作必要的而不是決定性的因素堤舒。它不是我們應(yīng)用中最重要的部分色建,所以不是我們最好的專家重點(diǎn)關(guān)注的地方,毫無疑問它甚至不應(yīng)該在主要的源代碼之中出現(xiàn)舌缤,它可能是通過依賴管理工具安裝的镀岛。
總結(jié)
再一次聲明,這里我選擇探討多是關(guān)于單一職責(zé)友驮、低耦合、高內(nèi)聚驾锰、邏輯隔離的 DDD 概念卸留,這樣我們的應(yīng)用才能更一致、更簡(jiǎn)單椭豫、更快地響應(yīng)變化并適應(yīng)業(yè)務(wù)的需要耻瑟。
引用來源
1992 – Ivar Jacobson – Object-Oriented Software Engineering: A use case driven approach
1996 – Robert C. Martin – Granularity
2003 – Eric Evans – Domain-Driven Design: Tackling Complexity in the Heart of Software
2014 – Eric Evans – Domain-Driven Design Reference