對單體系統(tǒng)優(yōu)缺點評判到位:拆分Shopify單體工程的經(jīng)驗分享

Shopify是現(xiàn)存最大的Ruby on Rails代碼庫之一葱峡。它已被超過一千名開發(fā)人員使用了十多年砚哗。它封裝了來自計費商家,管理第三方開發(fā)者應(yīng)用程序砰奕,更新產(chǎn)品蛛芥,處理運輸?shù)仍S多不同功能。它最初是作為整體構(gòu)建的军援,這意味著所有這些不同的功能都構(gòu)建在相同的代碼庫中仅淑,它們之間沒有邊界。多年來胸哥,這種架構(gòu)為我們工作涯竟,但最終,我們達到了這樣一個臨界點空厌,即單體monolith的缺點超過了好處庐船。我們必須選擇如何進行分解。

微服務(wù)近年來大受歡迎嘲更,并被吹捧為解決所有單體問題的最終解決方案筐钟。然而,我們自己的集體經(jīng)驗告訴我們赋朦,沒有一種尺寸適合所有最佳解決方案篓冲,微服務(wù)將帶來他們自己的一系列挑戰(zhàn)。我們選擇將Shopify發(fā)展為模塊化單體宠哄,這意味著我們將所有代碼保存在一個代碼庫中壹将,但確保在不同組件之間定義和遵守邊界。

每個軟件體系結(jié)構(gòu)都有自己的優(yōu)缺點琳拨,根據(jù)應(yīng)用程序的增長階段瞭恰,不同的解決方案對于應(yīng)用程序是有意義的。從單體到模塊化是我們的下一個合乎邏輯的步驟狱庇。

單體架構(gòu)


根據(jù)維基百科惊畏,monolith是一個軟件系統(tǒng),其中功能上可區(qū)分的方面都是交織在一起的密任,而不是包含架構(gòu)上獨立的組件颜启。對于Shopify來說,這意味著處理計算運費的代碼與處理結(jié)賬的代碼一起存在浪讳,并且?guī)缀鯖]有阻止他們互相打電話缰盏。隨著時間的推移,這導(dǎo)致處理不同業(yè)務(wù)流程的代碼之間的極高耦合。

單體系統(tǒng)的優(yōu)點


單體架構(gòu)是最容易實現(xiàn)的口猜。如果沒有實施架構(gòu)設(shè)計负溪,一般結(jié)果可能就是一個單體。在Ruby on Rails中尤其如此济炎,由于應(yīng)用程序級別的所有代碼的全局可用性川抡,非常適合構(gòu)建單體。單體架構(gòu)可以將應(yīng)用程序推向極致须尚,因為它易于構(gòu)建崖堤,并允許團隊在一開始就非常快速地移動耐床,以便更早地將產(chǎn)品提供給客戶密幔。

將整個代碼庫保存在一個位置并將應(yīng)用程序部署到一個位置具有許多優(yōu)點。您只需要維護一個存儲庫撩轰,并且能夠輕松搜索并查找一個文件夾中的所有功能胯甩。它還意味著只需要維護一個測試和部署管道,這取決于應(yīng)用程序的復(fù)雜性堪嫂,可以避免很多開銷蜡豹。這些管道的創(chuàng)建,定制和維護成本很高溉苛,因為它需要齊心協(xié)力才能確保所有管道的一致性镜廉。由于所有代碼都部署在一個應(yīng)用程序中,因此數(shù)據(jù)都可以存儲在單個共享數(shù)據(jù)庫中愚战。每當需要一個數(shù)據(jù)時娇唯,它就是一個簡單的數(shù)據(jù)庫查詢來檢索它。

由于單體部署在同一個地方寂玲,因此只需要管理一組基礎(chǔ)設(shè)施塔插。大多數(shù)Ruby應(yīng)用程序都帶有數(shù)據(jù)庫,Web服務(wù)器拓哟,后臺作業(yè)功能想许,然后可能還有其他基礎(chǔ)架構(gòu)組件,如Redis断序,Kafka流纹,Elasticsearch等等。這些其他基礎(chǔ)架構(gòu)還會增加可能的故障點违诗,從而降低應(yīng)用程序的彈性和安全性漱凝。

在多個獨立服務(wù)上選擇單體架構(gòu)最顯著的好處之一是,您可以直接調(diào)用不同的組件诸迟,而不需要通過Web服務(wù)API進行通信茸炒,這意味著您不必擔(dān)心API版本管理和向后兼容性愕乎,以及潛在的滯后調(diào)用。

單體系統(tǒng)的缺點


