Making it Go
A Brief Dive into Go Microservices
Lately we’ve been looking to expand our skills and modernise our tech stack at Brush, and part of that has involved gaining experience with new and diverse programming languages.
As developers, a chief concern is the technical quality of our software, but as a business we also care about the time-cost of our tools. Our main language of choice, Python, offers a lot of valuable and easy-to-use tools and web frameworks with powerful abstractions. However, these can come with unwanted baggage, which can create problems and slow a project down – technical simplicity is one of our core engineering values..
We needed to develop a few quick utility APIs for our customers, so we took the opportunity to take a look at something new.
Why Go?
Go (sometimes known as Golang) has been around since 2012 and has really taken off in recent years. In 2020, it ranked in the top 10 most used languages by professional developers in the State of Developer Ecosystem survey. That’s no small feat considering the ubiquity of languages like JavaScript, Python, Java, and C#. It has fast compile times and great support for modules, IDEs and tooling. It’s responsible for widespread technologies like Docker and Terraform, and sees a lot of use within tech stacks across the globe, including at Google (who developed the language), Microsoft, Netflix, GitHub, and plenty more.
The Plan
We wanted to build a microservice that could provide an email blacklist for our different clients. As anyone with email can tell you, spam messages and other email chicanery are everywhere. While lots of the tools available these days can reduce the incidence of malicious spam through your site (such as reCaptcha preventing bots from spamming signup and contact forms), it’s slightly more annoying to deal with real users entering incorrect addresses by mistake.
One of the easiest ways to deal with this is email verification – where your application requires a user to confirm their address, usually via a link or code, so if they enter the wrong address, no future emails are sent to that destination – but that doesn’t work as well when a user is providing an address they don’t control, such as sending emails to a third party via a service (like sending Xero invoices to a customer’s address). We wanted to make a single reusable microservice to store these bad emails, and allow our applications to check whether a user provided address was known to be bad, and if so provide feedback to the user.
Email might seem like a quaint relic in the days of $44B Twitter buyouts, but it’s still very relevant in modern business software, partly for communication, but also for things like automated notifications, third-party verifications, or inter-service connections. When the emails that your service sends out are critical – such as an urgent maintenance alert for a helicopter – it’s important to know whether the email address provided is usable, and you can’t necessarily wait for the 3rd party to verify they’ve received a message.
The Journey
With our destination in mind, we started looking into the world of Go, and found a rich ecosystem awaiting us. There was plenty of instructional material available online, with working examples and in-depth explanations of features and syntax, and in some cases full implementations of services to look at. After a brief tour of the language, we looked into working with databases, setting up web routes, and transforming data. After a few hours of this, we met to discuss which precise features we would need to implement in our microservice.
For our purposes, we didn’t need most of the components typically associated with web frameworks – there was no static content to deliver, no need for rendered templates or any frontend display, and our database layer was going to be very minimal, so no need for an especially thorough ORM. Out of the available frameworks, we decided to use Gin, a “Go framework that features a Martini-like API with performance that is up to 40 times faster” – which is a fancy way of saying it’s blazingly fast, minimalist, and modular. Sounds perfect for our use case, and when combined with MongoDB and Mongo Go Models for our database layer, we had all of the components we needed.
We identified our API structure, and the required capabilities of the service, and then set out to collaboratively create the router and its underlying logic. The Gin framework made this painless and quick, and by the end of our first day we had set up about 90% of our required features. We returned to the project a week later, and had deployed the first instance of our microservice by that afternoon. It’s been running smoothly since then, and we’ve been steadily integrating it with our existing applications as time allows. All in all, our foray into Go was a pleasant success. We’ve been looking at other use cases we could apply these skills into since then, and have found a few targets that we might tackle in future.
What we Loved
- Go is super integrated with tooling, such as the built in Go Modules tool for managing dependencies, or the excellent plugins for IDEs like VS Code.
- When they say Go is fast, they really mean it. It’s a very lightweight language – compilation is fast, binaries are small, and programs will run quickly.
- Go is statically typed – this ties in to the tooling integration, and means developing is predictable – even when using a 3rd party library, you can always trust that the values you get from a function will be what you expect.
- It’s very easy to learn – the numerous online tutorials are a big part of this, but more important is the inherent simplicity of the language. For a relatively low-level language, it has a very readable syntax; it’s similar to any other C-like language, so you can usually read a section of code and more or less understand it right away
- Despite being a low-level language, Go has dealt with a lot of the pain-points of modern development. Garbage collection is handled for you, it’s hard to break things with pointers, and, most impressively, concurrency is both expected and easy to work with via “goroutines” and channels.
What we Didn’t
- Go uses implicit interfaces – while this can make reuse and maintenance of code a bit easier, it can make it hard to tell if you’ve correctly implemented an interface without just trying to use it, which feels odd given how well the rest of the static analysis works.
- Errors in Go are handled quite differently from most other programming languages, being treated as a normal value rather than an exception. This isn’t necessarily a bad thing, but it adds a bit of adjustment time as you need to switch your thinking around errors.
Where can Go be used?
We’re enthusiastic about what Go can do, and excited to propose it to our clients where appropriate. That said, it’s definitely not a one-size-fits-all answer to the various problems we want to help our clients solve. If you’re thinking of working with us, we might recommend Go for your projects when:
- You want a lightweight, simple tool or service that needs to be easy to distribute
- You need a fast and concurrent solution, but don’t want to dedicate lots of resources just to getting your concurrency running
- You want to work with lower level concepts, but don’t want the headache of using C/C++
In particular, we think Go is great for lightweight microservice APIs, command-line utilities, background tasks, and for embedded systems.
14 December 2022 by Murray Tait