Test-driving the Go language

When I completed my compiler design studies, I felt it was not appropriate for me to give my exercise projects any fancy names and put them on Github. With this in mind, I was used to question any new language which came out of nowhere and wait for a number of readings on my technology radar before considering to deal with it.

With a number of significant readings from Google Go, I decided to give it a pleasant test drive.

The programming language Go, originally a 20%-Project initiated by a Google employee, quickly found a position in my mind - a slick alternative to C/C++ for smaller systems and fast backend services. Being used to Java/Spring Boot and .net/Nancy, Go could be interesting to me if there is any gap it could fill - maybe a gap I was yet to discover.

To find out, I built a simple echo service which covers some aspects I would usually face on backend services:

  • HTTP Server
  • JSON
  • Commandline Arguments or Environment Variables
  • Logging (ELK friendly, please)
  • Easy to integrate in CI/CD environments

Setting up Go

Setting up go takes 5 minutes on UMTS or 4 hours on a crappy hotel WiFi. Download the package, install, create your workspace and start coding. I used a text editor for writing Go code, afterwards I noticed that there is an awesome set of plugins for Atom.

Right after creating the workspace one thing becomes obvious - Go loves conventions, convention over configuration and implicit declarations. The workspace is organized in a way that seems comfortable to Java developers, because it works perfectly if you organize all your projects and external dependencies in the form of Java namespaces.

Java developers will, on the other hand, not be comfortable with the missing concept of classes and the implicit private/public declarations: function names in camelCase are private, while their PascalCased counterparts are public. The Echo Service

Lets build a simple Echo Service, which just listens to HTTP requests and echoes them back, while writing comprehensive logs and following a set of practices which appear a good idea in Java.

Enter Go..

Main file

 1/*
 2Package EchoService is a simple HTTP based echo service in Go language
 3
 4Compiling
 5go build m9d.de/goworkshop/echoService
 6(optional) go install m9d.de/goworkshop/echoService
 7
 8
 9Start with
10echoService -http=8080
11
12Help
13echoService -h
14
15Usage
16http://host:port?echo=hello
17*/
18package main
19
20import (
21    "flag"
22    "fmt"
23    "log"
24    "net/http"
25
26    "m9d.de/goworkshop/echoService/handlers"
27)
28
29// ApplicationConfiguration represents the Application Configuration
30type ApplicationConfiguration struct {
31    // HTTPPort - Public Listen port
32    HTTPPort int
33}
34
35func buildApplicationConfiguration() ApplicationConfiguration {
36    listenPort := flag.Int("http", -1, "Port on which the REST service should listen")
37    flag.Parse()
38
39    result := ApplicationConfiguration{
40        HTTPPort: *listenPort,
41    }
42
43    log.Print("ApplicationConfiguration=", result)
44
45    return result
46}
47
48func runWebserver() {
49    http.HandleFunc("/", echoServiceHandler.HandleRequest)
50    http.ListenAndServe(":8080", nil)
51}
52
53func setupLogging(applicationConfiguration ApplicationConfiguration) {
54    var loggingPrefix = fmt.Sprintf("%s%d ",
55        "echoService:",
56        applicationConfiguration.HTTPPort)
57    log.SetPrefix(loggingPrefix)
58}
59
60func main() {
61    applicationConfiguration := buildApplicationConfiguration()
62
63    setupLogging(applicationConfiguration)
64
65    if applicationConfiguration.HTTPPort > 0 {
66        log.Print("HTTP Server starting...")
67        go runWebserver()
68        log.Print("HTTP Server up and waiting. Press Enter to quit.")
69        fmt.Scanln()
70    } else {
71        log.Fatal("no listening port specified. Aborting")
72    }
73
74    fmt.Println("Terminating. Goodbye.")
75}

Service Handler

 1// Package echoServiceHandler includes all functionality for a basic HTTP based echo service.
 2package echoServiceHandler
 3
 4import (
 5    "encoding/json"
 6    "fmt"
 7    "log"
 8    "net/http"
 9    "time"
10)
11
12// EchoResponse - return type for an Echo Request
13type EchoResponse struct {
14    IncomingText string    `json:"incomingText"`
15    RequestTime  time.Time `json:"requestTime"`
16    SystemInfo   `json:"systemInfo"`
17}
18
19// SystemInfo - brief information about the system which is included in each echo Response
20type SystemInfo struct {
21    Hostname   string `json:"hostname"`
22    SystemTime string `json:"systemtime"`
23}
24
25func assembleResponse(request *http.Request) EchoResponse {
26    result := EchoResponse{
27        IncomingText: request.URL.Query().Get("echo"),
28        RequestTime:  time.Now(),
29        SystemInfo: SystemInfo{
30            Hostname:   "archimedes",
31            SystemTime: time.Now().Format(time.RFC3339),
32        },
33    }
34    return result
35}
36
37// Helper function for logging Purposes
38// request: incoming request
39func logRequest(request *http.Request, response EchoResponse) {
40    log.Printf("HTTP method=%s remote=%s echo=%s",
41        request.Method,
42        request.RemoteAddr,
43        response.IncomingText)
44}
45
46// HandleRequest handles requests.
47func HandleRequest(responseWriter http.ResponseWriter, request *http.Request) {
48    response := assembleResponse(request)
49    logRequest(request, response)
50
51    jsonResult, error := json.Marshal(response)
52
53    if error != nil {
54        http.Error(responseWriter,
55            error.Error(),
56            http.StatusInternalServerError)
57
58        log.Fatalf("error in handleRequest: %s", error)
59    }
60
61    responseWriter.Header().Set("Content-Type", "application/json")
62
63    fmt.Fprint(responseWriter, string(jsonResult))
64}

Compiling

go build m9d.de/goworkshop/echoService

or, to build to GoHome/bin:

go install m9d.de/goworkshop/echoService

Go generates fat binaries which include all their dependencies, which means that deployment is a little bit easier, regardless of the bigger files. Test Run

./echoService -http=8080


GET http://localhost:8080/?echo=hello
{
    "incomingText" : "hello",
    "requestTime" : "2016-02-16T19:14:51.3172385+02:00",
    "systemInfo" : {
        "hostname" : "archimedes",
        "systemtime" : "2016-02-16T19:14:51+02:00"
    }
}

Summary

Developers acquainted with Java and C++ should meet many familiar concepts, with a strong tendence to devops and convention over configuration, combined with a very opiniated Gopher who rather refuses to compile source which he does not like. To me, Go looks more than promising. I will definitively consider it as an alternative to C in my next projects which requires a small and slick microservice backend for tasks I can quickly replace with an alternative implementation.

5 Minutes