從 Rails developer 的角度認識 Golang,和新技術成為好友


最近 Codementor 開始試著逐步建立適合我們自己的 data pipeline。 一路上就會需要建立不少新的 service。 一直以來,Rails 都是我們 backend 的預設選項,主要是因為我們團隊的成員對不論是 ruby, rails 或相關的社群都有一定的掌握度。

但同時我們也希望在各種不同的層面試試新的東西, 從新的工具、framework、到新的語言等等。 於是和 Golang 的邂逅就這麼開始惹!

在這篇文章裡面,我想紀錄的東西主要是我們嘗試一個對我們來說全新的程式語言的過程,當中遇到的問題和歸納出來的小結論。

寫在前面:

在這篇文章裡面,想要討論的主題是

如果要做一個 web service 的話,用 Ruby + Rails 和 Golang + 任意的 library 的差別“。

理論上 framework (Rails) 和語言(Golang) 當然是不能拿來比較的。所以下面的文章如果有 Ruby 和 Rails 混用的話,其實並不是內個意思。

另外,這篇文章也不是要戰哪個語言比較好。因為我們都知道,在技術的擇擇這件事情上面,永遠只有適合或不適合,而沒有絕對的好壞。 對於 Golang 這個語言,其實我們的了解還相當的粗淺,所以自然也無法做出技術上很深入的比較。

但對於任何一個團隊,在選用新技術的時候要怎麼在對新技術的了解和時間人力有限的狀況下做出選擇, 這是這篇文章想要分享的主題。

背景介紹

一直以來我們都是以 Rails 做為主要的 backend 開發工具。 後端團隊的成員對 Ruby, Ruby on Rails 基本上有一定的熟悉度,另外 javascript 也是略懂。 如果說要有一個比較的標準的話, 大概就是:

國語 > Ruby > JavaScript > 英文

如果說為什麼想要嘗試新的東西的話,倒也不是 Rails 有什麼不好。 主要還是想說要保持學新東西的衝勁,二來也希望藉此有新的思考方式。

以 Golang 來說,在這之前我們對它的了解就是:

  • 好像 performance 很不錯
  • 是 static type 的語言,好像可以帶來新的思維
  • 看來社群也還算發達

於是我們就找了一個還算單純的 project 來試看看。 基本上我們預期的成果是做出一個 data gateway,要可以:

  • 解開我們 auth service 發的 JWT
  • 然後和 Google BigQuery API 串接
  • 找到某種 background job 的 solution, 來避免 throughput peak 把 server 衝爆

實驗的策略

對我們來說,這件事情的本身是要試試看一個新的語言。 但同時我們也希望實驗的情境是一個我們實際要用的東西, 因為如果不是真實的情境的話,很難體會用這樣一個東西,在 production 的步調下的感覺。

而在技術上,我們的策略則是把未知的東西盡量縮小。 也就是說,除了 application layer 之外,在 infrastructure 這個層面盡量都用我們熟悉的工具。 這個作法有時候是會要犧牲掉一些架構的完整度的。

例如說,我們在第一個階段,選擇把 server host 在 Heroku 上面。 單以技術上的考量,以一個 data gateway 來說這可能不是一個長遠的選項。 但一來我們目前的 scale 並不大,再者這樣的選擇可以讓我們專注在”試新語言”這件事情上面。 而如果我們實驗的結果是好的,那之後再來把 heroku 換掉也不遲。 更長遠來說,data gateway 甚至不會用一個 web server 來直接接 request 這樣的作法, 但只要我們有在 monitor 這件事情,到它快要撐不住的時候再換應該也ok。

Meet Golang as a Rails Developer

在開始之前,我對於要用 Golang 做出這樣的 service 的想像是:要做哪些事情我應該大致上都知道惹。 所以理論上只要找到相對應的事情要怎麼在 golang 上實作出來應該就沒問題了吧!

但實際上好像不是這樣 XD

首先,我走完了 A Tour of Go 並且看了 The Go Programming Language 這本書。

然後就開始了 Rails 與 Golang 的衝突之旅 😂

下面大概是一些”如果你從一個 Rails dveloper 的角度出發,你可能要先習慣的事情”們:

在 Golang 的世界裡,有 Rails 的對應 framework 嗎?

結果好像沒有 QQ

Golang 的 std library 是相對完整的,所以不一定要用 framework 也可以做出 production 的 web service。 也因此,Golang 沒有 Rails 的對應。 但 Golang 確實是有一些 web framework 的,從 lightweight 到功能很完整的都有。 但整體來說在 web development 這個領域,社群、文件的完整度還是略遜於 Rails。 有可能是因為有一定比例的 golang developer 其實根本不用任何 framework 的緣故。

