• Set up our Go workspace
  • Build our first microservice
  • Serve some JSON over HTTP using Gorilla Web Toolkit.
    To stay focused on the Go fundamentals, we’ll wait a bit with deploying it on Docker Swarm.

Introduction

While serving JSON over HTTP isn’t the only option for inter-service or external communication, we’ll focus on HTTP and JSON in this blog series. Using RPC mechanisms and binary messaging formats such as can be a very interesting option for inter-service communication or for external communication when the external consumer of your service(s) is another system. Go has built-in RPC support and gRPC is absolutely worth looking at as well. However, for now we’ll focus on HTTP provided by the built-in http package and .

Another aspect to take into account is that many useful frameworks (security, tracing, …) relies on HTTP headers to transfer state about the ongoing request between participants. Examples we’ll see in later blog posts is how we’ll pass correlation ID’s and OAuth bearers in HTTP headers. While other protocols certainly supports similar mechanisms, many frameworks are built with HTTP in mind and I’d rather try to keep our integrations as straightforward as possible.

Setting up a Go Workspace

Feel free to skip the section if you’re already a seasoned Go dev. In my humble opinion, the structure of a Go workspace took some time getting used to. Where I’m used to typically having the root of a project as workspace root, Go conventions about how to properly structure a workspace so the go compiler can find source codes and dependencies is somewhat unorthodox, placing your source code under the /src folder in a subtree named after its source control path. I strongly recommend reading the official guide and before getting started. I wish I had.

Before writing our first lines of code (or checking out the complete source), we’ll need to install the Go SDK. I suggest following the official guide, it should be straightforward enough.

In this blog series, we’ll be using the built-in Go SDK tools we just installed for building and running, as well as following the idiomatic way of setting up a Go workspace.

1. Create a root folder for your workspace

All commands are based on a OS X or Linux dev environment. If you’re running Windows, please adapt the instructions as necessary.

Here we created a root folder and then assigned the environment variable to that folder. This is the root of our workspace under which all Go source we write or 3rd party libraries we’ll use will end up. I recommend adding this GOPATH to your .bash_profile or similar so you don’t have to reset it each time you open up a new console window.

Given that we’re in the root of the workspace (e.g. the same folder as specified in the GOPATH env var), execute the following statements:

If you want to follow along and code stuff yourself, execute these commands:

  1. cd src/github.com/callistaenterprise
  2. mkdir -p goblog/accountservice
  3. cd goblog/accountservice
  4. touch main.go
  5. mkdir service

OR - you can clone the git repository containing the sample code and switch to branch P2. From the src/github.com/callistaenterprise folder you created above, execute:

  1. git clone https://github.com/callistaenterprise/goblog.git
  2. cd goblog
  3. git checkout P2

Remember - $GOPATH/src/github.com/callistaenterprise/goblog is the root folder of our project and what’s actually stored on github.

Now we should have enough structure to easily get us started. Open up main.go in your Go IDE of choice. I’m using IntelliJ IDEA with their excellent Golang plugin when writing the code for this blog series. Other popular choices seems to be Eclipse (with Go plugin), , Sublime, or JetBrains new dedicated commercial Gogland IDE.

Creating the service - main.go

