19. 在代碼中展現(xiàn)架構(gòu)和領(lǐng)域(譯)

原文:https://herbertograca.com/2019/06/05/reflecting-architecture-and-domain-in-code/

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

在創(chuàng)建應(yīng)用的時候奶赠,讓它可以工作易如反掌。要讓它在處理大量數(shù)據(jù)的情況下仍然保持性能药有,會有點困難毅戈。但是最難的挑戰(zhàn)是構(gòu)建一個真正可以維系多年(十年、二十年甚至一百年)的應(yīng)用程序愤惰。

我工作過的大多數(shù)公司都有每三到五年就重建應(yīng)用的歷史苇经,有時甚至不到兩年就要重建。這種做法成本極高宦言,它將極大地影響應(yīng)用程序的成功扇单,進而極大地影響公司的成功,還會讓開發(fā)人員在亂成一鍋粥的代碼庫種凌亂奠旺,讓他們想萌生辭職的想法蜘澜。任何一家志向遠大的正經(jīng)公司施流,都無法承受任何經(jīng)濟上、時間上鄙信、聲譽上瞪醋、客戶上、人才上的損失扮碧。

讓應(yīng)用程序保持可維護性的基石是讓代碼能夠反映出架構(gòu)和領(lǐng)域趟章,這對防止所有棘手問題至關(guān)重要。

清晰架構(gòu)是對比我經(jīng)驗更豐富的開發(fā)者所倡導的原則以及實踐的合理解釋慎王,也是對我如何組織代碼庫使之反映出項目架構(gòu)與領(lǐng)域并便于溝通的經(jīng)驗總結(jié)蚓土。

在我的上一篇博客譯文)里,我將這些理念匯總起來并用信息圖和 UML 圖呈現(xiàn)赖淤,試圖建立我思考的概念圖譜蜀漆。

然而,我們怎樣才能把實踐落實到代碼中呢咱旱?

在這篇博客里确丢,我將說明我是如何在代碼中體現(xiàn)一個項目的結(jié)構(gòu)和領(lǐng)域的,還將提出一個通用的結(jié)構(gòu)吐限,我認為它能幫助我們規(guī)劃好可維護性鲜侥。

我的兩張腦圖

在這個系列的前兩篇文章里我介紹了兩張腦圖,我用它們來思考代碼和組織代碼倉庫诸典,至少在我腦海里是這樣想的描函。

第一張腦圖由一系列同心圓層級組成,它們最終按照業(yè)務(wù)維度的應(yīng)用模塊切分狐粱,形成組件舀寓。在這張圖里,依賴的方向由外向內(nèi)肌蜻,意味著內(nèi)層對外層可見互墓,而外層對內(nèi)層不可見。

第二張則是一組平面的層級蒋搜,其中最上面的一層就是前面這張同心圓篡撵,下一層是組件之間共享的代碼(共享內(nèi)核),再下一層使是我們自己對編程語言的擴展豆挽,最下面一層則是實際使用的編程語言酸休。這里的依賴方向是自上而下的。

體現(xiàn)架構(gòu)的代碼風格

使用體現(xiàn)架構(gòu)的代碼風格祷杈,意味著代碼風格(編碼規(guī)范、類/方法/變量命名約定渗饮、代碼結(jié)構(gòu)...)某種程度上可以和閱讀代碼的人交流領(lǐng)域和架構(gòu)的設(shè)計意圖但汞。要實現(xiàn)體現(xiàn)架構(gòu)的代碼風格迟隅,主要有兩種思路迁筛。

[…] 體現(xiàn)架構(gòu)的代碼風格能讓你給代碼的閱讀者留下提示,幫助他們正確地推斷出設(shè)計意圖。
George Fairbanks

第一種思路是通過代碼制品的名字(類粱挡、變量、模塊...)來傳達領(lǐng)域和架構(gòu)的含義锨苏。因此公罕,如果一個類是處理收據(jù)(Invoice)實體的倉庫(Repository),我們就應(yīng)該將它命名成InvoiceRepository容贝,從這個名字我們就可以看出自脯,它處理的是收據(jù)領(lǐng)域的概念,而它在架構(gòu)中被當做一個倉庫斤富。這可以幫助我們理解它應(yīng)該放在哪個地方膏潮,何時使用它以及如何使用它。但是满力,我認為代碼倉庫中并不是每個代碼制品都需要這樣做焕参,例如,我覺得不必為每個實體(Entity)都加上后綴Entity油额,這樣做就有些畫蛇添足叠纷,徒增噪音。

[…] 代碼應(yīng)該體現(xiàn)架構(gòu)潦嘶。換句話說涩嚣,我一看到代碼,就應(yīng)該能夠清晰地區(qū)分出各種組件[…]
Simon Brown

