Friday, November 6, 2020

Go in Production – Lessons Learned

I recently got a DevOps job that mostly involves writing a new backend system in Go completely from scratch. Here's what I learned having never actually used it in production, knowing it mostly just from personal projects.

1. You (probably) should use a web framework

At the start, we decided to try simply sticking with Go's http library and a simple routing library - mux.

However, I quickly ran into real-life, production problems:

  • Recovery middleware  -  for logging & silently handling panics in handler's code.
  • Logging  -  I wanted some solution that would print information about each request, including body params, auth tokens, etc. (for debugging purposes).
  • Better error handling  -  I wanted the errors to still be JSON responses with an error message and code.
  • Other commonly-used middleware  -  including JWT auth and CORS.

I had two options; implement solutions to the above problems myself, use different 3rd party libraries for each problem, or pick a web framework that already does most (if not all) of these things.

I eventually decided to use the Echo web framework. With almost 20k GitHub stars, a pretty active community, and great documentation I thought it was a great tool for the job.

echo site

I also found there to be a little less boilerplate when it comes to writing the apps in echo (mainly when parsing a json body, writing errors, and manually setting headers), leading to improved code readability.

comparison

However the real difference in productivity will be noticed when you have slightly more complex endpoints. You'll often run into cases where you need to validate certain JSON fields, and you'll want meaningful error messages describing what's wrong. If you want to do that without any library, your code will quickly become much harder to read:

comparison

2. You NEED a good code structure

Go web frameworks (or go in general) don't enforce any particular file structure. If you ever used something like ASP.NET/ASP.NET Core, you'll know what I'm talking about when I say that some frameworks are tightly structured and many things are done implicitly by convention rather than explicitly specified.

The thing about Go is that it's really easy to skip learning about structuring your code and make it a hard to read+maintain mess. If you still don't know what I'm talking about, here's an example of a (bad) Go endpoint I wrote a while back:

bad code

Do you see what I mean? It's quite likely that the in total the "better" way will contain more lines in total after adding all the CreateUser and CreateAgency methods, but… It will be much easier to understand, reuse, debug, and modify later on as each method will have a single purpose. If you haven't already, I highly recommend you have a look at the following resources for a good code structure:

In general, the concept is simple. You should separate the code that communicates with the database from the actual application logic itself, which should also stay separate from the transport/endpoint logic (in this example the HTTP endpoints).

3. Pick your SQL driver wisely

When I first started programming in Go, I wanted to use the least libraries possible, so of course I opted in for using the database/sql package (with Postgres). Although the experience was OK, I came across quite a lot of boilerplate when querying data, especially having to use the Scan syntax. This lead me to the following 2 options:

  • sqlx -  a lightweight wrapper on top of database/sql with some extensions that will make querying much easier.
  • gorm -  an ORM (Object-Relational Mapping) library for Go, which generates SQL models and queries based on your Go models.

I don't think there's a clear "better" library, in the end it comes down to the use case and preference.

gorm will probably make your life easier, especially if you often forget to add fields to queries after altering the database (because in gorm you won't have to do that at all). 

sqlx on the other hand is much more SQL centric, it's more like writing Go code to interface with SQL rather than gorm 's approach of generating SQL from Go code. It's nice if you prefer to have total control over your SQL and not having to learn new syntax of GORM.

4. Docker

One of the challenges I came across was configuring the project for production. There are always some differences between the dev and prod environments, such as what port the app should run on, the host and credentials for the database, and etc.

I've seen people configure their app variables via JSON, YAML, or even gitignored .go files. I personally found env files to work the best, especially with docker-compose:

.env docker-compose I usually combine these with the following utility functions: Dockerfile Building the Docker image is also extremely easy in Go.: Dockerfile

5. Anything else?

There are also a few other things that came to my mind that I didn't consider worth their own sections:



from Hacker News https://ift.tt/38knrPe

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.