Golang is gaining a lot of momentum. I don’t think a day goes by without hearing that another company has switched from some other language or platform to go. In my last post I mentioned that we chose node even though we were pretty fond of go during our research. We felt the web development community in go specifically needed some maturity. The web development community seems like a bunch of isolated islands with 1 or 2 inhabitants all shouting to the world about how awesome their island is. I almost fell into the same trap myself as you can see. What I created was technically quite nice and performed better than anything else we found, but we would be swimming upstream for everything we needed. This is something we have already done in the past as most of us have been active maintainers and contributors of other OSS projects. However, we knew it would be a recipe for eventual burnout. Without just throwing in the towel I thought I would try and express my concerns and see if others feel the same way and whether or not there is merit to cleaning up the mess as a community and not just on a single isolated island.
One of the primary principles for us in any approach is that testing is easy, both unit testing as well as integration testing. We want the fastest feedback possible. You might be tempted to only do integration tests due to how fast things typically run. However, if an integration test fails it can sometimes be difficult to find out where the failure is. If a unit test can provide a tighter feedback loop then it has done its job.
Another principle we tend to lean on heavily is that you shouldn’t make runtime decisions for things you already know at startup. A small simple example below. The code shouldn’t have any condition at all for devMode. Instead there could be DevRecovery and LogOnlyRecovery. Why? I would say why have branching logic at runtime when we knew what to do while bootstrapping our application? I think you get the point.
1 2 3 4 5 6 7 8 9 10 11
I’ll talk briefly about a few of the frameworks and approaches we tried out. My goal isn’t to bash any particular project or approach, but rather call out that yes we did see (insert framework here), and felt that they conflicted with our goals and all suffer from the same underlying problem.
Revel was one of the first frameworks we looked at and until we knew better, it had a built-in testing module to run tests in the browser. However, after spending some time with golang and realizing how good the default testing tools are the approach seemed confusing and unnecessary. In addition, it seemed that they were intended only for integration tests and not unit tests. We came across a discussion about changing filters / interceptors (revel’s middleware) to get simplified for the project maintainers. In summary, if you wanted middleware applied to a single route or action it required you to add conditional behavior based on the name of the function. This conflicted heavily with one of those principles I mentioned and at this point we were concerned about choosing revel for our project. This is probably a classic example of fitting your code into the mold of a framework, or to bolt on additional behavior through libraries in your own idiomatic way.
Martini also seemed promising initially. After getting into our comparisons though we noticed that it was significantly slower than all the other frameworks we were trying out. Upon seeing other criticisms and an acknowledgement from the author that martini was not idiomatic go we decided to move on. I think the authors response to the criticism was humble and demonstrated something that you don’t see too often in the go community. A willingness to listen to constructive criticism and really hear it. I feel though that the new project he created in response to the criticism took things from one end of the spectrum to the other. I think there’s likely still a middle ground somewhere that hasn’t been discovered yet, but I’m hopeful.
Idiomatic Go – Aka http.Handler
After spending quite a bit of time researching and writing some samples in various frameworks it dawned on me that maybe the go community was right. Maybe you can get anything done with just the standard http.Handler interface. We got a lot of traction and began to realize how nice this could be until we started to try and find existing middleware that we could use. I’m not going to say it’s impossible to find, but it’s pretty scarce. We would find one and get excited until we would realize it’s not compatible with the standard http.Handler interface. Or it would be specific to martini, or negroni, or insert project here.
Why on earth would middleware be taking on dependencies to a specific framework? This is a solved problem in most other communities, heck even Microsoft is getting this right with their latest changes. The problem in my opinion lies in the inflexibility of the http.Request object to facilitate some common use-cases. Take a given route that looks something like ‘customer/:customerId’. That’s easy it goes on req.RouteParameters, but wait it doesn’t exist. Or perhaps when middleware might need to pass information through to another piece of middleware or the handler. That’s easy too, use Gorilla context. Sorry, global state is nasty, not to mention that it encourages a ton of repeating yourself all over the place. The request object seems again like the most logical place to put this code. These are the main reasons that each framework needs it’s own Context struct and why every framework re-implements the same solutions over and over.
One of the best libraries in node in my opinion is passportjs. Sure for the simple basic authentication it might be overkill, but in my experience it’s never that simple. There are typically multiple authentication schemes and sometimes the need for SAML. Authentication and security in general is something you want additional eyes on. If you’ve ever gone through a security audit, you’ll agree that they’re not as easy as you might think. Why should everybody re-implement and go through the same learning experience? Why not move forward together?
Maybe that example is unfair given the age of the community? Okay, let’s take a few common cases. Logging, and panic recovery for example is reimplemented in every lib / framework I ran across. Why not come up with something slick that the whole community can use throughout? What about gzip compression? I don’t think I’ve ran across a comprehensive library for compression yet that didn’t take on a framework dependency, or didn’t have tests, or was an incomplete implementation. This project had the right idea, but without the entire community it’s a lost cause.
Golang has implicit interfaces and is a HUGE advantage to solving this problem, but unfortunately nobody is using them. I propose the community discuss what the standard contract be for all middleware and stick to it. Until the go web development community stops re-implementing the same ideas over and over with a slightly different take on the same problem it’s going to be all upstream or possibly worse.