第二種思路是讓代碼倉庫中的頂級制品明確地區(qū)分出各個子域衬以,即領(lǐng)域維度的模塊缓艳,也就是組件。

第一種思路應(yīng)該很清楚看峻,無需贅述阶淘。但第二種思路有點兒微妙,我們得深入探討一下互妓。

讓架構(gòu)清晰的展現(xiàn)出來

在我的第一張圖里溪窒,我們已經(jīng)看到,在最粗粒度的層級上冯勉,我們只有三種不同用途的代碼:

  • 用戶界面澈蚌,這里的代碼就是為了適配某個用例的傳達機制;
  • 應(yīng)用核心灼狰,這里的代碼就是用例和領(lǐng)域邏輯宛瞄;
  • 基礎(chǔ)設(shè)施,這里的代碼就是為了適配應(yīng)用核心所需的工具/庫交胚。

因此份汗,在源代碼的根目錄下我們可以創(chuàng)建三個文件夾來體現(xiàn)這三類代碼盈电,一個文件夾對應(yīng)一個類別的代碼。這三個文件夾表示三個命名空間杯活,稍后我們甚至可以創(chuàng)建測試來斷言核心對用戶界面和基礎(chǔ)設(shè)施可見匆帚,反過來卻不可見,也就是說旁钧,我們可以測試由外向內(nèi)的依賴方向吸重。

用戶界面

一個 Web 企業(yè)應(yīng)用通常擁有多套 API,例如歪今,一套給客戶端使用的 REST API嚎幸,還有一套給第三方應(yīng)用使用的 web-hook, 業(yè)務(wù)還有一套需要維護的遺留 SOAP API彤委,或者還有一套給全新移動應(yīng)用使用的 GraphQL API…

這樣的應(yīng)該通常還有一些 CLI 命令鞭铆,用于定時作業(yè)(Cron Job)或按需的維護操作。

當然焦影,還有普通用戶可以使用的網(wǎng)站本身车遂,但也許還有另一個供應(yīng)用管理員使用的網(wǎng)站。

這些全都是同一個應(yīng)用的不同視圖斯辰,全都是同一個應(yīng)用的不同用戶界面舶担。

實際上我們的應(yīng)用可能擁有多個用戶界面,其中有些還是供非人類用戶(第三方應(yīng)用)使用的彬呻。我們通過文件/命名空間來區(qū)分并隔離這些用戶界面衣陶,來展現(xiàn)出這一點。

用戶界面主要有三類:API闸氮、CLI 和網(wǎng)站剪况。所以我們在UserInterface根命名空間里為每個類別創(chuàng)建一個文件夾,將不同界面的類型清晰地區(qū)分開來蒲跨。

下一步译断,如果有必要的話,我們還可以繼續(xù)深入每種類型的命名空間或悲,再創(chuàng)建更細分類的用戶界面的命名空間(CLI 可能不需要再細分了)孙咪。

基礎(chǔ)設(shè)施

和用戶界面一樣,我們的應(yīng)用使用了多種工具(庫和第三方應(yīng)用)巡语,例如 ORM翎蹈、消息隊列、SMS 提供商男公。

此外荤堪,上述每一種工具都可以有不同的實現(xiàn)。例如,考慮一家公司業(yè)務(wù)擴張到另一個國家的情況逞力,由于價格的因素曙寡,不同的國家最好采用不同的 SMS 提供商:我們需要端口相同的適配器的不同實現(xiàn),這樣使用時可以互相替換寇荧。另一個例子是對數(shù)據(jù)庫 Schema 進行重構(gòu)或者切換數(shù)據(jù)庫引擎,需要(或決定要)切換 ORM 時:我們會在應(yīng)用中注入兩種 ORM 適配器执隧。

因此揩抡,在Infrastructure命名空間來說,我們先給每一種工具類型創(chuàng)建一個命名空間(ORM镀琉、MessageQueue峦嗤、SmsClient),然后再每一種工具類型內(nèi)部為每一種用到的供應(yīng)商(Doctrine屋摔、Propel烁设、MessageBird、Twilio...)的適配器在創(chuàng)建一個命名空間钓试。

核心

Core命名空間下装黑,可以按照最粗粒度的層級劃分出三類代碼: 組件(Component)共享內(nèi)核(Shared Kernel)端口(Port)弓熏。為這三個類別創(chuàng)建文件夾/命名空間恋谭。

組件

Component 命名空間下,我們?yōu)槊總€組件創(chuàng)一個命名空間挽鞠,然后在每個組件命名空間下疚颊,我們再分別為應(yīng)用(Application)層和領(lǐng)域(Domain)層分別創(chuàng)建一個命名空間。 在 ApplicationDomain 命名空間下信认,我們先將全部類放在一起材义,隨著類的數(shù)量不斷增加,再來考慮必要的分組(我覺得一個文件夾下就放一個類有些矯枉過正嫁赏,所以我寧愿在必要時再進行分組)其掂。

