The Coffee is Getting Cold, It’s Time to GO - Getting Started Building Microservices

16 minute read

golangverticlejava

Overview

Java has been around a really, really long time. Certainly it continues to evolve and has evolved. Java has always been a "can do anything" programming language. It has more frameworks and middleware than there are stars in the sky. It is portable anywhere and of course probably 8 out of 10 developers today know Java to some degree. Given all of this though is Java the path forward?

Looking forward I think the clear trend is microservices and beyond. Therefore the question is a lot simpler, is Java the best path forward for microservices?

How Java Became #1

Java was released in 1996. At that time the biggest issue was compiling code and porting. At this time there were many Unix, Linux, Windows flavors and many other variants. Open systems was the key initiative. Java came along and essentially offered instant portability. It created the JVM and runtime environment that would guarantee execution of code, regardless of platform, as long as there was a JRE. In one swoop, Java took away the problem of portability from developers, yay!

Another thing Java did was create a language that was much more readable, easier to learn and way more modular than anything that existed previously. It was not an accident Java became the #1 and has been for so long.

However Java Got Fat...

Just like with anything, if you are #1 you might get complacent and sit on the couch. As Java grew over the years, it got bigger and bigger and bigger. More framework, framework to manage framework, tools to manage tools, don't get me started with Gradle and Maven. It grew and grew into the can do anything language for everything. Often frameworks were used as a crutch, code as a result became sloppy, technical debt grew, scalability was not by design and then came cloud. Things started breaking, surprise.

Java has since, been trying to lose some weight, springboot and other so-called lightweight frameworks came along. By lightweight we still mean overweight and slow from microservice perspective, just maybe not fat. Microservices did indeed change the game and possibly, could be the trigger that ends Java's reign.

Microservices Change the Rules

With the introduction of not only microservices, but also container platforms to run microservices, the rules have changed, just like in the 90s when Java was born, under the open systems initiative.

  • Containers means compiling code and portability is no longer an issue. The value of a JVM is much less than it once was.
  • Startup times and time to load code is critical in microservices. If your code is slow to load it really hampers your options and presents a lot of challenges.
  • Microservices are mostly REST APIs that exchange data, asynchronous operations are key.
  • Microservices require scalability and scalability generally requires concurrency.
  • Languages that are compact, script-like, easy to write and read are preferred.
  • Languages with simple dependency management are ideal.

At the time when Java was created none of these rules applied, it was developed to meet a totally different set of goals and challenges. Yet many Java developers go along on their merry way, trying to port their monoliths to microservices, using the same mentality that got them there? This just isn't logical. If the rules changed then clearly something else would be a wiser choice that adapts to the new game better?

Thankfully there are some interesting choices. One is Golang and the other could be Quarkus. Both attempt to adhere to the new rules. Quarkus is native statically compiled Java and uses an ultra-fast JVM called Graal. It also has built-in frameworks for building microservices that help scale your code such as vertx. In this aspect it is similar to Golang. It is very lightweight and fast. I haven't seen comparison to Golang but it should be similar in this regard. Where things start to look different though is when we get into concurrency.

Doing concurrency and scalability in Java is really hard and Java allows you to write bad code, there is a lot of it. Quarkus has a much tighter opinion and narrow set of tooling so that should help steer developers better than its predecessor. Asynchronous programming is also not that easy in Java, again depends on frameworks but it isn't baked-in by any means. In Golang concurrency is a feature, it is built-in and that alone should get your attention. In addition Golang is a super modern language, easy to write, easy to read, no fluff and dependency management is not stabbing your eyes out with an ice pick.

Good old Java of course can still provide value and does in microservices, I am just saying it is likely not the best choice if you are starting from scratch. If I could sum it up I would say, look into Quarkus if the goal is porting Java app to microservice or building Java microservices and otherwise Golang. Of course there are many other languages as well, but none of them in my opinion apply to the challenges of today as well as Golang or Quarkus.

Getting Started with Golang

All the code examples are available in the following Github repository: https://github.com/ktenzer/go-hello-world.

Setup your environment

$ vi ~/.bashrc

# User specific aliases and functions
export GOPATH=/home/ktenzer/go
export GOBIN=/home/ktenzer
PATH=$PATH:$GOBIN

Create go directory

$ mkdir -p /home/ktenzer/go/src

Install Go

$ sudo dnfs install -y go

Optionally you can install manually as well https://golang.org/doc/install

Clone Git Repo

$ git clone <repo url> /home/ktenzer/go/src

Create src and vendor directories in repo