但是壁公,如果應(yīng)用程序達到一定規(guī)母新郏或者團隊建設(shè)達到一定規(guī)模,它最終將超越單體架構(gòu)紊册。這發(fā)生在2016年的Shopify笛粘,由于構(gòu)建和測試新功能的不斷增加的挑戰(zhàn)而顯而易見。具體來說湿硝,有幾件事情可以作為我們的絆腳石。

應(yīng)用程序非常脆弱润努,新代碼具有意想不到的影響关斜。做出看似無害的變化可能會引發(fā)一系列無關(guān)的測試失敗。例如铺浇,如果計算我們的運費的代碼被調(diào)用到計算稅率的代碼中哥童,那么對我們計算稅率的方式進行更改可能會影響運費計算的結(jié)果止潮,但這可能并不明顯。這是高耦合和缺乏邊界的結(jié)果,這也導(dǎo)致難以編寫的測試隙轻,并且在CI上運行非常慢。

在Shopify中進行開發(fā)需要大量的上下文來進行看似簡單的更改螃诅。當新的Shopifolk上架并開始了解代碼庫時诲锹,他們在生效之前需要獲取的信息量是巨大的。例如惑折,加入運輸團隊的新開發(fā)人員應(yīng)該只需要了解運輸業(yè)務(wù)邏輯的實施授账,然后才能開始構(gòu)建。然而惨驶,現(xiàn)實情況是白热,他們還需要了解訂單的創(chuàng)建方式,我們?nèi)绾翁幚砀犊畹鹊却植罚驗橐磺卸际侨绱私豢椩谝黄鹞萑贰_@對于一個人來說只是為了發(fā)布他們的第一個特征而必須堅持下去的知識太多了。復(fù)雜的整體應(yīng)用導(dǎo)致陡峭的學(xué)習(xí)曲線续扔。

我們遇到的所有問題都是代碼中不同功能之間缺乏界限的直接結(jié)果攻臀。很明顯,我們需要減少不同域之間的耦合纱昧。

微服務(wù)架構(gòu)


微服務(wù)是一種非常時髦的解決方案茵烈。微服務(wù)架構(gòu)是一種應(yīng)用程序開發(fā)方法,其中大型應(yīng)用程序構(gòu)建為一套獨立部署的小型服務(wù)砌些。雖然微服務(wù)可以解決我們遇到的問題呜投,但它們會帶來另一整套問題加匈。

我們必須維護多個不同的測試和部署管道,并承擔(dān)每項服務(wù)的基礎(chǔ)架構(gòu)開銷仑荐,同時并不總是能夠在需要時訪問我們需要的數(shù)據(jù)雕拼。由于每個服務(wù)都是獨立部署的,因此服務(wù)之間的通信意味著跨越網(wǎng)絡(luò)粘招,這會增加延遲并降低每次呼叫的可靠性啥寇。此外,跨多個服務(wù)的大型重構(gòu)可能很繁瑣洒扎,需要對所有相關(guān)服務(wù)進行更改并協(xié)調(diào)部署辑甜。

模塊化單體


我們想要一種解決方案,在不增加部署單元數(shù)量的情況下增加模塊化袍冷,使我們能夠獲得單塊和微服務(wù)的優(yōu)勢磷醋,而沒有太多的缺點。

模塊化整體是一種系統(tǒng)胡诗,其中所有代碼都為單個應(yīng)用程序提供支持邓线,并且在不同域之間存在嚴格的強制邊界。

Shopify的Modular Monolith實現(xiàn):組件化


很明顯煌恢,我們已經(jīng)超越了單體結(jié)構(gòu)骇陈,并且它正在影響開發(fā)人員的生產(chǎn)力和幸福感,我們已經(jīng)向在我們的核心系統(tǒng)中工作的所有開發(fā)人員發(fā)送了一項調(diào)查瑰抵,以確定主要的難點你雌。我們知道我們遇到了問題,但我們希望在提出解決方案時能夠獲得數(shù)據(jù)信息二汛,以確保它能夠真正解決我們遇到的問題匪蝙,而不僅僅是傳聞中的問題。

該調(diào)查的結(jié)果告知我們決定拆分我們的代碼庫习贫。在2017年初逛球,一個小而強大的團隊被組合起來解決這個問題。該項目最初被命名為“Break-Core-Up-Into-Multiple-Pieces”苫昌,最終演變?yōu)椤敖M件化”颤绕。

代碼組織


他們選擇解決的第一個問題是代碼組織。目前祟身,我們的代碼組織得像典型的Rails應(yīng)用程序:軟件概念(模型奥务,視圖,控制器)袜硫。目標是通過真實世界的概念(如訂單氯葬,運輸,庫存和計費)對其進行重新組織婉陷,以便更容易找到代碼帚称,找到理解代碼的人官研,并了解他們的個別部分。

