Objective
Writing our first microservice in GoLang using gin-gonic as a framework.
Golang is a buzzword that everyone wants to explore and have a taste of nowadays.
At ZestMoney, we had to build a concurrent, scalable and fault-tolerant microservice so we started exploring Golang.
Features needed for our micro-service
- Jwt Authentication for all our API calls; just to provide an overview, all our services are authenticated via a JWT token with different scopes, so we wanted the same feature to be implemented for this service.
- All our configurations are stored on our configuration server rather than inside our code and we fetch all those configurations during our service execution, depending on what environment is chosen, i.e. dev, stage, prod.
- A lot of our codebase is written in Java and we use maven as a build automation tool for managing our dependency. We needed a similar library while designing the service using Golang.
- We are using Flyway as our migration tool so we would need a similar migration tool that can run our migration seamlessly when an application starts.
- An ORM tool since we avoid writing plain native query.
- Logging mechanism inside the service. Writing logs to files and log rotation.
Background
History of Go
Go was created by Robert Griesemer, Rob Pike, and Ken Thompson in the year 2007 with the aim of providing a language similar to C but with more type safety, garbage collection, and support for concurrency.
Why Gin-Gonic?
- There are many frameworks available but we went with Gin since it has a huge community, the code coverage is ~ 98% () and it has comparatively fewer open issues.
- It features a Martini-like API with much better performance that is up to 40 times faster.
- Gin Gonic can be primarily classified as “Frameworks (Full Stack)” tools.
- SpartanGeek, Sezzle, and SEASON are some of the popular companies that use Gin Gonic.
Comparison of Golang with other Languages
Below are some of the fastest programs which are used for benchmarking languages.
Go Vs Python
Go Vs Java
- secs => The time is taken before forking the child-process and after the child-process exits
- mem => Memory taken by the program to run
- busy => The cpu idle and cpu total are taken before forking the child-process and after the child-process exits. The sum of cpu not-idle for each core, scaled by secs
More details can be found here => https://benchmarksgame-team.pages.debian.net/benchmarksgame/how-programs-are-measured.html
Also, we can see that Go has fairly good performance compared to languages like Java, Python, etc as per the above data points.
Features of Go
Fast Compilation
Go offers lightning fast compilation compared to even C and C++ by using a smart compiler and a simplified dependency resolution algorithm.
When building Go program, the compiler only needs to look at the libraries that need to be directly included, rather than traversing the dependencies of all the libraries that are included in the entire dependency chain like Java, C, and C++.
Concurrency
- C, C++, Java will create OS threads to achieve concurrency.
- Go,on the other hand, does not use OS-level threads to achieve concurrency.
- Concurrency in Go can be achieved via Go-routines.
- Go-routines are neither system thread nor thread managed by OS runtime.
- One mechanism of using Go-routines is via channels.
- Go channels provide built-in thread safety and encourage single-threaded access to shared, critical resources
- Go’s stacks are dynamically sized, growing and shrinking with the amount of data stored, which helps in solving memory-related errors.
Example of a Go-routine =>
- package main
- import (
- “fmt”
- “time”
- )
- func f(input string) {
- fmt.Println(input, “:”, i)
- }
- func main() {
- f(“hello”)
- go f(“goroutine”)
- time.Sleep(time.Second )
- fmt.Println(“done”)
- }
As you can see, we have a function that is printing the input value passed in the parameter.
We can call the function synchronously using just the function name, i.e. line number 13.
However, if you want to run the same function via go-routine, you have to prefix go before the function call, i.e. line number 15.
Statically typed
Go needs all your variables and functions to be declared at compile time and this leads to the error being found during compile time rather than run time.
Imagine you have written a function in some dynamic language like javascript that expects a variable ID in a parameter. Now to check if that variable is a string, int, UUID, we will have to check the source, however, the go compiler will check the type during compilation.
Running on Different Platform
Go code directly gets compiled into machine code, which also depends upon the CPU (Linux/windows/mac) making it fast.
Since the code directly gets converted into machine code, this makes the go binaries portable. It further helps the developer run the binary on any similar architecture machine, i.e. code compiled on Linux 64-bit can run on any other Linux 64-bit platform.
Consistency
Function names in Go will start with uppercase and will be exposed outside and functions whose name starts with lowercase are not exposed outside.
I am personally not a big fan of this feature as I am fond of using camelCase :).
Garbage Collection
Go has a single garbage collector algorithm that is highly optimized for very low latency with GC pauses in the order of microseconds which helps in optimizing GC.
How we Build our service
We used open-source libraries to meet our needs. They are all mentioned below:
- Authentication => github.com/dgrijalva/jwt-go , github.com/auth0/go-jwt-middleware
- Managing modules => (https://blog.golang.org/using-go-modules)
- ORM => gorm (https://gorm.io/)
- Migration => goose (https://github.com/pressly/goose)
- Logging => Zap (https://github.com/uber-go/zap)
Authentication
For auth, I have shared the link of my boiler-plate repository which I hope will be helpful.
Modules
For managing modules inside a Go service, you can just type the below command:
Go mod init <package name>
Go will create a go.mod file that will try to fetch all the dependencies when you are building your service.
Logging
Logging is hygiene for service in production.
We have used Zap (Logging library by Uber) and Lumberjack for log rotation.
Zap is blazing fast compared to other logging libraries. You can find an example below:
Link for sample Code => Logging
Migration
We are using gorm as ORM and goose for Database migration.
Some of the features of the library:
- Full-Featured ORM (almost)
- Associations (Has One, Has Many, Belongs To, Many To Many, Polymorphism)
- Hooks (Before/After Create/Save/Update/Delete/Find)
- Preloading (eager loading)
- Transactions
- Composite Primary Key
- SQL Builder
- Auto Migrations
- Logger
- Extendable, write Plugins based on GORM callbacks
- Every feature comes with tests
- Developer Friendly
All these features can be seen on their page as well https://gorm.io/.
We did not want to write extra code for doing migration that is explicitly provided by gorm. Rather we wanted to add the SQL files in our code and run the migration similar to the flyway library in java.
Also, in the future, if we have to run the migration elsewhere, we should be able to take those files and directly run via SQL commands.
We have chosen to do migration via goose. Goose internally creates a goose_db_version table that checks for all the migration files that we have added inside our db/migration folder.
Link for Sample Code => DB Migration
Conclusion
- The reason for choosing Go inside ZestMoney was that we wanted to build a highly concurrent service with less footprint, highly optimized GC, serving concurrent requests with very fast context switching.
- Go has helped us achieve the use case and now a lot of the other microservices inside ZestMoney are getting written with the help of Go.
- One thing I can add while using Go is that it is one of the most robust languages, which might take a bit of time to learn (writing code for enterprise solutions), but once you are comfortable with it, you can fairly evaluate Go vs any other language.
Please find below the link for the sample boiler-plate, which can be used for building Rest API’s.