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.Handler
func 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.

Show your support

Clapping shows how much you appreciated Jacopo Salvestrini’s story.