At Lincoln Loop, we have been building large web applications using Django since 2007. Recently, however, we have recently started using Go as a critical network component of Botbot.me.

Reading others' successes stories about replacing some existing components of their infrastructure with a new incarnation written in Go (Disqus and Iron.io gave me the motivation to build a webapp that is a bit more complex than the canonical "hello world" one page app.

The result of this experiment is called gowebexp and is available on BitBucket. Here are a few things I learned in the process.

Go is a modern language with an amazingly deep standard libraries given its age. It comes with everything you need to build a webapp:

  • template language
  • full featured webserver
  • HTTP request multiplexer
  • cookies
  • ...

However it is a totally different beast than something like "Django":htpp://djangoproject.com/. Go standard libraries tend to be very low level and, as of now, I am not aware of any full-featured framework than can compare with Django in terms of ease of use, features, and documentation.

For example, here's a few things that you take for granted when you use Django that need to be handled
manually:

  • form validation
  • Cross site request forgery (CSRF) protection
  • ...

When building a webapp that goes beyond a single page, you'll quickly notice the difference and very quickly find yourself in a situation where you really miss your familiar tools. In the Go ecosystem Gorilla is a popular choice for building on the web. It is a collection of packages that you can assemble as your need grows. Gowebexp imports 4 packages from the Gorilla toolkit:

  • github.com/gorilla/context
  • github.com/gorilla/mux
  • github.com/gorilla/securecookie
  • github.com/gorilla/sessions

The names are mostly self explanatory. github.com/gorilla/mux implements a request router and dispatcher. From its documentation:

The name mux stands for "HTTP request multiplexer". Like the standard http.ServeMux, mux.Router matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions.

mux probably overkill for my simple experiment but I wanted to work with tool that I feel confident to use for something bigger.

Getting started with gowebexp

The README provides a step-by-step procedure on how to install gowebexp .

Coming from the Python land this is an area where Go really shines. The tooling around the language is fantastic and built-in. Go comes with a command to get a package and all its dependencies named go get similar to pip install .

Installing and downloading all the dependencies is as simple as:

go get bitbucket.org/yml/gowebexp

You can install and compile it using:

go install bitbucket.org/yml/gowebexp

Since we are talking about tooling, Go also comes with a test runner:

go test bitbucket.org/yml/gowebexp/...

There are several others that I encourage you to check out:

  • go fmt ...
  • go vet ...
  • go doc ...

Anatomy of gowebexp

It is built around 3 components in 2 packages:

  • A command to start the web server
  • A page package that describes the object we are managing in gowebexp : Page
  • a web package that contains the logic requires to deliver a Page

gowebexp uses a singleton pattern to create an App instance that stores all the information that we pass around.

This is very useful to provide information inside the function handlers because they only take two arguments http.ResponseWriter and http.Request . These functions build the response that is returned to the browser. Quickly after the joy of responding "hello world" you will feel the need to have more things available.

I found that I needed a way to access my router, storages, templates or cookie store. This App singleton is initialized when the web package is imported so it is always available. It is based on the following struct that can be extended further:

type WebApp struct {
        Router      *mux.Router
        Storage     Storage
        StaticDir   string
        TemplateDir string
        Templates   map[string]*template.Template
        CookieStore *sessions.CookieStore
}

Sample view: PageList

The terminology I'm using here should be familiar to Django users. In a Model, View, Template paradigm, the View is responsible for collecting all the information that will be rendered in a Template. The example below displays the list of pages and creates a new one:

func PageList(w http.ResponseWriter, r *http.Request) {
        ctx := make(map[string]interface{})
        ctx["csrf_token"] = context.Get(r, "csrf_token")
        if r.Method == "POST" {
                page := pages.Page{
                        Name:    r.FormValue("name"),
                        Slug:    r.FormValue("slug"),
                        Content: r.FormValue("content")}

                validationErrors, err := page.Validate()
                if err != nil {
                        ctx["validationErrors"] = validationErrors
                } else {
                        App.Storage.AddPage(page)
                        // Redirect to / after the creation of the page
                        http.Redirect(w, r, "/pages/", 302)
                }
        }

        tmpl := App.Templates["page_list.html"]
        ctx["storage"] = App.Storage
        err := tmpl.ExecuteTemplate(w, "base", ctx)
        if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
        }
}

The overall control flow is very similar to what you find in a Django
functional view, but there are two things that might require some explanation:

  • ctx is a map[string]interface{} . Go, being a statically typed language, requires a bit of ceremony to build a map of anything. The closest thing available in Go is an empty interface interface{} . ctx lets us push anything in the template.
  • The second interesting thing here is page.Validate() . This method returns a map[string]string that contains the validation errors. Unlike Django's ModelForm you on your own to implement the exact logic you want to support.

A word about performance

I know people either love or hate micro-benchmarks, so I will be very short on this and point you to this series of benchmarks. It compares languages and frameworks in a careful and repeatable way.

In a nutshell Go is FAST. Below is a result from an Apache bench run
this will give you an idea of the order of magnitude we are talking about.

yml@garfield$  ab -n 10000 -c 300 http://127.0.0.1:8080/pages/
[...]
Concurrency Level:      300
Time taken for tests:   4.330 seconds
Complete requests:      10000
Failed requests:        7420
   (Connect: 0, Receive: 0, Length: 7420, Exceptions: 0)
Write errors:           0
Total transferred:      26985096 bytes
HTML transferred:       21485096 bytes
Requests per second:    2309.63 [#/sec] (mean)
Time per request:       129.891 [ms] (mean)
Time per request:       0.433 [ms] (mean, across all concurrent requests)
Transfer rate:          6086.48 [Kbytes/sec] received
[...]

Conclusion

Writing a simple webapp in Go is much more work than Django or Ruby on Rails developers have grown accustomed to. The tools available in the standard libraries are very low level. Happily you will find packages in the ecosystem to limit the number of wheels you have to reinvent.

On one hand, the best practices to write a large webapp in go are not as formalized as are in the Django world. Looking through Github, BitBucket and co, I noticed the coding styles and patterns are still very diverse and not always for the best. I have probably done a ton of things that could be improved in gowebexp .

On the other hand Go is already a fantastic tool that you can use to ease growing and scaling pain points. Its footprint is so small and it is abile to serve dynamic pages with massive throughput . In addition to this, the deployment story is ridiculously simple. All you need to do is copy a statically linked binary and your templates/static folders. Its simple deployment and fantastic performance will make Go an important tool for scaling large websites in the future.