Static Type and Testing

其實在接觸到 Golang 之前,大部份認真寫的語言都是動態型別的。 雖然說之前就有預期到,在 static type 的語言下,要設計程式的思維會有所不同(這也是我們想要嘗試 Golang 的原因之一), 但實際上寫起來的不適應還是比我預期的多一點。

而連帶的,在測試的時候的彈性也會受到限制。 在 Ruby 的環境下,在寫測試的時候可以在動態的時候爽改 dependency 的行為來做到 stub/mock。 但在 Golang 下,因為是靜態型別的關係,所以要從程式的設計上去著手。

基本上我認為這不是好或壞的差別,而是兩個語言本身的特性就不同。

Document

文件的完整度和可讀性,我覺得是讓我感受比較深的地方。 以 Background Job 來說,在用 ruby base 的 library (sidekiq) 的時候都是可以看 guide 就咻咻咻設定好, 但用 golang 的 library (Machinery) 則是常常照著 doc 走就不 work, 最後要進去看 source code

這點其實是很有可能隨著時間而改變。 因為當社群壯大之後,很多事情都會改善。 只不過以目前的感覺現況大概是這樣子沒錯。

開發環境和 Dependency

Golang 的開發環境和 Ruby 很不一樣。 在寫 ruby 的時候,你基本上可以把你的檔案任意的擺放,但在 Golang 下面則是有一些限制。 先讀 How To Write Go Code 會很有幫助。

而 dependency control 的話,要記得選一個 dependency control 的工具來用。

大決擇的時刻

在經過大約一個星期的奮鬥之後,我們用 Golang 做出了一個可以使用的版本。 這時候開始認真想這個新的語言究竟適不適合我們的使用情境。 首先,在評估之前,我們希望先 normalize 學習的成本和一開始環境的建立成本。 我們希望評估的情境是:

假設我們已經很熟悉這個語言了,並且相對應的開發環境也已經建立好。 也就是說,扣除一次性的成本,如果我們現在要開發一個新的功能,那用兩種 stack 的優劣各是如何呢?

下面大概是我們用來比較的一些維度:

Runtime performance: Golang > Ruby

這點應該是蠻客觀沒有什麼好說的。

但以我們的使用情境,大多數的時候 performance 的 bottleneck 會是在 database 的 IO, 比較少是卡在語言 runtime 的計算。 所以在這點方面,Golang 的 runtime performance 對我們來說可以帶來的好處比較有限一點。

程式的簡潔度(寫起來的爽度): Ruby > Golang

Golang 是一個靜態語言,所以程式寫起來比較囉嗦是無法避免的。 而 Ruby 的話,個人認為這是一個有把”寫code時候的爽度” 考慮進去設計的語言,所以寫起來的簡潔程度是有明顯的勝出的。 雖然說比較寫起來的爽度聽起來好像有點中二,但是我們認為它其實有更重要的意義。

對我們來說,我們要用程式 model 的東西比較少很精準的數值計算, 反而是在商業邏輯方面會相當的複雜。

這種時候,如果程式寫起來太繁瑣的話,其實沒有辦法把重要的商業邏輯突顯出來。 這對我們來說是很傷的一件事情,也是一開始沒有考慮到的一個點。

Concurrency Control: Golang > Ruby

在 Ruby 裡面要控制 concurrency 的東西大多數是很痛苦的。 Golang 在這點則是有 go routine。(但說來慚愧,這次我們並沒有真正用 go routine 做什麼事情,所以上面的比較是從 google 得到的結論)

回到我們的使用情境,在真正用 Rails 寫程式的時候,其實也很少另外開 thread 來用。(webserver 這層就交給 puma 來處理了) 再者,又因為我們大多數的API都要和 database 溝通, 即使在 application 這層我們開了很多 concurrent (不管是 thread 或者是 go routine, 並且有好好控制它…),concurrency 的限制還是會出現在 db connection 的這關。

所以以我們目前預想的情境來說,幫助好像也不太大。

Web 相關的 community/tool/document: Ruby > Golang

在 Ruby/Rails 的社群裡,和 web 相關的大多數 utility 都有現成的 library 可以用。 在 Golang 的環境下目前這點還是略遜一籌。 當然,現成的 library 不一定好用,真正要用的時候還是要花時間去選, 但以我們的狀況來說,這讓我們有比較高的機會可以專注在我們自己獨有有商業邏輯上面(因為我們在技術上比較沒有什麼特殊需求 XD), 而不用花太多資源去建立一些比較底層的工具。

