微服務是個迷人的東西,易於開發、理解、維護,可以挑選適合技術、獨立部署、易於擴充等,若曾被單體(Monolithic)架構應用程式「蠱毒」過,面對各種對微服務優點的歌頌,應該會心癢難耐。

只是,我們需要哪些微服務呢?開展一個全新架構?這本質上就違反微服務的精神!

大爆炸或重構

單個微服務也許是簡單的,然而為了串起多個微服務,微服務架構充滿許多名詞,組態伺服、服務註冊與發現、負載平衡、斷路器、路由閘道等,單個微服務宣稱的易於開發、理解、維護等優點,依賴在高門檻的基礎設施與架構策略之上,光是要理解就不容易,進一步地,還需考量如何適當安插在應用程式的哪個環節,而且,真的需要用上全部的東西嗎?

如果打算採納微服務的動機,是來自於對單體應用程式的想法,並且想要從無到有建立各個服務,將之組合起來,因為沒有歷史包袱,這應該比較簡單吧?

然而,事先規畫的服務真的有用嗎?值得獨立出來?會不會實際上只有單個服務與之溝通,也從沒有過彈性擴充的需求呢?大爆炸式的(Big Bang)重寫成微服務架構,就像Martin Fowler說的:能保證的就是「大爆炸」!

從另一個角度來看,如果真能做出個全新架構來取代單體應用程式,單體應用程式本身在架構上,應該是沒有什麼大問題,微服務架構中確實充滿著許多基礎設施及策略考量,不過服務之間的關係在規畫上,與開發上耳熟能詳的Program to interface、模組化等概念,實質上是類似的,既然單體應用程式本身在架構上沒有太大問題,從中抽取服務的成本,應該會低於重寫應用程式的成本,那又何必重寫呢?

想要大爆炸式地以微服務架構重寫應用程式,以擺脫單體架構應用程式中元件、模組間的耦合等歷史包袱,只會令開發者更疲於奔命而不切實際,以重構方式逐步清理,逐步抽取服務,讓應用程式隨著時間推移,逐漸瓦解為多個微服務,或許是更為務實的做法!

重構的策略

重構的策略之一是重構單體應用程式,使之更遵循MVC架構的分層概念,讓呈現層、商務層與儲存層,彼此之間都有更清晰的界線,更具體地說,就是運用介面(物件之間的協定,而不是Java中的interface)溝通、隔離變化,若架構中各層之間有著清晰界線,就會出現可抽換的模組,一塊一塊的模組可能就暗示著,它們是可抽取的服務,在進一步抽取時,遇到的阻力會少一點。

讓商務層與儲存層間有著清楚的界線,有助於看清有這些商務邏輯,以及各自需要的儲存邏輯,也就有助於資料庫上的分離,讓個別模組能夠擁有個別的資料儲存,因為在抽取成服務之後,個別服務會擁有自己的資料庫,也因為如此,個別服務可以選擇更適合的資料儲存方案。

單體架構應用程式中,在資料儲存上,往往錯綜複雜,例如,在應用關聯式資料庫時,若表格之間有緊密相關的操作,那麼,這些表格可能屬於同一個模組;若表格數量眾多,而且有著複雜的操作關係,那麼,資料庫表格之間也需重構,否則的話,表示單一模組過於巨大,承載了過多的職責,未來,就算抽取成服務,可能也是個「巨」服務,而不是「微」服務。

在單體應用程式重構、抽取服務期間,應用程式可能會需要滿足新的需求,這時,不應該直接在單體應用程式中繼續加入程式碼,以避免單體應用程式繼續肥大下去。

在〈Refactoring a Monolith into Microservices〉(https://goo.gl/u1Et1m)中,也談到了重構單體應用程式的幾個策略,其中之一就,是停止挖掘(Stop Digging),試著將新需求實作為服務,透過路由,將新需求的請求轉發給新的服務,舊的需求轉發給單體應用程式,而單體應用程式與新服務之間透過膠合程式碼來溝通。

逐步抽取服務

單體應用程式中可能有許多的模組,然而不必在一開始就全部都抽取成為服務,如果單體應用程式中的模組有著清晰的界線,而且多個應用程式存在著重用該模組的需求,才來考慮將該模組抽取為服務,這可以作為微服務粒度考量的依據之一,避免服務過大或過小,如果未來其他服務對於該服務中某模組有著重用的需求,該服務可以進一步將該模組抽取成為服務。

在抽取出多個服務之後,不同的服務之間,也許會共用某些組態資訊。例如,多個服務都需要郵件服務,而郵件服務的組態資訊相同,在多個服務間直接複製組態是很簡單的一件事,然而,這就跟複製程式碼一樣,未來若組態變更,就需要記得逐一更新應用程式的組態設定。像是《Spring Microservices in Action》第三章就談到,組態管理的重要性經常被忽略,作者甚至面對過12,000個組態檔案的遷移難題。

在抽取服務之後,就要開始重視組態管理問題,跟程式碼管理類似,組態需要視需求而重構,決定採用哪種組態方式。

更進一步地,也會有版本控制之類的需求,衍生出專用的組態供應者,以及對應的組態消費者。而當組態的供應與消費,需要透過網路來供給與消費時,組態供應者就會以伺服器的形式實現,也就需要決定組態交換時的協定、格式等,以便組態消費者進行組態的取得、更新等。

在抽取服務的過程中,單體應用程式與服務,服務與服務之間,透過API介面溝通,不見得要馬上採用任何微服務基礎設施方案,在既有技術方案下逐步進行服務抽取,令單體應用程式原本的客戶端不察覺功能上的變化(符合重構基本精神)。

而這些步驟的進行,可能相當緩慢而踏實,在〈StranglerApplication〉(https://goo.gl/cCuxwL),Martin Fowler用絞殺藤蔓逐步剝奪被寄生樹生存空間,來比喻大規模重構應用程式的這個過程。

逐步套用基礎設施

是否套用微服務基礎設施,也是依需求而定,也許既有的技術在抽取出服務之後,就足以應付需求,那麼,就不見得要用上任何基礎設施方案,畢竟,每增加一個方案,就是增加一層技術上的難度,維護的門檻也會隨之上升,更何況,問題並不只有在採用哪個技術方案,更多是在於採用哪個架構策略。

如果需要彈性地擴充,試著採用服務註冊與發現;在需要考量服務狀態不佳、避免服務連鎖性癱瘓下,再加上斷路器的方案;在客戶端多樣化,想要避免曝露個別服務細節、以橫切關切點方式,對某些客戶端加以控管、限制API開放與否等時,再來設置閘道路由等策略。

值得一提的是,〈Netflix成為第一間完全上雲的大型企業〉(https://goo.gl/yywVFV)的經驗值得借鏡,其中,對於速度、規模,以及策略三個階段,也有著分層式架構等類似考量。總而言之,對於微服務的架構,不要一開始就想要用上全部的基礎設施或策略,重構會是更務實的做法!

專欄作者

熱門新聞

Advertisement