Our middleware system sucks #10
Labels
No Label
admins only
bug
design
duplicate
gimme feedback
good first issue
hmmmm
invalid
reference
wontfix
No Milestone
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Blocks
Reference: hmn/hmn#10
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Our middleware system is bad and I don't like it.
So, when we were developing the site, we were dissatisfied with the approaches to "middleware" that we have seen in other web frameworks. I've attached a document that I wrote during development that contrasts two major approaches (neither of which is used purely in the wild). The fundamental problem, as I see it, is that the behaviors people put in "middleware" are torn between two major use cases:
The most popular design for middleware these days is a "composition" approach where you have a generic "handler" function signature, and everything is achieved by wrapping handlers inside handlers. A pure function composition approach might look like:
I'm not really sure how much I like explicitly writing out function composition in this way. It's accurate to how things are handled, though. I feel like this doesn't do a great job expressing the "do things in sequence" nature of many middlewares, but things like the
timingMiddleware
obviously need to wrap the entire next handler.Other frameworks typically allow you to provide these handler functions as a list instead, and the framework essentially converts the list to function composition:
However, this results in unclear control flow and stack traces that I really do not like:
This indirect function composition makes debugging more difficult and lots of things more cumbersome, and I don't want to do it.
We explored the idea of all middlewares being a simple list, e.g.
but this clearly splits up many middlewares in a really awkward way and generally sucks.
So - is there a way to make the composition approach work for us? We currently use something like the example I gave, except that we attempted to tame some of the seemingly unnecessary nesting by making middlewares that simply call a sequence of utility functions. This has turned out to suck major ass:
a147cfa325/src/website/routes.go (L294)
This now uses a simple composition approach. My concerns about confusing stack traces were unfounded, this handles the majority of middleware cases just fine, and more advanced cases can always just process handlers differently. No major downsides that I see right now.
I guess the common practices in the industry were pretty good this time.