$ mkdir -p /home/ktenzer/go/src/<repo>/src
$ mkdir -p /home/ktenzer/go/src/<repo>/vendor

Install Dependency Management Tool

You will want to use other frameworks and tools. The dependency management tool I would recommend is called dep.

$ curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh

Initialize Dep

$ cd /home/ktenzer/go/src/<repo>
$ dep init

Add dependencies

$ dep ensure --add github.com/gorilla/mux

This will add the dependency and version to Gopkg.toml file automatically.

[[constraint]]
name = "github.com/gorilla/mux"
version = "1.7.2"

Creating First Microservice

As mentioned concurrency is built-in, one of the great advantages of Golang for microservices. As such the net/http module already has concurrency, no extra code needed. The only thing that needs to be deciding upon is what to you for URL routing? There are several Frameworks: Gorilla, Gin, Chi, etc. There are of course lots of pros and cons. Some route URLs faster while others have more capabilities. Personally I like Gorilla, it isn't the fastest but I like it's utility.

In this microservice we will run a concurrent http service that responds to a status API route with a message and a version. We will output JSON.

Main.go

In the main.go we will simply intantiate a new URL router and configure http service to listen on port 8000 and implement our router.

package main


import (

    "log"

    "net/http"
)

func main() {

    router := NewRouter()

    log.Fatal(http.ListenAndServe(":8000", router))
}

Routes.go

In the routes we obviously configure API endpoints and routing. Here we have a single endpoint "/status". Calls sent to the /status endpoint will be handled or routed to our GetStatusEndpoint function.

In addition we create a new router object with our configuration. Each route incoming route is processed, checked if it exists and if so the route is logged and a handler function is called.

package main

import (
    "github.com/gorilla/mux"
    "net/http"
)

type Route struct {
    Name string
    Method string
    Pattern string
    HandlerFunc http.HandlerFunc

}

type Routes []Route
func NewRouter() *mux.Router {
    router := mux.NewRouter().StrictSlash(true)
    for _, route := range routes {
        var handler http.Handler
        handler = route.HandlerFunc
        handler = LogApi(handler, route.Name)
        router.
            Methods(route.Method).
            Path(route.Pattern).
            Name(route.Name).
            Handler(handler)
    }

    return router

}

var routes = Routes{
    Route{
        "GetStatusEndpoint",
        "GET",
        "/status",
        GetStatusEndpoint,
    },
}

Handlers.go

The handers are where we want to implement our API. In this case we just set our status struct and return it as json using the json encoder.

package main

import (
    "encoding/json"
    "net/http"
)

func GetStatusEndpoint(w http.ResponseWriter, r *http.Request) {
    var status Status
    status.Msg = "Hello World"
    status.Version = "1.0.0"
    json.NewEncoder(w).Encode(status)
}

Logger.go

Each incoming URL is logged, this is called in the routes.go, this is just the implementation for that function.

package main
import (
    "log"
    "net/http"
    "time"
)

func LogApi(handler http.Handler, apiRoute string) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        startTime := time.Now()
        handler.ServeHTTP(w, r)
        log.Printf(
            "%s\t%s\t%s\t%s",
            r.Method,
            r.RequestURI,
            apiRoute,
            time.Since(startTime),
        )
    })
}

Status.go

A struct to store status of service, nothing more to say.

package main

type Status struct {
    Msg string `json:"msg"`
    Version string `json:"version"`
}

Compiling the code

Compiling code in Golang is really easy, just run go.

$ go install go-hello-world/src/hello

The binary will be called hello and exist in the $GOBIN directory, in this case /home/ktenzer.

Running the code

Since Golang produces a binary you simply execute it.

$ /home/ktenzer/hello

Using curl we can hit our /status API endpoint.

$ curl http://localhost:8080/status
{"msg":"Hello World","version":"1.0.0"}

Check hello service

Since we log API requests you should see a message printed to stdout.

$ /home/ktenzer/hello
2019/06/13 13:13:11 GET /status GetStatusEndpoint 58.842µs

Also very interesting is the time the API took to process, in this case 58.842 micro-seconds. As mentioned Gorilla isn't one of the faster URL routers either.

Summary

In this article we have discussed some of the history of Java. We talked about how the rules are changing for microservices, why Golang or Quarkus may be a better choice than standard Java for concurrent, asynchronous microservices and both could very likely become languages of choice in the microservice space. Finally we went through a hello-world implementation of a microservice written in Golang. This should give you a good idea of how to get started using Golang. Feedback is always welcome.

(c) 2019 Keith Tenzer