从 PHP 转移到 Go 的这篇文章首先在这里发表,并在作者的许可下重新发布。

今年早些时候,我做出了可以说是糟糕的商业决定。 我决定在 Go 中重写 Boxzilla 的 Laravel 应用程序。
但我不后悔。

几个月后我开始部署 Go 应用程序。构建这个应用是这几个月中我认为最有趣的。我学到了很多,最终在旧应用的基础上进行了很多改进。(新应用)性能更佳,更易部署,有了更高的测试覆盖率。
该应用是一个非常直接的数据库驱动 API 和账户域,用户可以直接登录以下载产品,查看发票或更新付款方式。
Stripe 和 Braintree 用来接受订阅付款。 MoneyBird 用来处理发票, Mailgun 用来发送 一些交易电子邮件。
尽管 Laravel 在这一方面工作得很好,但有些东西对于我来说太复杂了。而且每月发布一个新的“主要版本”又是什么呢?如果每个新版本的发布都带来了一些有意义的改进,那倒喜闻乐见,但在我看来,它大多数时候带来的只是一些小型命名或文件结构的更改。
为什么用 Go?
去年,我已经把几项服务转移到了 Go,所以我并不是第一次接触这个语言。 作为销售 WordPress 产品的开发人员,我的一部分工作就是要在古老的技术堆栈中工作,它主要专注于终端用户。
如果我不是自由职业者,我将会申请一份新工作来弥补无法使用这项性感技术的遗憾。由于我是自己的老板,我要保证自己日常工作的乐趣,而不仅仅是追求更直接的金钱。如果收入允许(而且确实是), 为什么不开心点呢?
用Go写代码是件很快乐的事情,工具很棒,不仅开发速度快,而且最终的结果也通常贼快 。 推荐阅读一下这个Go项目的目的 来了解下该语言吧。
我想我们在接下来的几年时间里,会看到有大量的人从动态类型语言如PHP、Python以及JavaScript转向使用Go。
移植代码库
迁移的代码到Golang上主要包含正确与数据库交互,以及 移植Blade模板为我们可以在Go中使用的东西。
ORMs 总是最终给我带来麻烦,因此我使用一个模拟的数据访问层和普通的SQL查询来实现。Meddler 可以用来帮我避免使用那些可以将查询结果影射为结构体的样板。
为了支持分层模板和局部模板,我开源了grender,这是一个基于Go标准的html/template包的小型的包装器。它可以使得很轻易的就能将Blade模板文件移植到Go上,因为我可以使用同样的层次化的结构和局部模板。
想要能跟Stripe进行整合的话,有一份官方的stripe-go包可以使用。 而对于Braintree则有非官方的 braintree-go 包可用,它曾经有一小段时间被人忽视了,不过最近又重新获得了关注。由于还有没有Go的包来管理Moneybird里的invoices,我就自己构建了一个开源的 moneybird-go。
比较苹果和橘子
既然Go是一种比PHP有更好标准库的编译型语言,那么我将要对这两种语言进行比较的话,还真不那么公平。 因此我认为分享一些数字会比较有意思。
性能
我们使用wrk来运行一些简单的HTTP 基准,对两个程序,我们都将返回登录网页的HTML源码。
| 并发 | 平均延迟 | 请求/秒 | 传输量/秒 | |
|---|---|---|---|---|
| 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 |
很不幸的是,当我把并发的“用户”数字增加到超过100的时候, Laravel 应用 (或者 PHP-FPM 套接字)就一直挂掉。
NetData 能够提供以下图表查看服务器在所有负载下如何坚持下来的。
有100个并发连接情况下的Golang 
有100个并发连接情况下的Laravel 
请注意我是在运行程序的同一台机器上跑的这些基准测试,因此这会相当程度的影响到两个图表的表现情况。
代码行数
让我们来比较一下两个应用中的代码行数, 包括所有供应商的依赖。
find . -name '*.php' | xargs wc -l
156289 total
Laravel的版本包含了超过156,000 行的代码。这其中并不包含Laravel运行测试所需的开发依赖。
find . -name '*.go' | xargs wc -l
33624 total
Golang 的版本则包含了33,000行的代码。这是前者数量的五分之一,然而却完成了同样的功能。
我们来排除Laravel应用里的外部依赖关系,因此我们得知到底有多少行代码实际上是由我来写的。
find . -name '*.php' -not -path "./vendor/* " | xargs wc -l
13921 total
而对于Golang:
find . -name '*.go' -not -path "./vendor/* " | xargs wc -l
6750 total
当只是查看自己写的代码行数时,结果稍微均匀些。然而只使用了一半的代码量就完成了同样的应用的功能。
测试覆盖率
测试是Golang里的一等公民,而且测试文件就跟实际的源文件放在一起。
license.go
license_test.go
subscription.go
subscription_test.go
这使得它能非常方便的使用测试驱动开发。
在我们的Laravel应用中我们主要有集成测试来检查请求处理器是否返回了正确的响应。整体测试覆盖率相当的低, 主要是由于紧耦合,当然这主要是我的错。第二次编写同样的应用程序时,这里有了很大的改善。
长文慎入:)
我做了你不该做的事:使用一个完全不同的语言重写了一个应用程序,因为我喜欢这么做。这样做有很多乐趣并且还因此得到了更小更快的应用。