【譯】從PHP(Laravel)遷移到Go
今年早些時(shí)候,我做出了一個(gè)糟糕的商業(yè)決定,我決定用Go重寫Laravel應(yīng)用Boxzilla,雖然沒(méi)有遺憾,最后效果驚人。

????僅僅幾個(gè)星期后我就部署了Go應(yīng)用程序。構(gòu)建它是我?guī)讉€(gè)月來(lái)最有趣的事情,我學(xué)到了很多東西,最終結(jié)果是對(duì)舊應(yīng)用程序的巨大改進(jìn)、更好的性能、更容易的部署和更高的測(cè)試覆蓋率。
????該應(yīng)用程序是一個(gè)相當(dāng)簡(jiǎn)單的數(shù)據(jù)庫(kù)驅(qū)動(dòng)的API和包含帳戶管理等應(yīng)用程序,用戶可以在登錄以后下載產(chǎn)品,查看發(fā)票或更新付款方式。
??? Stripe和Braintree用于接受訂閱付款。發(fā)票使用MoneyBird處理,一些事務(wù)性的電子郵件使用Mailgun發(fā)送。
????雖然Laravel在這方面做得足夠好,但有些事情總是讓我感到過(guò)于復(fù)雜。每隔幾個(gè)月發(fā)布一個(gè)新的“主要”版本,如果新的版本包含重大改進(jìn),我心里也會(huì)覺(jué)得這沒(méi)什么,但是很多時(shí)候,感覺(jué)都是一些非常簡(jiǎn)單的改動(dòng),例如命名和目錄結(jié)構(gòu)進(jìn)行了更改。
Why Go?
? ? 去年,我已經(jīng)將幾種服務(wù)轉(zhuǎn)移到了Go上,所以我對(duì)這種語(yǔ)言并不是完全陌生。作為銷售基于WordPress的產(chǎn)品的開發(fā)人員,我的一部分工作是在一個(gè)古老的技術(shù)堆棧中工作,該堆棧主要關(guān)注最終用戶。
如果我不是自雇人士,我會(huì)簡(jiǎn)單地申請(qǐng)一份新工作來(lái)彌補(bǔ)這種缺乏性感技術(shù)的現(xiàn)象。作為我自己的老板,我有責(zé)任使自己的日常工作變得有趣,而不僅僅是追逐更多的即時(shí)資金。如果收入允許(并且確實(shí)如此),為什么不找點(diǎn)樂(lè)子呢?
編寫Go代碼很高興,工具很棒,不僅開發(fā)速度快,而且最終結(jié)果通常也很快。剛讀完Go項(xiàng)目的目的(https://golang.org/doc/faq#What_is_the_purpose_of_the_project),就使我對(duì)語(yǔ)言產(chǎn)生了濃厚的興趣。
我認(rèn)為我們會(huì)在未來(lái)幾年內(nèi)看到大量人從動(dòng)態(tài)類型的語(yǔ)言(如PHP,Python和JavaScript)切換到Go。
移植代碼庫(kù)
將代碼遷移到Go中主要包括正確地進(jìn)行數(shù)據(jù)庫(kù)交互以及將Blade模板移植到我們可以在Go中使用的東西。
ORM總是會(huì)阻礙我的一件事,因此我選擇了可模擬的數(shù)據(jù)訪問(wèn)層和簡(jiǎn)單的SQL查詢。使用Meddler擺脫了一些煩惱:將查詢結(jié)果映射到結(jié)構(gòu)體中。
為了支持分層模板和局部,我開源了grender(https://github.com/dannyvankooten/grender),這是基于Go的標(biāo)準(zhǔn)庫(kù)html / template包的一個(gè)小包裝。這使我可以相對(duì)輕松地將Blade模板文件移植到Go中,因?yàn)槲铱梢允褂孟嗤膶哟谓Y(jié)構(gòu)和部分模板。
為了與Stripe集成,有官方的Stripe?-Go(https://github.com/stripe/stripe-go)軟件包。對(duì)于Braintree,有一個(gè)非正式的braintree-go(https://github.com/lionelbarrow/braintree-go)軟件包,該軟件包被忽略了一會(huì)兒,但最近又受到了新的關(guān)注。由于到目前為止,在Moneybird中還沒(méi)有Go軟件包來(lái)管理發(fā)票,因此我構(gòu)建并開源了moneybird-go(https://github.com/dannyvankooten/moneybird-go)。
對(duì)比分析
由于Go是一種編譯語(yǔ)言,具有比PHP更好的標(biāo)準(zhǔn)庫(kù),因此像我將要比較的那樣比較這兩種語(yǔ)言并不是很公平。就是說(shuō),我認(rèn)為分享一些數(shù)字會(huì)很有趣。
性能
wrk用于為兩個(gè)返回登錄頁(yè)面HTML的應(yīng)用程序執(zhí)行一些簡(jiǎn)單的HTTP基準(zhǔn)測(cè)試。
| ? | 并發(fā) | 平均?潛伏 | 要求/秒 | 傳輸/秒 |
|---|---|---|---|---|
| Laravel | 1 | 3.87ms | 261.48 | 1.27MB |
| Laravel | 100 | 108.86ms | 917.27 | 6.04MB |
| Go | 1 | 325.72μs | 7365.48 | 34.27MB |
| Go | 100 | 11.63ms | 19967.31 | 92.91MB |
| Go | 200 | 37.68ms | 22653.22 | 105.41MB |
不幸的是,一旦我將并發(fā)“用戶”的數(shù)量增加到100以上,Laravel應(yīng)用程序(或PHP-FPM套接字)就一直崩潰。
NetData提供了以下圖表,以查看服務(wù)器在所有這些負(fù)載下的承受能力。
100個(gè)并發(fā)連接的Go程序

100個(gè)并發(fā)連接的Laravel?程序

請(qǐng)注意,我在運(yùn)行應(yīng)用程序的同一臺(tái)計(jì)算機(jī)上運(yùn)行了基準(zhǔn)測(cè)試,因此這會(huì)嚴(yán)重影響兩個(gè)圖表。
代碼行數(shù)
讓我們比較兩個(gè)應(yīng)用程序中的代碼行,包括所有的依賴項(xiàng)。
$ find . -name '*.php' | xargs wc -l
156289 total
Laravel版本包含超過(guò)156.000行代碼。這還不包括要運(yùn)行Laravel測(cè)試等相關(guān)的開發(fā)依賴包。
$ find . -name '*.go' | xargs wc -l
33624 total
另一方面,Go版本包含33.000行代碼。這是完全相同功能的代碼的五分之一。
讓我們?cè)贚aravel應(yīng)用程序中排除外部依賴關(guān)系,以便知道我實(shí)際寫了多少行php。
$ find . -name '*.php' -not -path "./vendor/*" | xargs wc -l
13921 total
多少行Go
$ find . -name '*.go' -not -path "./vendor/*" | xargs wc -l
6750 total
即使僅查看托管代碼行,結(jié)果也要稍微多一些。盡管如此,它還是只使用了原來(lái)完全相同的應(yīng)用程序的一半代碼。
測(cè)試范圍
測(cè)試是Go中的頭等公民,測(cè)試文件緊鄰實(shí)際源文件存在。
license.go
license_test.go
subscription.go
subscription_test.go
這使得應(yīng)用測(cè)試驅(qū)動(dòng)的開發(fā)變得異常方便。
在我們的Laravel應(yīng)用程序中,我們主要進(jìn)行了集成測(cè)試,以檢查請(qǐng)求處理程序是否返回正確的響應(yīng)。總體測(cè)試覆蓋率很低,主要是由于緊密耦合,而這又主要是我的錯(cuò)。再次編寫相同的應(yīng)用程序確實(shí)也有幫助。
TLDR
做了一些您不應(yīng)該做的事情:用另一種語(yǔ)言重寫應(yīng)用程序,因?yàn)槲腋杏X(jué)很喜歡。獲得了很多樂(lè)趣,并且得到了更小,更快的應(yīng)用程序。
