Even though Go has only been a language since 2009, it has quickly become one of the leading languages to write web applications with.
This is partially due because Go was modeled for distributed computing. Unlike other languages which support concurrency through the standard library or some add-on module, Go’s concurrency is built-in to the language itself.
While writing web applications is fairly easy to do in Go (thanks in part to its marvelous concurrency and small, easy to grasp language) it occasionally causes some issues you don’t find in traditional web-application languages such as PHP or Python.
Go is, in the traditional sense, compiled. Much like C, you have to compile the
.go files into a binary before you can run the application. Unfortunately, this causes complications when you want to add or modify running code.
While writing a web app for Sermo Digital, I wanted to be able to modify a template file and see the changes live without recompiling my binaries. In most languages it’d be easy enough—simply modify the file and the next time the program reads the file you’ll have the updated version. Super simple.
However, with Go that’s not possible without (somehow) gracefully shutting down the server, recompiling the binary, and restarting the server… Or so I thought.
Go provides a lot of systems support; that is, you have access to a lot of the same APIs that you would in a typical systems programming language like C. One of these is inotify.
Per man7, “The inotify API provides a mechanism for monitoring filesystem events… individual files, or to monitor directories.” Keep that in mind.
In most Go web apps, templates are used to render specific views. For example, a template for a blog post might look like this:
However, this quickly becomes hard to maintain. Do all the template text variables have the same naming scheme? Do you repeat logic? Do you know in which files the variables reside? (That last one is key if you separate your app logic into different files/directories. Having run
ag 'const postTemplateText' to find the variable gets old quick. Also, ag.)
The answer to this problem is to use maps.
So, instead of this:
you end up with this
which is much more maintainable.
Interestingly enough, this approach also opens the door to automagically hot-reloading template files.
Back to inotify. It just so happens that Go provides support for creating templates from physical files in the same way that it does string variables. Putting two and two together, we can use inotify to alert our binary when we’ve altered a template file and have the binary slurp the modified file up into memory, replacing the old template.
Let’s start from the top. First, we need to declare our map that’ll hold our template files. However, since we’re modifying map values while we’re reading them, we need to add a mutex lock to protect our map. Race conditions suck, and they’re prone to sneakily corrupting your data without you knowing.
Simple enough. Now let’s add methods to retrieve our templates.
Great. Now we have a worry-free method of retrieving our templates. We could modify that a bit more to remove the costly defer, but for the sake of this simple example let’s not.
Now let’s add inotify in so we can keep track of file changes.
Boom. Now that we have a way to find out when the physical files are modified, let’s add the actual reload function. For brevity’s sake I’ll stick to only writing out the additions and modifications from now on.
I mentioned “base” files in the snippet above. What I meant is this:
And that’s essentially it! Short, sweet, and to the point. (While writing this my editor is only at line 255, including all the extra markdown code!)
If you’d like the full source of the code here (sans the doReload method), you can find it in /r/golang or in this Gist.
At Sermo Digital we use a slightly enhanced version of this hot reloading. We use a fixed-size array, have some helper functions that allow maps with functions, and a lot more code that makes using the reloading system a lot nicer. (And faster!)