每個組件都將構(gòu)建為自己的迷你rails應(yīng)用程序闯睹,目標是最終將它們命名為ruby模塊戏羽。希望這個新組織能夠突出那些不必要耦合的領(lǐng)域。

提出最初的組件清單涉及公司每個領(lǐng)域的利益相關(guān)者的大量研究和投入楼吃。我們通過在一個大型電子表格中列出每個ruby類(大約6000個)并手動標記它所屬的組件來完成此操作始花。即使在此過程中沒有更改代碼,它仍然觸及整個代碼庫孩锡,如果操作不正確可能存在風(fēng)險酷宵。

我們在自動腳本構(gòu)建的一個大爆炸PR中實現(xiàn)了這一改革舉措。由于引入的更改只是文件移動躬窜,因此可能發(fā)生的故障將導(dǎo)致我們的代碼不知道在何處查找對象定義浇垦,從而導(dǎo)致運行時錯誤。我們的代碼庫經(jīng)過了充分的測試斩披,因此通過在本地和CI中運行我們的測試而不會出現(xiàn)故障,以及在本地和分段上運行盡可能多的功能讹俊,我們能夠確保沒有遺漏任何東西垦沉。我們選擇在一個PR中完成所有操作,因此我們只會盡可能少地破壞所有開發(fā)人員仍劈。這種變化的一個不幸的缺點是厕倍,當文件移動被錯誤地跟蹤為刪除和創(chuàng)建而不是重命名時,我們在Github中丟失了很多Git歷史記錄贩疙。我們?nèi)匀豢梢允褂枚锿洹碜粉櫰鹪磄it-follow選項跟隨文件移動的歷史,但是这溅,Github不理解這一舉??動组民。

隔離依賴關(guān)系


下一步是通過將業(yè)務(wù)域彼此分離來隔離依賴關(guān)系。每個組件都定義了一個干凈的專用接口悲靴,其域邊界通過公共API表示臭胜,并對其關(guān)聯(lián)數(shù)據(jù)進行獨占所有權(quán)。雖然團隊無法在整個Shopify代碼庫中實現(xiàn)這一點癞尚,因為它需要來自每個業(yè)務(wù)領(lǐng)域的專家耸三,但他們確實定義了模式并提供了完成任務(wù)的工具。

我們在內(nèi)部開發(fā)了一個名為Wedge的工具浇揩,它跟蹤每個組件朝著隔離目標的進展仪壮。它突出顯示任何違反域邊界的行為(當通過除公共定義的API之外的任何組件訪問另一個組件時)以及跨邊界的數(shù)據(jù)耦合。為實現(xiàn)這一目標胳徽,我們編寫了一個工具积锅,在CI期間掛鉤到Ruby跟蹤點以獲得完整的調(diào)用圖爽彤。然后,我們按組件對調(diào)用者和被調(diào)用者進行排序乏沸,僅選擇跨組件邊界的調(diào)用淫茵,并將它們發(fā)送到Wedge。除了這些調(diào)用之外蹬跃,我們還會從代碼分析中發(fā)送一些其他數(shù)據(jù)匙瘪,例如ActiveRecord關(guān)聯(lián)和繼承。Wedge然后確定哪些跨組件事物(調(diào)用蝶缀,關(guān)聯(lián)丹喻,繼承)是正確的,哪些是違反的翁都。通常:

  • 跨組件關(guān)聯(lián)總是違反組件化
  • 調(diào)用只適用于明確公開的內(nèi)容
  • 繼承將類似碍论,但尚未完全實現(xiàn)

Wedge然后計算總分并列出每個組件的違規(guī)。下一步柄慰,我們將繪制隨時間變化的分數(shù)趨勢鳍悠,并顯示有意義的差異,以便人們可以看到分數(shù)變化的原因和時間坐搔。

運行邊界


從長遠來看藏研,我們希望更進一步,并以編程方式強制執(zhí)行這些邊界概行。Dan Manges的這篇博客文章 提供了一個應(yīng)用團隊如何實現(xiàn)邊界實施的詳細示例蠢挡。雖然我們?nèi)栽谘芯课覀兿胍捎玫姆椒ǎ呒売媱澥亲屆總€組件僅加載其明確依賴的其他組件凳忙。如果它試圖訪問未聲明依賴的組件中的代碼业踏,則會導(dǎo)致運行時錯誤。當組件通過其公共API以外的任何其他方式訪問時涧卵,我們還可能觸發(fā)運行時錯誤或測試失敗勤家。