通用性: Golang > Ruby

Golang 可以把東西 compile 出來就丟上 server,但 Ruby 寫出來的東西則是要機器上有 ruby runtime 才可以跑。 再者,以語言的特性來說,Golang 可以比較容易的控制到較底層的東西。 (但實際上更精確的比較,可能就要請更熟悉 Golang 的大大們來說明惹)

結論

好,所以最後我們就把 Golang 的版本 archive 起來,然後用 Rails 重做了一個 😎

學到的功課們

這次雖然說最後還是沒有選用新的武器,但一方面來說,我們對 Golang 這個語言有了比以前更多的認識, 再者,也希望可以從這次的經驗慢慢建立”嘗試新技術”的流程。

定義嘗試的目標

在實際上嘗試之前,可以先有某種預期的目標。好比說是”希望 performance” 可以變好,或者是”開發的速度變快”。即使只是”好像大家都說好,想要試看看會發生什麼事”也可以。重點是如果先有明確的目標,在實驗結束之後,要決定要不要採用新武器的時候會比較有個依據。

做一些功課,但也不用完全相信

對於要解決的問題,要先做多一點功課。可能有人已經踩過同樣的雷了。 做功課的目的比較偏向對於在實際上做下去之後,會遇到什麼困難先有個心裡準備。

但對於別人的感想,卻也不用到全盤接受的程度。 因為每個人、每個團隊的狀況都不同,還是要做出理性的判斷,最後還是要試了才知道。 有些討論的命題其實並沒有辦法得到一個非黑即白的答案。例如說:

Golang 和 Rails 哪一個比較適合拿來做 Restful API“。

這個問題不論說”是”或者是”不是”都不會是完整的答案。因為 Restfule API 包含了各種情境,適合或不適合又要怎麼定義才算數呢? 這時候要深入去看在哪一種情境下,用什麼樣的標準來判斷適不適合,然後再回頭看看這一切和自己遇到的狀況是不是吻合。

找到一個 project 來嘗試,並且定義 scope 和停損點

用來試新技術的 project 可能是一個全新的 service,也可能是既有 code base 下面的一個功能。 如果是後者的話,可能會需要額外的架構設計。

接著要定義 scope。基本上就是要決定 “要做到什麼程度才可以決定要不要用新技術”。 通常這個 scope 會和上面提到的嘗試的目標有關。好比說我們希望可以解決的是 performance 的問題, 那就要做到某個可以推上線的程度,或者某個可以讓我們真的試 performance 的程度。 而以我們這次的例子,我們希望嘗試的目標是開發的感覺,所以就試著把一個功能從頭到尾做完。

但是也因為是不熟悉的技術,總是會有時程無法預計的時候。 這時候定義一下停損點也是一個好主意。例如說 “如果兩個星期都做不完的話,那我們就要再認真考慮這個選項”。

根據自己團隊的特性定義出比較的維度

當一個 project 做完之後,要用什麼為標準決定要不要真的使用呢? 這時候可以定義一些評估的維度,好比說 “performance”,”程式的簡潔度” 等等。 這些維度有可能是一些常常拿來比較的項目(好比說 performance),也可能是團隊在乎的東西(好比說程式的簡潔度)。 而依照各個團隊特性的不同,每個維度在比較的時候會有不同的比重。

在評估的時候,要把一次性的成本分開計算

其實在學習新的東西的時候,是會遇到一定的陣痛期的。尤其是越底層的東西越會。 好比說學習一個全新的語言,相比於學習用一個新的 library,前者的陣痛就會比較痛。 像這類的東西我們把它定義為”一次性的成本”。因為理論上你學會了,之後再繼續做的時候,這些成本就消失了。 同樣的東西還有像是建立一個 project 的 convention 等等。

在評估要不要採用新技術的時候,我覺得可以把這些一次性的成本分開計算。 避免因為正處於學習的陣痛期而誤認為這個新的技術不好用。

寫在後面

以這次的這個 case 來說,即使後來沒有使用 Golang,但對於這個語言又比以前多了一些些掌握。 覺得如果在各種需求符合的狀況下,它想必會是一個很強大的武器。 以後如果有更適合的情境就可以更精準的認出來。

寫程式這件事情對我們來說,是一個不斷前進的過程。 好像一直都有新的東西可以試看看。除了會帶來新的思維之外,學習本身就是很有趣的一件事情。

另一方面,大多數的時候我們是在一個資源有限的環境下寫程式的, 所以要學習新東西的時候也要把有限的資源考慮進去,希望可以做出精準的取捨。 不管用什麼方法,學新東西94爽阿阿