這是我們就要考慮是按照業(yè)務(wù)主題(收據(jù)、交易...)分組還是按照技術(shù)作用(倉庫橄教、服務(wù)清寇、值對象...)分組,但我覺得無論怎樣分組影響都不大护蝶,因為這已經(jīng)是整個代碼組織樹的葉子節(jié)點了华烟,如果需要,在整個組織結(jié)構(gòu)的最底端進行調(diào)整也很簡單持灰,不會影響代碼倉庫的其它部分盔夜。

端口

Infrastructure 命名空間一樣,Port 命名空間里核心使用的每一種工具都有一個命名空間,核心通過這些代碼才能使用底層的這些工具喂链。

這些代碼還會被適配器使用返十,它們的作用就是端口和真正工具之間的轉(zhuǎn)換。這種形式簡單得不能再簡單了椭微,端口就是一個接口洞坑,但很多時候它還需要值對象、DTO蝇率、服務(wù)迟杂、構(gòu)建起、查詢對象甚至是倉庫本慕。

共享內(nèi)核

我們把在組件之間共享的代碼放到 Shared Kernel 命名空間下排拷。嘗試了幾種不同的共享內(nèi)核內(nèi)部結(jié)構(gòu)之后,我無法找到一種適用于所有情況的結(jié)構(gòu)锅尘。有些代碼和Core\Component一樣按組件劃分很合理(例如 Entity ID 顯然屬于一個組件)监氢,有些代碼這樣劃分卻不合適(例如,事件可能被多個組件觸發(fā)或監(jiān)聽)藤违。也許要結(jié)合使用兩種劃分的思路浪腐。

用戶區(qū)里的編程語言擴展

最后,我們還有一些自己對編程語言的擴展纺弊。這個系列中前面一篇文章已經(jīng)討論過牛欢,這些代碼本可以放在編程語言中,卻因為某些原因沒有淆游。比如傍睹,在 PHP 中我們可以想到的是 DateTime 類,它基于 PHP 提供的類擴展犹菱,提供了一些額外的方法拾稳。另一個例子是 UUID 類,盡管 PHP 沒有提供腊脱,但是這個類天然就是純粹的访得、對領(lǐng)域無感,因此可以在任意項目中使用陕凹,并且不依賴任何領(lǐng)域悍抑。

這些代碼用起來和編程語言自己的提供的功能沒啥區(qū)別,因此我們要完全掌控這些代碼杜耙。然而搜骡,這并不是意味著我們不能使用第三方庫。我們能用而且應(yīng)該用佑女,只要合理记靡,但是這些庫應(yīng)該用我們自己的實現(xiàn)包裝起來(這樣的話我們可以方便的切換背后的第三方庫)谈竿,而應(yīng)用代碼應(yīng)該直接使用這些包裝代碼。最終摸吠,這些代碼可以自成項目空凸,使用自己的 CVS 倉庫,被多個項目使用寸痢。

強化架構(gòu)

上述就是所有我們決定要落地的思路和方法呀洲,這需要大量投入,也不容易掌握轿腺。就算我們掌握了所有思路和方法两嘴,但我們終究還是人類,所以我們一定會犯錯族壳,我們的同事也會犯錯,事情就是這樣趣些。

就像我們?yōu)榱吮苊鈱懘a時犯的錯進入生產(chǎn)環(huán)境而編寫測試一樣仿荆,我們也必須對代碼倉庫的結(jié)構(gòu)做點什么。