我們還想 通過刪除意外和循環(huán)依賴關(guān)系來解開域依賴關(guān)系圖。實現(xiàn)完全隔離是一項持續(xù)的任務(wù)柳恐,但是Shopify的所有開發(fā)人員都在投資却紧,我們已經(jīng)看到了一些預(yù)期的好處。例如胎撤,我們有一個傳統(tǒng)的稅務(wù)引擎晓殊,不再滿足我們商家的需求。在本文所述的努力之前伤提,將舊系統(tǒng)更換為新系統(tǒng)幾乎是不可能完成的任務(wù)巫俺。但是,由于我們已經(jīng)投入了大量精力來隔離依賴關(guān)系肿男,我們能夠?qū)⑽覀兊亩悇?wù)引擎換成一個全新的稅收計算系統(tǒng)介汹。

總之却嗡,在系統(tǒng)早期,沒有任何架構(gòu)通常是最好的架構(gòu)嘹承。這并不是說不實施良好的軟件實踐窗价,而是花費數(shù)周和數(shù)月的時間來嘗試構(gòu)建一個您還不知道的復(fù)雜系統(tǒng)。

Martin Fowler的Design Stamina Hypothesis 通過解釋在大多數(shù)應(yīng)用程序的早期階段叹卷,您可以實施比較少的事先設(shè)計撼港。將設(shè)計質(zhì)量與上市時間進行權(quán)衡是切合實際的。一旦您可以添加特性和功能的速度開始減慢骤竹,那就是投資良好設(shè)計的時候了帝牡。

重構(gòu)和重新構(gòu)建的最佳時間是盡可能晚,因為您在構(gòu)建時不斷了解有關(guān)系統(tǒng)和業(yè)務(wù)領(lǐng)域的知識蒙揣。在擁有領(lǐng)域?qū)I(yè)知識之前設(shè)計一個復(fù)雜的微服務(wù)系統(tǒng)是一個冒險的舉措靶溜,太多的軟件項目都會陷入其中。根據(jù)Martin Fowler的說法懒震,“幾乎所有我聽說過從頭開始構(gòu)建為微服務(wù)系統(tǒng)的系統(tǒng)罩息,它已經(jīng)結(jié)束了嚴重的麻煩......你不應(yīng)該開始一個帶微服務(wù)的新項目,即使你'確保你的應(yīng)用程序足夠大个扰,以使其值得“瓷炮。良好的軟件架構(gòu)是一項不斷發(fā)展的任務(wù),適合您應(yīng)用的正確解決方案絕對取決于您的運營規(guī)模锨匆。隨著應(yīng)用程序復(fù)雜性的增加崭别,Monolith冬筒,模塊化整體結(jié)構(gòu)和面向服務(wù)的體系結(jié)構(gòu)將逐漸演化恐锣。每個架構(gòu)都適用于不同規(guī)模的團隊/應(yīng)用程序,并將被痛苦和痛苦的時期分開舞痰。當你開始體驗本文中強調(diào)的許多痛點時土榴,那就是當你知道你已經(jīng)超越當前的解決方案時,是時候進入下一個了响牛。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末玷禽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子呀打,更是在濱河造成了極大的恐慌矢赁,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贬丛,死亡現(xiàn)場離奇詭異撩银,居然都是意外死亡,警方通過查閱死者的電腦和手機豺憔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門额获,熙熙樓的掌柜王于貴愁眉苦臉地迎上來够庙,“玉大人,你說我怎么就攤上這事抄邀≡耪#” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵境肾,是天一觀的道長剔难。 經(jīng)常有香客問我,道長准夷,這世上最難降的妖魔是什么钥飞? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮衫嵌,結(jié)果婚禮上读宙,老公的妹妹穿的比我還像新娘。我一直安慰自己楔绞,他們只是感情好结闸,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酒朵,像睡著了一般桦锄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蔫耽,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天结耀,我揣著相機與錄音,去河邊找鬼匙铡。 笑死图甜,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的鳖眼。 我是一名探鬼主播黑毅,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钦讳!你這毒婦竟也來了矿瘦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤愿卒,失蹤者是張志新(化名)和其女友劉穎缚去,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琼开,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡易结,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衬衬。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡买猖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滋尉,到底是詐尸還是另有隱情玉控,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布狮惜,位于F島的核電站高诺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碾篡。R本人自食惡果不足惜虱而,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望开泽。 院中可真熱鬧牡拇,春花似錦、人聲如沸穆律。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽峦耘。三九已至剔蹋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辅髓,已是汗流浹背泣崩。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留洛口,地道東北人矫付。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像绍弟,于是被迫代替她去往敵國和親技即。 傳聞我的和親對象是個殘疾皇子著洼,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361

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