Shopify 高級(jí)工程師 Kirsten Westeinde 在 Shopify Unite 2019 大會(huì)上討論了 Shopify 向模塊化單體架構(gòu)的演變。這包括使用設(shè)計(jì)收益線( design payoff line )來(lái)決定何時(shí)進(jìn)行此更改,如何實(shí)現(xiàn)更改,以及為什么將微服務(wù)排除在目標(biāo)架構(gòu)之外。
一個(gè)關(guān)鍵的結(jié)論是,單體不一定是一個(gè)糟糕的架構(gòu),它具有許多優(yōu)點(diǎn),比如單個(gè)測(cè)試和部署管道。在項(xiàng)目開(kāi)始時(shí),當(dāng)必須快速交付新特性時(shí),這一點(diǎn)特別有用。只有在跨越了“設(shè)計(jì)收益線”時(shí),也就是糟糕的設(shè)計(jì)阻礙特性開(kāi)發(fā)的那一點(diǎn),才應(yīng)該開(kāi)始改進(jìn)架構(gòu)。就 Shopify 而言,改進(jìn)其架構(gòu)并不意味著轉(zhuǎn)向微服務(wù),而是轉(zhuǎn)向模塊化單體架構(gòu)。這結(jié)合了單體(例如單個(gè)測(cè)試和部署管道)和微服務(wù)(例如代碼模塊化和解耦)的優(yōu)點(diǎn)。
Westeinde 認(rèn)為,單體架構(gòu)是一個(gè)很好的項(xiàng)目起點(diǎn),他說(shuō):“其實(shí),我建議新產(chǎn)品和新公司開(kāi)始時(shí)使用單體架構(gòu)?!彼信e了其中的一些優(yōu)點(diǎn):
一個(gè)項(xiàng)目包含所有代碼;
只有一個(gè)代碼庫(kù),測(cè)試和部署都很簡(jiǎn)單;
所有數(shù)據(jù)都可用,無(wú)需跨服務(wù)傳遞;
一組基礎(chǔ)設(shè)施。
由于這些優(yōu)點(diǎn),Shopify 一開(kāi)始只是一個(gè)小型的 Ruby on Rails 單體,隨著時(shí)間的推移,逐漸發(fā)展成為一個(gè)非常大的代碼庫(kù)。當(dāng)這種情況發(fā)生時(shí),它意味著 Shopify 開(kāi)始變得不可維護(hù),并且因此很難交付新特性。例如,更改一段代碼會(huì)對(duì)看似無(wú)關(guān)的代碼造成意想不到的副作用,并且構(gòu)建和測(cè)試應(yīng)用程序花費(fèi)的時(shí)間太長(zhǎng)。
Westeinde 援引 Martin Fowler 的設(shè)計(jì)耐力假說(shuō)( design stamina hypothesis )解釋說(shuō),是時(shí)候重構(gòu)他們的架構(gòu)了——一旦功能開(kāi)發(fā)被糟糕的設(shè)計(jì)所阻礙,設(shè)計(jì)收益線就會(huì)被跨越,這意味著投入資源來(lái)修復(fù)它是有意義的。
最初,Shopify 將微服務(wù)視為一種可選的、更易于維護(hù)的架構(gòu)。然而,由于分布式系統(tǒng)的復(fù)雜性,它被排除在外,取而代之的是更易于維護(hù)的單體架構(gòu):
我們意識(shí)到,我們喜歡單體所有的東西,代碼都在一個(gè)地方,而且向一個(gè)地方部署。我們遇到的所有問(wèn)題都是由代碼中不同功能之間缺乏界限所直接導(dǎo)致的。
Westeinde 解釋說(shuō),他們意識(shí)到他們的設(shè)計(jì)目標(biāo)是提升系統(tǒng)的模塊化,比如使用微服務(wù),同時(shí)保持一個(gè)單一的可部署單元,像一個(gè)單體。為了實(shí)現(xiàn)這一點(diǎn),Shopify 采用了模塊化的單體模式。這使得代碼之間有了邊界,但要使代碼都在同一個(gè)位置并且部署到同一個(gè)位置。遷移路徑如下:
代碼重組:最初,代碼的組織方式類似于典型的 Rails 應(yīng)用程序,頂層部件以技術(shù)組件命名,如控制器。這被更改為基于業(yè)務(wù)功能進(jìn)行組織,如“賬單”和“訂單”,從而更容易定位代碼。
隔離依賴關(guān)系:每個(gè)業(yè)務(wù)組件彼此隔離,然后通過(guò)公共 API 供外部使用。他們內(nèi)部開(kāi)發(fā)了一個(gè)名為 Wedge 的工具,它跟蹤每個(gè)組件的隔離情況。它構(gòu)建一個(gè)調(diào)用圖,然后計(jì)算出哪些調(diào)用(比如跨組件的調(diào)用)違反了規(guī)則。
強(qiáng)制邊界:一旦每個(gè)組件都實(shí)現(xiàn)了 100% 的隔離,它們之間就有了強(qiáng)制的邊界。其思想是,當(dāng)代碼試圖訪問(wèn)它沒(méi)有顯式依賴的組件的代碼時(shí),就會(huì)出現(xiàn)運(yùn)行時(shí)錯(cuò)誤。以這種方式聲明依賴關(guān)系也將使它們?cè)谝蕾囮P(guān)系圖中可視化。
最后,Westeinde 解釋說(shuō),這個(gè)例子很好地說(shuō)明了架構(gòu)如何根據(jù)業(yè)務(wù)需求發(fā)展:
良好的軟件架構(gòu)是一項(xiàng)不斷演化的任務(wù),而應(yīng)用程序的恰當(dāng)解決方案完全取決于你的操作規(guī)模。