The main function in Go is exactly what you expect it to be - the entry point of our Go programs. Let’s create just enough code to get something we can actually build and run:

  1. package main
  2. import (
  3. "fmt"
  4. var appName = "accountservice"
  5. func main() {
  6. fmt.Printf("Starting %v\n", appName)
  7. }

Now, let’s run it. Make sure you’re in the folder corresponding to your $GOPATH/src/github.com/callistaenterprise/goblog/accountservice

  1. > go run *.go
  2. Starting accountservice
  3. >

That’s it! This program will just print and then exit. Time to add our very first HTTP endpoint!

Building an HTTP web server

To keep things neat, we’ll put all HTTP service related files into the service folder.

Bootstrap the HTTP server

Create the file webserver.go inside the /services folder:

We’re using the built-in net/http package to execute ListenAndServe which starts a HTTP server on the specified port.

Update main.go so we call the StartWebServer function with a (for now) hard-coded port:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. var appName = "accountservice"
  6. func main() {
  7. fmt.Printf("Starting %v\n", appName)
  8. service.StartWebServer("6767") // NEW
  9. }

Run the program again:

  1. > go run *.go
  2. Starting accountservice
  3. 2017/01/30 19:36:00 Starting HTTP service at 6767

We now have a simple HTTP server listening to port 6767 on localhost. it:

  1. > curl http://localhost:6767
  2. 404 page not found

A 404 is exactly what we’re expecting as we havn’t declared any routes yet.

Stop the Web server by pressing Ctrl+C.

It’s time to actually serve something from our server. We’ll start by declaring our very first route using a Go that we’ll use to populate the Gorilla router. In the service folder, create routes.go:

  1. package service
  2. import "net/http"
  3. // Defines a single route, e.g. a human readable name, HTTP method and the
  4. // pattern the function that will execute when the route is called.
  5. type Route struct {
  6. Name string
  7. Pattern string
  8. HandlerFunc http.HandlerFunc
  9. }
  10. type Routes []Route
  11. // Initialize our routes
  12. var routes = Routes{
  13. Route{
  14. "GetAccount", // Name
  15. "GET", // HTTP method
  16. "/accounts/{accountId}", // Route pattern
  17. func(w http.ResponseWriter, r *http.Request) {
  18. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  19. w.Write([]byte("{\"result\":\"OK\"}"))
  20. },
  21. },
  22. }

In the snippet above, we declared the path /accounts/{accountId} which we later can curl. Gorilla also supports complex routing with regexp pattern matching, schemes, methods, queries, headers values etc. so one is certainly not limited to just paths and path parameters.

For now, we will just return a tiny JSON message we’ve hard-coded as response:

  1. {"result":"OK"}

We’ll also need some boilerplate code that hooks up the actual Gorilla Router to the routes we declared. In service folder, create router.go:

Importing dependencies

In the import section for router.go we see that we have declared a dependency on the github.com/gorilla/mux package. See for a good explanation on how go dependencies are fetched using go get.

In order for the above file to compile and run, we’ll need to use go get to fetch the declared package(s) into our workspace:

  1. > go get

This may take a little while since the Go tool is actually downloading all the source code required by the gorilla/mux package from https://github.com/gorilla/mux. This source code will end up in $GOPATH/src/github.com/gorilla/mux on your local file system and it will be built into your statically linked binary.

Wrapping up

  1. func StartWebServer(port string) {
  2. r := NewRouter() // NEW
  3. http.Handle("/", r) // NEW

This attaches the Router we just created to the http.Handle for the root path /. Let’s compile and run the server again.

  1. > go run *.go
  2. Starting accountservice
  3. 2017/01/31 15:15:57 Starting HTTP service at 6767

Try to curl:

  1. > curl http://localhost:6767/accounts/10000
  2. {"result":"OK"}

Nice! We’ve just created our first HTTP service!

Footprint and performance

Given that we’re exploring Go-based microservices due to alleged awesome memory footprint and good performance, we’d better do a quick benchmark to see how this performs. I’ve developed a simple test that hammers /accounts/{accountId} with GET requests. If you’ve checked out the source for this part, you can find the load test in the /goblog/loadtest folder. Or you can look at it on github.

If you want to run the load-test yourself, make sure the “accountservice” is up and running on localhost and that you have cloned the source and checked out branch P2. You’ll also need to have a Java Runtime Environment and installed.

Change directory to the /goblog/loadtest folder and execute the following command from the command-line:

This should start and run the test. The arguments are:

  • users: Number of concurrent users the test will simulate
  • duration: For how many seconds the test will run
  • baseUrl: Base path to the host providing the service we’re testing. When we move over to Docker Swarm, the baseUrl will need to be changed to the public IP of the Swarm. More on that in part 5.
    After the test has finished, it writes results to the console windows as well as a fancy HTML report into target/gatling/results/.

Results

Note: Later on, when the services we’re building are running inside Docker containers on Docker Swarm, we’ll do all benchmarks and metrics capturing there. Until then, my mid-2014 MacBook Pro will have to suffice.

Before starting the load test, the memory consumption of the Go-based “accountservice” is as follows according to the OS X task manager:

1.8 mb, not bad! Let’s start the Gatling test running 1K req/s. Remember that this is a very naive implementation that just responds with a hard-coded string.

mem use2Ok, serving 1K req/s makes the “accountservice” consume about 28 mb of RAM. That’s still perhaps 1/10th of what a Spring Boot application uses at startup. It will be very interesting to see how this figures changes once we start to add some real functionality to it.

Performance and CPU usage

Serving 1K req/s uses about 8% of a single Core.

performanceNote sure how Gatling rounds sub-millisecond latencies, but mean latency is reported as 0 ms with one request taking a whopping 11 millisecond. At this point, our “Accountservice” is performing admirably, serving on average 745~req/s in the sub-millisecond range.

In the , we’ll actually make our accountservice do something useful. We’ll add a simple embedded database with Account objects that we’ll serve over HTTP. We’ll also take a look at JSON serialization and check how these additions to the service affects its footprint and performance.