Go – from playing to production

7 min read
gogolang

Proposition

  • There are so many new languages popping up that it's hard to keep up with all of them.
  • It's nice to be able to quickly evaluate a language by prototyping a small project or two without investing too much time learning it.
  • Accustomisation to a certain setup/workflow; for example, being able to describe and install dependencies (like Ruby's Gemfile for Bundler)
  • Start simple, evolve to production.

If the above holds for you then here's a practical guide on how to get started with Go.

Warning: this is an opinionated approach.

1. Play

Play web interface offers the fastest way to experiment with Go. It lets you run commands and save your gist for sharing and later use. Useful for both beginners and experienced users.

Exercises

2. Tour

Tour walks you through exercises describing facets of Go. Offers solid starting point for a learner.

Exercises

  • complete Tour   (at your convenience)

3. go run

Getting the language running on your machine is the next step once the above options are no longer satisfying.

Given the content of hello_world.go:

// hello_world.go
package main
import "fmt"
func main() {
  fmt.Println("Hello go world!")
}

Continue with exercises, but before that make sure to clone the example repo

Exercises

  • install Go
  • export GOPATH=~/golang && export PATH=$PATH:$GOPATH/bin && mkdir $GOPATH set up required environment
  • cd 03-go-run/ (code)
  • run go run hello_world.go to get the "hello go world" output
  • run go fmt hello_world.go to auto-format the code

4. go test

Tests help evolve code and ensure it works. So in order to test that the hello string is generated properly let's refactor the example and add a test.

package main

import "fmt"

func main() {
    fmt.Println(hello("go"))
}

func hello(one string) string {
    return fmt.Sprintf("hello %s world", one)
}

and test

package main

import "testing"

func TestHello(t *testing.T) {
    exp := "hello go world"
    got := hello("Go")

    if exp != got {
        t.Errorf("\nExp: %s\nGot: %s", exp, got)
    }
}

Exercises

  • cd 04-go-test (code)
  • go fmt both files
  • run go test, notice the failing test
  • do not fix the test yet

Failing test output:

--- FAIL: TestHello (0.00s)
        hello_world_test.go:11:
                Exp: hello go world
                Got: hello Go world
FAIL

5. Continuous testing and Makefile

Makefile lets you describe automation for routine tasks and save some typing. In this case we want to have project rebuilt every time a file changes.

test:
    go test

re-test:
    reflex -r '\.go' $(MAKE) test

dev-deps:
    go get github.com/cespare/reflex

Exercise:

  • cd 05-ContinuousTestingAndMakefile (code)
  • run make dev-deps to install development dependencies
  • run make re-test and watch the test fail
  • fix the failing test and see it PASS immediately!

6. Packages, Project Layout

To demonstrate use of packages and basic project layout, our helloworld will be converted into a library.

NOTE: Currently there's no official and convenient way to manage dependencies. Gb was chosen as a tool to govern project layout, building and handling dependencies.

New layout now looks like this:

tree
.
├── Makefile
├── bin
│   ├── helloworld
│   └── helloworld-server
└── src
    ├── cmd
    │   ├── helloworld
    │   │   └── main.go
    │   └── helloworld-server
    │       └── main.go
    └── helloworld
        ├── hello_world.go
        └── hello_world_test.go

Changes:

  • the hello function has been renamed to Hello to be exported and accessible from outside the package
  • helloworld is now a package (note package helloworld instead package main in src/helloworld/*.go)
  • there are now separate "entry points" (or executables) source of which located under src/cmd
  • Makefile now has a build rule that builds the entry points/executables to bin directory
  • Gb is our new development dependency

Exercise:

  • cd 06-PackagesAndProjectLayout (code)
  • run make dev-deps to install new dependencies
  • run make re-test, notice failing test
  • fix the failing test
  • run make build to get binaries built
  • run bin/helloworld ensure it prints hello go world
  • run bin/helloworld-server and point your browser to http://localhost:8080/?name=Go to get a web "hello Go world"

7. Dependencies

Let's now add a dependency and describe the process. In this example we're going to use httprouter to add a nicer route for our web server hello example.

Adding a dependency with Gb is running a command:

gb vendor fetch github.com/julienschmidt/httprouter

As result we now have vendor/ directory that looks like this:

tree -L 4 vendor/
vendor/
├── manifest
└── src
    └── github.com
        └── julienschmidt
            └── httprouter

where vendor/manifest is the file which tracks dependencies added with gb vendor fetch command. Check vendor/manifest into your SCM to be able to retrieve dependencies later with gb vendor restore (without having to check them in to SCM).

Now that that dependency is ready to be used let's update the server code to look like:

package main

import (
    "fmt"
    "helloworld"
    "net/http"

    "github.com/julienschmidt/httprouter"
)

func main() {
    router := httprouter.New()
    router.GET("/", index)
    router.GET("/:name", index)

    http.ListenAndServe(":8080", router)
}

func index(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  name := ps.ByName("name")
  if name = "" {
    name = "Unknown"
  }
    fmt.Fprint(w, helloworld.Hello(name))
}

Changes:

  • import section now includes vendored dependency "github.com/julienschmidt/httprouter"
  • new router router := httprouter.New()
  • new route with named parameter /:name
  • index function signature change(as required by the router API)

Exercise:

  • re-build and run bin/helloworld-server
  • point your browser to http://localhost:8080/Go and notice "Hello Go World" response
  • add a Makefile rule that builds and runs the server
  • add a Makefile rule that re-builds server and runs on a file change
  • check-in vendor/manifest file to your SCM
  • write Makefile rule to restore dependencies

8. Deployment

Deploys are just copying the binary to a server and launching it once it's built for the platform it's going to be running on. Building could be done through cross-compilation or on a build machine.

But it's left as an exercise to the reader ;)

References

Thanks for reading and happy birthday to Go!