This article on moving from PHP to Go was first published here and redistributed with the permission of the author.

Earlier this year, I made what was arguably a bad business decision. I decided to rewrite Boxzilla’s Laravel application in Go.

But I don’t regret it.

A few months later I started deploying the Go application. Building this app has been the most fun I’ve had in months. I learned a lot and ended up making a lot of improvements on the old app. Better performance, easier to deploy, higher test coverage.

The application is a very straightforward database-driven API and account domain where users can log in directly to download products, view invoices or update payment methods.

Stripe and Braintree accept subscription payments. MoneyBird is used to process invoices and Mailgun is used to email some transactions.

Although Laravel did a good job of that, some things were too complicated for me. And what about a new “major release” every month? It would be nice if every new release brought some meaningful improvement, but in my opinion, most of the time it brought only minor naming or file structure changes.

Why Go?

I’ve moved several services to Go in the last year, so I’m not new to the language. Part of my job as a developer selling WordPress products is to work within the ancient technology stack, which focuses on end users.

If I wasn’t freelancing, I’d be applying for a new job to make up for not being able to use this sexy technology. Since I’m my own boss, I want to keep my daily work fun, not just in pursuit of more immediate money. If income allows it (and it does), why not be happy?

Writing code in Go is a joy, the tools are great, not only are they fast to develop, but the end result is usually fast. It is recommended to read the purpose of the Go project to learn about the language.

I think we’re going to see a significant shift from dynamically typed languages like PHP, Python, and JavaScript to Go over the next few years.

Ported code base

Migrating the code to Golang mainly involves interacting with the database properly and porting the Blade template to something we can use in Go.

ORMs always ended up getting me in trouble, so I used a simulated data access layer and plain SQL queries. Meddler can be used to help me avoid using boilerplate that can be used to map query results to structures.

To support both layered and partial templates, I’ve opened source Grender, a small wrapper based on the Go standard HTML/Template package. It made it easy to port Blade template files to Go because I could use the same hierarchical structure and local templates.

For integration with Stripe, there is an official Stripe-Go package available. For Braintree, there is the unofficial Braintree-Go package, which was neglected for a while but has recently regained attention. Since there was no Go package to manage invoices in Moneybird, I built an open source Moneybird-Go.

Compare apples and oranges

Since Go is a compiled language with a better standard library than PHP, it’s not really fair for me to compare the two languages. So I thought it would be interesting to share some numbers.

performance

We use WRK to run some simple HTTP benchmarks, and for both applications, we return the HTML source for the login page.

  concurrent The average latency Requests per second Transmission per second
Laravel 1 3.87 ms 261.48 1.27 MB
Laravel 100 108.86 ms 917.27 6.04 MB
Go 1 325.72 u s 7365.48 34.27 MB
Go 100 11.63 ms 19967.31 92.91 MB
Go 200 37.68 ms 22653.22 105.41 MB

Unfortunately, the Laravel application (or phP-FPM socket) kept dying when I increased the number of concurrent “users” to more than 100.

NetData can provide the following chart to see how the server held up under all the load.

There are 100 concurrent connections in case of Golang

There are 100 concurrent connections in Laravel case

Note that I am running these benchmarks on the same machine that is running the program, so this significantly affects the performance of both charts.

Lines of code

Let’s compare the lines of code in the two applications, including all vendor dependencies.

find . -name '*.php' | xargs wc -l
156289 total
Copy the code

Laravel’s version contains more than 156,000 lines of code. This does not include the development dependencies that Laravel needs to run tests.

find . -name '*.go' | xargs wc -l
33624 total
Copy the code

Golang’s version contains 33,000 lines of code. That’s a fifth of the number, yet it does the same thing.

Let’s exclude external dependencies in the Laravel app, so we know how many lines of code I actually wrote.

find . -name '*.php' -not -path "./vendor/* " | xargs wc -l
13921 total
Copy the code

For Golang:

find . -name '*.go' -not -path "./vendor/* " | xargs wc -l
6750 total
Copy the code

When you just look at the number of lines of code you’ve written, the results are slightly more even. Yet the same application can be done with half the amount of code.

Test coverage

The test is a first-class citizen in Golang, and the test file is placed next to the actual source file.

license.go
license_test.go
subscription.go
subscription_test.go
Copy the code

This makes it very convenient to use test-driven development.

In our Laravel application we mainly have integration tests to check that the request handler has returned the correct response. Overall test coverage is pretty low, mostly due to tight coupling, which is mostly my fault. The second time I wrote the same application, there was a big improvement.

Long article carefully into 🙂

I did what you shouldn’t: I rewrote an application in a completely different language because I liked it. It’s a lot of fun and leads to smaller, faster applications.