How to Write Microservices in Go

Explore microservices in other languages

Writing Microservices in Go

There are many options when it comes to picking the right language for building your microservices. Honestly, there is no “perfect” one, because whether they’re a good fit or not depends on many variables: your project, your team, your context, and more. However, out of all the different programming languages, some definitely lend themselves to the task, and Go is one of those.

From its inception at Google in 2007, Go has evolved and risen to every challenge like the best of them. Throughout the years, we’ve seen developers using it for multiple different projects proving how versatile of a tool Go ended up being.

In this article, we’re going to go over why Go might be a great option for your next microservice and we’ll also quickly show you how to get started!

So let’s learn how to build microservices using Go!

Pros and cons of building microservices with Go

While there is no single language that is perfect for building microservices, there are always “pros” and “cons” we can assess while picking the right stack for our project.

Let’s take a look at some of the benefits of choosing Go as our main driving language:

  • Efficiency: Go is known for its efficiency in terms of both runtime performance and development speed. Its compiled nature and lightweight concurrency primitives make it a solid choice for building highly responsive and efficient microservices.

  • Concurrency: Go’s goroutines and channels make it easy to handle concurrent tasks, which is essential for microservices that often need to handle multiple requests and tasks simultaneously. This simplifies the design of concurrent systems, reducing the chances of errors and making it easier to scale.

  • Minimalist Syntax: The less you have to write, the faster you’ll get the logic written, and Go’s simplicity and minimalist syntax make it easy for developers to do exactly that. This can significantly reduce the learning curve for new team members and improve collaboration on microservices projects.

  • Strong Standard Library: Go comes with a rich standard library that includes packages for HTTP handling, JSON encoding/decoding, and more. This built-in support for common tasks simplifies microservices development and reduces the need for third-party libraries.

  • Scalability: Go’s design promotes scalability. With its efficient garbage collector and support for concurrent processing, Go is well-suited for building microservices that can handle a high volume of requests and scale horizontally as needed.

  • Static Typing: While this might be a controversial point, you can’t deny that Go’s static typing system will help you catch errors at compile time. You may or may not agree on the benefits of static typing, but this effectively reduces the likelihood of runtime errors and makes code more robust and maintainable. In turn, that will help while creating any type of project, not only microservices, but it’s definitely a “pro” of using this language.

As mentioned, there is no “perfect” solution, and while the above points might sound great, there is also a downside to this choice of language:

  • Limited Frameworks and Libraries: While Go’s standard library is robust, the ecosystem of third-party libraries and frameworks is not as extensive as some other languages (like say JavaScript, where a new framework is released every 2 weeks). You may need to implement certain functionalities from scratch or rely on less mature libraries.

  • Garbage Collection Latency: While Go’s garbage collector is efficient, it can introduce occasional latency spikes, which may be a concern for microservices requiring strict real-time performance.

  • Less Popularity Than Some Other Languages: While Go has gained popularity, it might not be as widely adopted as languages like Java, Python, or JavaScript. This can impact the availability of developers with Go expertise or even the material required for your devs to pick up Go as a language fast enough.

Which hyperautomation tools should be included in your tech stack to stay competitive?

Developers’ Guide to Building a Hyperautomation Tech Stack

Honestly, the list of cons for Go is pretty slim and they’re all very context-specific, so for you they might not even be relevant. 

This points to Go as being one of the best options out there for building microservices. At least with teams that are proficient with it.

Let’s now review what building a microservice in Go looks like.

Building Microservices in Go

In this hands-on example, we’ll create a simplified product catalog microservice using Go. This microservice will expose RESTful endpoints for managing product information.

Step 1: Project setup

If you haven’t yet, make sure to download the latest version of Go for your specific OS from their official website.

Step 2: Define the data model

We’ll quickly create a new type for our “product” data model that looks like this (in other words, it’ll have an ID, a name, and a price):

				
					type Product struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Price string `json:"price"`
}

				
			

