Fluent Middleware in #golang
A Fluent Evolution of the Adapter Pattern in Go
In “Writing middleware in #golang and how Go makes it so much fun.”, Mat Ryer shows an awesome way to write middleware in Go. It’s awesome because
- it doesn’t break the standard library
http.Handler
interface - it’s idiomatic Go
- it’s beautiful to look at when the middlewares are used
Here we are gonna have more fun with middlewares… and Fluency! This is what we wanna ultimately achieve:
http.Handle("/",
Adapt(indexHandler).With(
Notify(logger), // Outermost Wrapper
CopyMgoSession(db),
CheckAuth(provider),
AddHeader("Server", "Mine"),// Innermost Wrapper
// direct parent of indexHandler
))
But let’s do some steps back first…
Some Historical Background of the Adapter Pattern in Go
Quite often there is beauty not only in the results but also in the process to get there, so let’s briefly add a bit of context by summing up what Mat already explained in his article.
Basic and Ugly
Let’s start by abiding by the principle of not breaking the http.Handler
interface. Here’s a simple wrapper function that returns an http.Handler
and accepts some parameters and another http.Handler
which is wrapped:
func AddHeader(name, value string, h http.Handler) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(headerName, headerValue)
h.ServeHTTP(w, r)
})
}
The above function will be called in this way:
http.Handle("/", AddHeader("Server", "Mine", indexHandler))
And there’s nothing ugly in it … unless we use more wrappers — now, things are getting messy!
http.Handle("/",
Notify(
logger,
CopyMgoSession(
db,
CheckAuth(
provider,
AddHeader(
"Server", "Mine",
indexHandler)))))
The signature of each wrapper is:
func(...interface{}, http.Handler) http.Handler
But that’s not possible in Go — variadic arguments must be the last ones:
func(http.Handler, ...interface{}) http.Handler
So that, more specifically, AddHeader
signature will look like:
func AddHeader(h http.Handler, name, value string) http.Handler
But when calling it with other wrappers it still doesn’t look good:
http.Handle("/",
Notify(
CopyMgoSession(
CheckAuth(
AddHeader(
indexHandler,
"Server", "Mine"),
provider),
db),
logger))
Adding Some Zest
Let’s now take the aforementioned wrapper signature, move out all the arguments (…interface{}
) with the exception of http.Handler
, and make a type out of it; here we have the Adapter pattern:
type Adapter func(http.Handler) http.Handlerfunc Notify(log Log) Adapter {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
defer log.Some("Call end")
log.Some("Call start")
h.ServeHTTP(w, r)
})
}
}
Now everything looks a bit neater when called:
http.Handle("/",
Notify(logger)(
CopyMgoSession(db)(
CheckAuth(provider)(
AddHeader("Server", "Mine")(
indexHandler))))))
And yet, still, it can be improved!
Beauty Kicks In
By adding a helper function all the beautiful middlewares… now look very beautiful!
// This is just awesome - a breath of fresh air in the code!
// Thanks Mat!func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
for _, adapter := range adapters {
h = adapter(h)
}
return h
}http.Handle("/", Adapt(indexHandler,
AddHeader("Server", "Mine"),
CheckAuth(provider),
CopyMgoSession(db),
Notify(logger),
))
From a fluent perspective, looking at the code above where the adapters are called, there are still 2 pitfalls:
- the order of execution of the adapters is the reverse of what is shown
- the difference between the adapted handler (i.e. the simple handler — the one that is executed last and it’s not an adapter — that is wrapped by all the other handlers) and the adapter handlers is implicit (you know it from
Adapt
's signature, and visually because the adapted handler doesn’t have brackets and it’s the first one in the list)
Fluency for Go
Order Kicks In
To show the adapters in the same order as they are executed, we need to reverse the order inside of the Adapt
function:
func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
last := len(adapters) - 1
for i := range adapters {
h = adapters[last-i](h)
}
return h
}http.Handle("/",
Adapt(
indexHandler, // executed last-why is it 1st?!?
Notify(logger), // executed 1st
CopyMgoSession(db), // 2nd
CheckAuth(provider), // 3rd
AddHeader("Server", "Mine"),// 4th, right before indexHandler
))
But that makes the difference between the adapted handler and the adapters even more implicit, as the appearing order reflects the execution order… with the exception of the adapted handler!
Fluency Kicks In
In order to make everything explicit, we can add a new helper function, a new type, and tweak the Adapt
function:
// Now the Adapt func takes care of the adapted handler only
func Adapt(h http.Handler) *AdaptedHandler {
return &AdaptedHandler{h}
}type AdaptedHandler struct {
handler http.Handler
}// The logic of the former Adapt func has been separated
func (a *AdaptedHandler) With(adapters ...Adapter) http.Handler {
h := a.handler
last := len(adapters) - 1
for i := range adapters {
h = adapters[last-i](h)
}
return h
}
And in case we want to adapt a function we can create an AdaptFunc
helper:
func AdaptFunc(f func(http.ResponseWriter, *http.Request))
*AdaptedHandler {
return &AdaptedHandler{http.HandlerFunc(f)}
}
Cool! We now know how to write fluent middleware in Go!!
http.Handle("/",
Adapt(indexHandler).With(
Notify(logger), // Outermost Wrapper
CopyMgoSession(db),
CheckAuth(provider),
AddHeader("Server", "Mine"),// Innermost Wrapper
// direct parent of indexHandler
))
To have a full experience of the adapter pattern evolution, you can use the revision history on my Gist.