技術管理觀點
技術管理觀點
技術管理閒聊:與時俱進的架構選擇
0:00
-8:18

技術管理閒聊:與時俱進的架構選擇

在組織裡面作技術管理,不免俗的會碰到工程師的鄙視鏈1。最近剛好也在跟同事討論到這個議題,他認為公司應該要統一使用某個語言,這樣子學習的成本會比較低,大家可以互相支援。

The Programmer Hierarchy | MacRumors Forums
軟體工程師鄙視鏈

這個想法本身沒有問題,不過就如謝文憲憲哥的名言「人生沒有平衡只有取捨」一樣,是有一些取捨的邏輯在裡面的。舉例來說,公司統一使用 Java,因為生態圈完整,使用的人很多,也有公司提供各式各樣的解決方案,但 Java 在環境的設定以及開發編譯的速度上,就不如 PHP 或 Perl 這種 Scripting Language,而這兩種語言的生態圈也很完整,但因為是弱型態語言,寫出來的程式就比較容易有預想以外的問題2

因此,一個 Java 工程師鄙視一個 PHP 工程師(或相反),這是沒有意義的。他們兩個面對的問題可能不太一樣,語言特性上有的「坑」也不同,可以說是沒有可比性的。同樣要使用 AI 來解問題,在訓練模型的時候可能是用 Python,但實際提供服務的時候可能會改用 Golang 甚至 C/C++。如果堅持只用 Python,雖然大家可以互相支援,可是使用者服務上就多了 100ms 的反應時間而造成轉換率不佳,或是無法在硬體比較弱的 IoT 設備上使用。

同樣把這個邏輯套用到架構上,作 Service Mesh 是不是一定比 Micro Service 更好?或是絕對不要作單體架構?這件事應該不是單純的零或一的議題。我認為要決定架構需討論以下三個重點:產品生命週期、團隊規模與能力、容錯設計

產品生命週期

客戶買單的產品才是好產品。但所有的產品在還沒上市之前,沒有人知道有沒有客戶會決定買單(可能連神都不知道),這時軟體團隊的首要目標是:

儘快做出產品並給小規模的客戶使用,收集客戶的回饋並快速調整。

也就是最小可行產品(MVP)的概念。這時架構設計的原則應該是「重資料而輕邏輯」。

  • 重資料:資料結構需要可以彈性,欄位的定義不能太精確,以應對商業邏輯的調整與修改。舉例來說,對於一個「訂單」,規劃的欄位可能有寄送地址、訂單狀態、訂單內商品等(如下圖)。

    初期「訂單」的資料表

    其中商品可能有實體商品(需要寄送)與數位商品(直接 Email 寄送)等。如果一開始就把寄送地址設計為城市+郵遞區號+街道地址,對於虛擬商品來說就無法套用進去。因此可以把寄送地址的定義擴大(變得比較模糊),欄位裡面除了填寫住址以外,也可以填寫 Email。或是更進一步,將寄送地址合併到訂單商品的欄位內,用 JSON 的格式來儲存。
    這樣的壞處是,在大量寄送的時候,無法有效率的撈出同一地區的訂單來處理,但其實初期小規模客戶的時候,也不會有大量寄送的需求;等到需要大量寄送的時候,也可以另外用報表系統,非同步產生的方式來處理寄送的資料。
    不過要注意的是,該收集的資訊還是要盡量收集。例如以訂單為例,一開始就要紀錄訂單日與出貨日,這樣對於未來的營運分析(接收到訂單後多久出貨,有沒有改善的空間等)才有資料能夠幫助作決策。

  • 輕邏輯:因為這個階段的商業邏輯會大幅調整,因此架構上只要每個模組的責任清楚即可,不需要最佳化每個模組的邏輯。把責任理清楚是為了之後架構擴大的時候容易逐步改寫,也方便作單元測試。
    一開始的模組數量可能也不會很多,後面才會慢慢增加。

到了產品的中期,已經有固定的客戶,這個時候客製化會是一個常見的需求。這時除了開始最佳化模組邏輯以外,外掛元件 (Plugins) 的設計會是一個兼顧彈性與滿足客製化需求的方式。定義好核心元件與外掛之間的溝通模式後,外掛元件除了可以在內部軟體團隊進行開發外,也可以讓外包團隊來開發。

