18.清晰架構(gòu)(02): 超越同心圓分層(譯)

原文:https://herbertograca.com/2018/07/07/more-than-concentric-layers/

這篇文章是軟件架構(gòu)編年史()的一部分,這部編年史由一系列關(guān)于軟件架構(gòu)的文章組成。在這一系列文章中,我將寫下我對軟件架構(gòu)的學習和思考,以及我是如何運用這些知識的。如果你閱讀了這個系列中之前的文章,本篇文章的的內(nèi)容將更有意義。

我之前的這篇系列文章()中,我發(fā)表了一幅信息圖,展示了用來理解代碼單元類型之間聯(lián)系的心智地圖。

然而,我始終覺得有一部分內(nèi)容并沒有得到很好的體現(xiàn),但我不知道應(yīng)該如何更好地將它展示出來。這個部分就是共享內(nèi)核。

而且,我還發(fā)現(xiàn)了一些新內(nèi)容,在這篇博客中我將記錄下所有的內(nèi)容。

觀察上一篇博客中發(fā)表的信息圖,我們發(fā)現(xiàn)共享內(nèi)核位于圖的中心,看起來好像在領(lǐng)域?qū)觾?nèi)部,疊在代表領(lǐng)域上下文的同心圓之上。

盡管將共享內(nèi)核放在這個位置,但我要表達的并不是共享內(nèi)核會依賴其余部分的代碼,它也不是領(lǐng)域?qū)觾?nèi)部的另一個層次。

什么是共享內(nèi)核?!

共享內(nèi)核由 DDD 之父 Eric Evans 定義,它是多個限界上下文之間共享的代碼,由開發(fā)團隊決定:

[...] 兩個團隊同意共享的領(lǐng)域模型的子集。當然,和模型子集一起共享還包括代碼的子集,還有和這部分模型有關(guān)的數(shù)據(jù)庫設(shè)計。這部分明確要共享的內(nèi)容有著特殊的狀態(tài),而且在沒有和其他團隊達成一致的情況下不應(yīng)該修改。

Shared Kernel, Ward Cunningham 的 DDD wiki

所以基本上,它可能是任何類型的代碼:領(lǐng)域?qū)哟a、應(yīng)用層代碼、庫,隨便什么代碼。

然而,在我的這份心智地圖里,我將它當做一些特定類型的代碼的子集。在我的心智地圖里,共享內(nèi)核包含的是領(lǐng)域?qū)雍蛻?yīng)用層的代碼,這些代碼會在限界上下文之間共享,讓這些上下文可以互相通信。

這意味著,例如,一個或多個限界上下文觸發(fā)的事件可以在其它的限界上下文里被監(jiān)聽到。需要和這些事件一起共享的還有它們用到的所有數(shù)據(jù)類型,例如:實體 ID、值對象、枚舉,等等。事件不應(yīng)該直接使用像實體這樣的復(fù)雜對象,因為將它們序列化到隊列中或是從隊列中反序列化時都會遇到一些問題,所以共享的代碼不應(yīng)該太寬泛。

當然,如果我們手中的是一個由不同語言開發(fā)的微服務(wù)組成的多語言系統(tǒng),共享內(nèi)核必須是描述性的語言,格式是 json、xml、yaml 或者其它,這樣所有的微服務(wù)都能理解。

因此,共享內(nèi)核就完全和其余的代碼以及組件完全解耦了。這樣很好,因為這意味著盡管組件耦合了共享內(nèi)核,但組件之間不再耦合。共享代碼可以被清晰地識別出來,并輕松地提取到一個獨立的庫中。

如果我們決定將一個限界上下文從單體中分離出來并提取成一個微服務(wù),這也會很方便。我對共享代碼了然于心,可以輕松地將共享內(nèi)核提取到一個庫中。而這個庫即可以安裝到單體中,也可以安裝到微服務(wù)中。

所以回顧一下,在我的心智地圖中,應(yīng)用核心依賴共享內(nèi)核,共享內(nèi)核包括了在限界上下文之間共享的領(lǐng)域?qū)雍蛻?yīng)用層代碼。

當語言不夠用時…

這樣,我們得到了包含同心圓層次的應(yīng)用代碼,而應(yīng)用核心依賴著支撐所有這些代碼的共享內(nèi)核。