Note that after each field declaration, there’s a string inside backticks (`). Those are struct field tags. They provide a way to attach metadata to the fields. In this case, they’re being used to control how the fields are being serialized to JSON. 

For example, `json:”id”` tells Go’s JSON package to encode/decode the `ID` field as JSON with the key “id”.

Step 3: Implement RESTful endpoints

We will create a single handler function that will act as a “controller” sort of speak. While we’re not really implementing an MVC pattern here, the function will centralize the request handling, and based on the HTTP method used, it’ll call the proper handling function.

				
					func handleProduct(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		handleProductGet(w, r)
	case "POST":
		handleProductPost(w, r)
	default:
		http.Error(w, "Method not supported.", http.StatusMethodNotAllowed)
	}
}

				
			

The way we specify the URL for this handler function to listen to, is by using the http.HandleFunc method like this:

				
					func main() {
	http.HandleFunc("/product", handleProduct)

	fmt.Println("Server running on port 8081")
	log.Fatal(http.ListenAndServe(":8081", nil))
}

				
			

Our handler function will call the handleProductGet function to list all products, and the handleProductPost function to add a new product to the data repository (more on that in a second).

Let’s take a look at the implementation of both functions.

Step 4: Implement the business logic

Our “business logic” is going to be the listing and creation of products, and that code will be part of the following functions:

				
					func handleProductPost(w http.ResponseWriter, r *http.Request) {
	var product Product
	err := json.NewDecoder(r.Body).Decode(&product)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	ch := w.Header()
	ch.Set("Content-type", "application/json")
	mux.Lock()
	products[product.ID] = product
	mux.Unlock()

	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(product)
}

func handleProductGet(w http.ResponseWriter, r *http.Request) {
	id := r.URL.Query().Get("id")
	if id != "" {
		mux.Lock()
		product, exists := products[id]
		mux.Unlock()

		if !exists {
			http.Error(w, "Product not found.", http.StatusNotFound)
			return
		}

		json.NewEncoder(w).Encode(product)
		return
	}

	var productList []Product
	ch := w.Header()
	ch.Set("Content-type", "application/json")
	mux.Lock()
	for _, product := range products {
		productList = append(productList, product)
	}
	mux.Unlock()

	json.NewEncoder(w).Encode(productList)
}

				
			

Notice how the handleProductGet function can, in fact, handle two scenarios: one where you ask specifically for the details of a single product (by passing an ID as query parameter) and one where you request the full list of products.

Step 5: Implement the data layer

At this stage, you’ll want to add database access code. It doesn’t necessarily matter what DB you use at this point, just make sure you persist the data somehow. 

To keep the article’s length under control, we’ll simply use a map to store the data of our products. 

If you look back at the previous two functions, you probably saw code using it.

To declare our “database”, we’ll simply use the following code:

				
					var (
	products = make(map[string]Product)
	mux      sync.Mutex
)

				
			

Here, products is a map where each key-value pair represents a product:

  • The key is a String that represents the product’s ID.
  • The value is a Product struct that holds the details of each product.

In Go, a map is a built-in data type that associates values of one type (the “value” type, in this case, Product) with values of another type (the “key” type, in this case string). It’s similar to what other languages call a dictionary (Python), hash (Ruby), or object (JavaScript).

This map is being used as a simple in-memory data store for the product catalog. When a product is added, it’s stored in this map with its ID as the key. The product’s details can then be retrieved using the ID.

The sync.Mutex type we’ve declared mux as is a mutual exclusion lock. It’s used to synchronize access to the map. This is necessary because maps in Go are not safe for concurrent use.

That’s it! You’ve just created a basic product catalog microservice in Go. You can expand upon this example by adding more features like authentication, pagination, and validation to suit your specific requirements.

Orchestrate microservices with Camunda

Leverage the strength of Go’s concurrency model and simplicity in your microservices architecture with Camunda’s workflow engine. 

Camunda’s orchestration tools are designed to integrate effortlessly with the Go ecosystem, ensuring lightweight process management that respects Go’s design principles. 

By providing a robust framework for visual design, automatic execution, and process improvement via BPMN, Camunda enables Go developers to focus on writing high-quality code while it handles the complexities of inter-service communication, state resilience, and real-time process visibility. Curious to see how Camunda can help you with microservice orchestration? Check out this article about the top 6 benefits of setting up an event-driven process orchestration, and then dive right into Camunda with a free account.

Start orchestrating your microservices with Camunda