在 PHP 的世界里坏平,我們有一個叫做 Deptrac 的小工具可以做這種檢查(但我敢打保票其它編程語言也有類似的工具)拢操,這個小工具由 Sensiolabs 創(chuàng)建。我們可以通過一個 yaml 文件進行配置舶替,我們可以在其中配置有哪些層級令境,以及層級之間有哪些依賴。然后我們使用命令行執(zhí)行測試顾瞪,這意味著測試可以輕松地在 CI 中執(zhí)行舔庶,就像我們可以在 CI 種執(zhí)行其它測試一樣。(譯注陈醒,對于 Java 語言來說惕橙,也有類似的工具 https://www.archunit.org/,可以用它把依賴關(guān)系的規(guī)則寫成自動化測試钉跷,但是不能生成依賴圖弥鹦。)。

我們還可以創(chuàng)建依賴圖爷辙,將依賴可視化地展示出來彬坏,包括那些違反實現(xiàn)配置好的規(guī)則集的依賴關(guān)系:

總結(jié)

應(yīng)用遵循某種領(lǐng)域結(jié)構(gòu)組成,也遵循某種技術(shù)結(jié)構(gòu)(即架構(gòu))組成膝晾。這兩種結(jié)構(gòu)才是一個應(yīng)用的與眾不同之處栓始,而不是它使用的工具、庫或者傳達機制玷犹。如果我們想讓一個應(yīng)用可以長時間的維護混滔,這兩種結(jié)構(gòu)都要清晰的體現(xiàn)在代碼倉庫中洒疚,這樣開發(fā)者才能知道、理解坯屿、遵循油湖,并在需要時改進。

這種清晰度讓我們可以在編碼的同時理解邊界领跛,這能反過來幫助我們保持應(yīng)用的模塊化設(shè)計乏德,做到高內(nèi)聚低耦合。

再一次重申吠昭,之前文章里提到的這些思路和實踐大多來自于遠比我優(yōu)秀和經(jīng)驗豐富的開發(fā)者喊括。我和我在不同公司的同事們進行過反復(fù)討論,也在企業(yè)應(yīng)用代碼中進行過嘗試矢棚,在我參與過的項目中都能得到很好地應(yīng)用郑什。

但是,我堅信沒有銀彈蒲肋,沒有均碼的鞋子蘑拯,沒有圣杯

本文介紹的思路和解耦可以被視為適用于大多是企業(yè)應(yīng)用的通用模板兜粘,如過有必要申窘,不要猶豫,對其進行調(diào)整孔轴。我們總是要對上下文進行評估并竭盡所能剃法,但我希望并相信這個模板是一個不錯的開始,至少值得一試路鹰。

如果你想看看實現(xiàn)了這個模板的 Demo 項目贷洲,我 fork 了 Symfony Demo 應(yīng)用并按照上面的思路進行了重構(gòu)。你可以在這里找到我的重構(gòu)悍引。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恩脂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子趣斤,更是在濱河造成了極大的恐慌俩块,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浓领,死亡現(xiàn)場離奇詭異玉凯,居然都是意外死亡,警方通過查閱死者的電腦和手機联贩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門漫仆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泪幌,你說我怎么就攤上這事盲厌∈鹫眨” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵吗浩,是天一觀的道長建芙。 經(jīng)常有香客問我,道長懂扼,這世上最難降的妖魔是什么禁荸? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮阀湿,結(jié)果婚禮上赶熟,老公的妹妹穿的比我還像新娘。我一直安慰自己陷嘴,他們只是感情好映砖,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著灾挨,像睡著了一般啊央。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涨醋,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音逝撬,去河邊找鬼浴骂。 笑死,一個胖子當著我的面吹牛宪潮,可吹牛的內(nèi)容都是我干的溯警。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼狡相,長吁一口氣:“原來是場噩夢啊……” “哼梯轻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起尽棕,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤喳挑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后滔悉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伊诵,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年回官,在試婚紗的時候發(fā)現(xiàn)自己被綠了曹宴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡歉提,死狀恐怖笛坦,靈堂內(nèi)的尸體忽然破棺而出区转,到底是詐尸還是另有隱情,我是刑警寧澤版扩,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布废离,位于F島的核電站,受9級特大地震影響资厉,放射性物質(zhì)發(fā)生泄漏厅缺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一宴偿、第九天 我趴在偏房一處隱蔽的房頂上張望湘捎。 院中可真熱鬧,春花似錦窄刘、人聲如沸窥妇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽活翩。三九已至,卻和暖如春翻伺,著一層夾襖步出監(jiān)牢的瞬間材泄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工吨岭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拉宗,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓辣辫,卻偏偏與公主長得像旦事,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子急灭,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 原文: iOS應(yīng)用架構(gòu)談 view層的組織和調(diào)用方案 iOS應(yīng)用架構(gòu)談 開篇 iOS應(yīng)用架構(gòu)談 網(wǎng)絡(luò)層設(shè)計方案 i...
    難卻卻閱讀 1,262評論 0 7
  • 星星發(fā)亮是為了讓每一個人有一天都能找到屬于自己的星星姐浮。 我看過黃昏追逐黎明 ,卻沒沒看過你葬馋。 看過楚門的世界卖鲤,感觸...
    魚兒雜記閱讀 1,192評論 1 1
  • 文/塘楓 ——不含任何元素的集合即為空集。 她是班里的異類点楼。 關(guān)于她的形象扫尖,真可以用蓬頭垢面來形容。頂著一頭亂糟糟...
    塘楓Legend閱讀 450評論 3 2
  • 無意中打開《跟著貝爾去冒險》這檔綜藝節(jié)目掠廓,剛看完一期就深深地著迷了换怖。冒險其實是一個直面內(nèi)心的旅程,是一個面對自己內(nèi)...
    小水_ca59閱讀 1,289評論 0 3
  • 強化自己的時間段意識蟀瞧,每天早晨九點半沉颂,和每天晚上九點半条摸。早上整理早教。晚上整理我自己铸屉。中午一點搶知識钉蒲。
    零度清爽閱讀 74評論 0 0