Google Chrome 的應用程式商店,讓客戶可以客製化自己的瀏覽器功能

團隊規模與能力

通常團隊規模是跟產品生命週期掛勾的。在初期的產品,團隊小,反應速度快,但缺點也很明顯:無法有足夠的軟體開發能量優化每個模組。因此若依照模組化的方式來切團隊,既無法提升模組開發的品質,公車指數3變得更糟,對於整個產品來說只有壞處沒有好處;因此初期的軟體,通常不會一開始就導入微服務這類的架構,而是會用單體架構的方式來開發。

等到產品成功有了客戶,開始需要最佳化的時候,讓專職的團隊負責開發與營運某個模組;最後為了更容易的水平擴充,把模組拆出來變成獨立的服務,變成服務導向架構 (SOA) 或更小的微服務,是一個比較合理的演進過程。

之前我自己的經驗是,軟體團隊規模在 20 人左右的時候,開始規劃專職的團隊負責,是一個比較好的作法。一個模組可能會規劃 4 到 5 個軟體工程師負責,而這個團隊有需要維護這個模組的所需能力,包含前後端、SRE、資料工程、AI等等。

另外模組的拆分,會需要有幾位架構師來協助各團隊。每個模組團隊有自己的目標,而這些目標集合起來,會是技術單位的目標。如何協調並保證團隊都在往目標的方向,並且有效率的溝通(無論是軟體上的溝通——API或是團隊間Task的溝通),架構師都是很重要的角色。他們有點像是團隊之間的膠水,負責讓團隊和團隊之間緊密結合,不要散掉。我自己的經驗是,架構師需要是團隊中比較資深,對整體全貌比較了解,喜歡溝通的工程師,會是比較適合的人選。

容錯設計

最後討論容錯這件事。模組和模組之間的資料傳遞,我認為原則是「檢查輸入值:預設傳入的資料都有可能不正確」以及「預設失敗:被呼叫的模組有可能不可用」這兩個最重要。檢查輸入值避免模組內的狀態被錯誤的呼叫者污染,而預設失敗讓模組設定要達到的目標不容易被其他模組影響。

模組內的資料傳遞可以不需要考慮這兩件事,以便降低複雜程度,但到了模組邊界(單體的 Public Function 或是服務的 API)的時候,這兩個原則是必要的。

而如何設定模組的邊界呢?我會使用 SOLID單一職責原則來設計。因此,當發生錯誤的時候,可以很快的定位出錯誤的位置。對於呼叫者來說,也能夠對這類型的錯誤作正確的處理,而不是永遠都回傳 Something went wrong 這類的狀況。

Rails: use Capistrano to deploy, how to check log when error happens? -  Stack Overflow
Something went wrong…

舉例來說,如果購物車模組呼叫會員模組,希望取得會員的折扣資訊,但呼叫失敗了,這時購物車模組可以選擇用更優惠的條件繼續讓會員作結帳,而不是直接中斷流程。或是當結帳模組呼叫風險控管模組失敗時,結帳模組可以將訂單放入一個「待處理 (Pending)」狀態,讓消費者知道訂單仍在處理中,但需要等到人工確認後才會生效。

重點是根據商業模式以及情境,來規劃哪些模組失效時,一定要讓流程中斷,而哪些是可以做到優雅降級 (Graceful Degradation) 的。

與時俱進的架構選擇

Premature optimization is the root of all evil.
過早最佳化是萬惡的根源

— Donald Knuth, 1974 recipient of the ACM Turing Award

軟體架構是為了要支撐商業模式,而不是主管或架構師炫耀技術的場合。因此無論是單體架構還是 Service Mesh,只要是適合當下商業模式的架構就是完美的。在資料結構中,預想未來的需求(因為這個通常很難改),但模組的部分可以先以簡單的單一職責原則設計就好,後續再與時俱進的調整。雖然單體聽起來很老派也不潮,但他能解決問題,讓產品在商業的路上往前推進,這才是技術帶來的最大價值。

2

例如在 PHP 裡面, ““ == 0 會是 true,如果在處理使用者輸入的時候沒注意到,邏輯上可能就會出錯。

3

Bus Factor

0 Comments
技術管理觀點
技術管理觀點
關於技術管理的觀點