我們還可以說所有代碼都依賴它們所使用的編程語言,但我們對這樣顯而易見的事實熟視無睹。

然而我要拋出這個事實,原因是還有一個問題“當語言結(jié)構(gòu)不夠用時我們該怎么辦?!”。顯然我們會自己創(chuàng)造語言結(jié)構(gòu),來填補這些語言的瑕疵。隨后我會提出下一個重要的問題“我們?nèi)绾蝹鬟f這些代碼之所以存在地背后原理?我們把它放在哪里?我們?nèi)绾吻逦乇磉_使用它的時機?使用方法?”

我見過做法的是,我自己也是這樣做的,將代碼放在一個名為 Utils 或 Commons 的包里。但它最終會變成一個筐子,當我們不知道代碼該放在哪兒時,就會一股腦兒全扔進這個筐子里!不同類型的代碼,不同用途的代碼,還有不同用法(包裝在適配器里使用,或是直接使用...)的代碼最后都被扔在這里,這個包沒有概念性的含義,沒有一致性,沒有內(nèi)聚性,一點也不明確,到處充斥著歧義。

我想拋棄 Utils 和 Commons 包!

每一個包都必須在概念上內(nèi)聚!何時使用包以及如何使用包必須是明確的!不能有任何歧義!

因此,舉個例子,如果我們的應(yīng)用有一些特殊的和 CLI 交互的方式,我們可以把相關(guān)代碼放在“Acme/App/Infrastructure/Cli/SpecialCli”命名空間下,而不是放在“Acme/Util/SpecialCli”命名空間下。前一個名字告訴我們這個包和 CLI 有關(guān),它是“Acme”公司的應(yīng)用“App”的“Infrastructure”(基礎(chǔ)設(shè)施)的一部分。既然它是應(yīng)用基礎(chǔ)設(shè)施的一部分,也就是說應(yīng)用核心中還應(yīng)該有一個端口,它必須遵循這個端口。

另一種情況是,如果我們發(fā)現(xiàn)包是這種語言自己應(yīng)該/能夠具備的一些東西,我們可以把它放在可以體現(xiàn)出這層含義的命名空間之下,比如“Acme/PhpExtension/SpecialCli”。這個名字告訴我們這個包應(yīng)該被看作是語言自身的一部分,因此其中的代碼應(yīng)該被其它代碼像語言結(jié)構(gòu)一樣直接使用。但是,如果其它公司依賴這個包時,顯然不會傻傻地直接依賴它,而是為它創(chuàng)建端口/適配器,這是更妥善的處理方法,這樣他們可以換掉它。然而,如果這個包是我們自己來負責,我們就可以決定把它當成語言的一部分來對待,因為出現(xiàn)尋找替代品的風險的幾率要小得多。這里總有一些權(quán)衡。

另一個我們可以認為是語言的一部分的例子是 PHP 中的 UUID。我可以不把它想成是語言的一部分,因為每隔一段時間就會有一個新版本并造成一些維護的負擔;但它卻是一個通用、廣泛和一致的概念,足以成為語言的一部分。

所以,為什么不創(chuàng)建一個 UUID 的實現(xiàn),把它當成 PHP 自身的一部分來使用呢?就像我們使用 DateTime 對象一樣?只要實現(xiàn)還在我們的掌控之中,我覺得沒有什么壞處。

那么 Doctrine Query Language (DQL) 呢? (Doctrine 是 Hibernate 在 PHP 中的移植) 我們能把 DQL 當成 SQL、Elasticsearch QL 或 Mongo QL 嗎?

總結(jié)

所以總結(jié)一下,我在宏觀層面發(fā)現(xiàn)了四種主要的代碼類型,而且我認為在代碼組織中將它們清晰地展現(xiàn)出來,是我們避免最終形成大泥球的關(guān)鍵。

于我而言,不能質(zhì)疑的真相是架構(gòu)就在那里,唯一的問題是:它在不在我們掌控之中?!。

因此,讓我們清晰地組織好代碼,來幫助我們表達架構(gòu),全盤或者部分遵循我的心智地圖,遵循你自己的心智地圖,或者遵循其他人的心智地圖都可以,但是請使用一致的推理思路來組織代碼,讓項目可以使用結(jié)構(gòu)和代碼組織來清晰地表達架構(gòu)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容