🎉 Save 25%! Early Bird offer - Hurry before it ends!
So you’re looking for how to implement microservices with GoLang? All the videos and conferences you saw seem interesting. So much theory overload, but zero examples of how you can do it yourself. I've been there. It's so frustrating. So I decided to create a simple guide myself, to show you how to create your own microservice project.
When you ask most devs, they will imagine a crazy large-scale infrastructure. Orchestration complexity even rocket scientists can’t comprehend. And… they’re not wrong, but not right either.Â
Those systems do exist in the biggest companies in the world like Google, Amazon, etc… But that's not the case for most projects. Unless you're dealing with the scale of those behemoths, your system will be much simpler.Â
A microservices system doesn’t need to have hundreds of services. You can start with adding one, and then slowly evolving when it makes sense for YOUR project.Â
In most cases you’ll encounter systems that are composed of what I prefer to call “macro-services”. What's that? They are not ideal microservices described in your textbooks, they may handle whole domain logic, rather than a smaller granular parts of it.
For example, in case of a billing system, the ideal microservices architecture would have multiple services like:
But more often than not, in many projects you’ll find a “billing service” that handles all of that. When its logic will start to become too complex, you'll start to break it up, but initially it’ll be more of a “macro-service”. Â
The simplicity and speed of Go, makes it a perfect choice for microservices. It’s fast to develop, easy to test and deploy.Â
I’ve worked on microservices in different languages, and Go usually wins in all aspects.
In one project I worked on we were slowly migrating from Node.JS service to Go, the same service in node was using over 150mb of RAM vs 8mb in Go.
That’s not a big deal for a personal project, but for an enterprise system it is a huge saving in resources.Â
The concurrency in Go is superb, yeah yeah, i know you heard it a million times, but it’s true.
The final reason is size and portability, you can build a go binary for any system you’ll run on in a simple way, and the final binary will most likely be less than 50mb.
Here is where the confusion comes when you want to create your own system. Where do I start? What do I need?
In this article I’ll show you how to create a simple version of a microservices system that you can deploy on any VPS, and manage by yourself.Â
What you need to understand before you start, is what data each part of your system needs. Why is it important?
In monotheistic systems, you can just import whatever repo or service you need and query it. In microservices that’s not the case. You’ll need to do a network call to fetch that data.
The communication between services is the most complex part of microservices, and often it becomes a part of frustrations.Â
Imagine an E-Commerce platform, it has three services, User Service, Order Service and Product service.Â
The user and product services are pretty simple, they handle their own data and don’t care about others. But the order service does, it needs data from both of them.
Synchronous Calls
When a new order is created just call the user and product services via HTTP or gRPC to fetch the needed data. Simple enough, right?
How about if one of the services is down? Then the user cannot create an order, yeah not nice.
So much for a distributed and resilient system.Â
Asynchronous Events
Instead of calling the services for the data every time it's needed, we just listen to the events from the other service and keep a copy in our database.
It does solve the problem of service availability to some extent, but it creates another problem: the eventual consistency of data.Â
Hybrid Approach
The real answer always lays somewhere in between. In most system you’ll see a mix of both, real time calls and asyncronous events.
You have to decide which scenario you are willing to accept.
Using eventually stale data or the possible service down?
To create your microservices infrastrcuture all you need is:
Optionally you can add telemetry to understand your system better, but for small project it can be an overkill.
Before we start with the the code part, we need to have a proper structure. When you work on distributed systems, you want your services to follow the same structure, any member of the team can quickly understand them.
For a baseline I’ll use the structure provided in Golang API Structure for Beginners, its very nice and scalable.
Lets break it down:
This layout helps you clearly separate business logic from infrastructure concerns, making it easier to test, refactor, or swap dependencies later.
In the go ecosystem devs do love the standard library, and in many cases is a great idea to use it, but you want want something more convient for your project.
Personally I am a big fan of Fiber framework, is inspired on node’s Express.js which I worked on for years, so it feels easy to get going. Its robust and fast, and has everything you’ll need for a microservice.
There are other great options like Gin, Go-Kit and other, you can see a comparation here. Remember its your project, choose what feels best for you, not what’s the “communtity” likes.
When it comes to setting up the project there are few things are a must have for me:
You never want to hard code your config data, it’s important to have proper config parsing. Same goes for structured logs, you want to add context and as much important information as you can, so the debuging gets easier.
There nothing worse than a log with a message “something went wrong”.
Here a simple example of a cmd/http/main.go
that you’ll find in microservices project:
package main import ( "fmt" "net/http" "os" "my-service/internal/config" "my-service/internal/dependencies" "my-service/internal/http" ) func main() { cfg := config.LoadFromEnv() deps := dependencies.Init(cfg) srv := http.NewServer(deps) fmt.Printf("Starting HTTP server on port %d\n", cfg.HTTPPort) err := srv.ListenAndServe(fmt.Sprintf(":%d", cfg.HTTPPort)) if err != nil { fmt.Fprintf(os.Stderr, "failed to start server: %v\n", err) os.Exit(1) } }
config.LoadFromEnv()
reads env vars / config filedependencies.Init()
wires DB, repos, services, etc.http.NewServer(deps)
sets up the router, middleware, handlersSimple and easy, nothing more than the necessary.
You’ve got the foundation, you understand what microservices are, why Go is such a great match, and how to set up your first service with a clean, scalable structure.
But this is just the beginning.
If you want to go deeper and build a complete microservices system in Go, everything we’ve covered here comes to life in my course: Build Microservices in Go.