HN user

zenazn

447 karma
Posts2
Comments14
View on HN

If Go supported method overloading, you could actually write the types of those functions out. The first one is either a string or a regexp.Regexp, and the second one is one of four variations on an http.Handler, giving a total of 8 varieties of each function.

I decided that the sin of exposing an interface{} as a parameter was less egregious than the sin of multiplying Goji's API surface area by a factor of 8, but you'll be happy to know that passing a value of the wrong type causes the invocation of Get (Post, etc.) to fatally exit immediately. If you're defining all your routes in a single goroutine before calling goji.Serve() (which is probably the most common way to define routes), your application will crash before it even binds to the socket.

So, not quite as good as a guarantee enforced at compile time, but it'll have to do.

Huh. I'm not sure I saw that particular project during my search. It looks neat!

Without having looked in detail at httprouter, I think the most obvious reason it might not be sufficient is that it doesn't support regular expressions. This might not be a dealbreaker, but I'm fond of the occasional regex route, and I'd have to think long and hard about whether it's worth giving up for a faster router. And plus, I'm still not sure router speed actually matters for most applications.

In any event, I do have a long plane trip coming up, and I'm sure Goji will grow itself something at least slightly more efficient than a linear scan then. I'm think Goji's router and middleware stack are already zero-allocation, so it'll just be finding a way to binary search through routes.

Yeah, I looked at gocraft/web for a long time before writing Goji. It's a good library, and I think it does a lot of things right. But at the end of the day, just like my disagreement with Martini, this comes down to a difference in principles. I think there are two theories of Go library design here. One of us has chosen one, one the other, and let me prefix this by saying that I'm not sure the way I chose is correct.

On one hand, a library can allow its users access to typed structs—this is the way gocraft/web does it. The upside here is huge: applications are now type-safe, and can lean on the compiler to enforce their own correctness. The library code that backs this behavior can be arbitrarily gnarly, but you only have to write it once, after which it'll Probably Be Correct (tm).

On the other hand, you can provide a standard "universal" context type like Goji does. Since the library makes no statements about the sorts of fields you have or their types, applications have to do a bunch of shenanigans with type casts in order to extract the typed values they expect. The upside here, however, is the standardization and what that allows. I've found that in most of the web applications I've built, only a small fraction of the middleware is authored by me: I end up pulling in the standard request logging middleware, or the middleware that assigns request IDs, or the middleware that does CSRF protection. There's a huge amount of value in being able to write (read: "let someone else write") those middlewares once and being able to use them in any application. With arbitrary application-provided structs, this isn't possible [1], but if you can instead standardize on types and keys out-of-band, you can mix and match arbitrary middleware with impunity.

This alone is one of those awkward engineering tradeoffs where you're exchanging one nice thing (application-level type safety) for another (standard interface, and hopefully an ecosystem of drop-in middleware), and given just this I'd argue that the ecosystem is probably more important. But the frosting on this particular cake is that, with only marginally more awkwardness, you can actually get application-level type safety too: look at what I did for the request ID middleware that ships with Goji, for instance (https://github.com/zenazn/goji/blob/master/web/middleware/re...). Again, it's not quite as nice as a user-defined struct, but in the grand scheme of tradeoffs and sacrifices, I don't think it's so terribly bad.

So TL;DR, Goji chose standardization and composability in the design of its context object over application type safety, but if you're willing to write a few helper functions I don't think you end up losing the type safety anyways.

[1]: actually, come to think of it, with a lot of reflection and some struct tags you could probably pull it off. (Something like "struct { RequestID string `context:"reqid"`}", and the request ID assigning middleware would hunt around for a string field tagged "reqid"). But it seems like it'd introduce a lot of coupling between middleware and the application which I'm not sure will turn out well—any time a middleware needed a bit of extra storage, every application would have to change. In any event, if someone builds this I'd love to play around with it.

This was not the feedback I was expecting, but thank you :)

It was based on my terminal color scheme (named "Solarized Darcula"—not sure where I found it) and the way vim happens to color my Go code.

Actually, Goji grew out of a single deficiency in "pat": the fact that it does not have a standard way of defining request context.

The big use case here is how you'd write a middleware that did authentication (using API keys, session cookies, ???) and emitted a username for other middleware to consume. With net/http, you end up with a lot of coupling: your end handler needs to know about every layer of middleware above it, and you start losing a lot of the benefit of having middleware in the first place. With an explicit middleware stack and a universal interface for middleware contexts, this is easy: everyone can code to the same single context object, and instead of standardizing on weird bound variables (or a global locked map a la gorilla), you just need to standardize on a single string key and a type.

I think my ideal world would involve Go providing a map[string]interface{} as part of the http.Request struct in order to implement this behavior, but until we get that, I think Goji's web.C ("the context object") is the next best thing.

There's one other thing pat hacks around: the issue of how to pass bound URL variables to the resulting handler. At first I was a little grossed out at how pat did it, but I've sort of come to terms with it. I still think Goji's way is better, but I don't think it's the reason I wrote (or a reason to use) Goji.

To be perfectly honest, I'm not sure it is better (I was hoping you would help me decide that!), and I wrote it mostly because every aspiring programmer writes a web framework at some point, and it was time I wrote mine (it was a lot of fun :) ).

But I think there's a good chance it is better.

First, I think one important difference is that Goji isn't full of magical reflection. If Go had support for method overloading, its entire interface is type-safe. In contrast, Martini does a lot of magical object injection, and it's not clear until runtime if your routes will even work, or what they'll even do, or where exactly the memory for them is coming from.

Second, I much prefer Goji's way of defining middleware. To me, middleware is like an onion (just like ogres!): each layer is a wrapper around the previous one. The way you write middleware in net/http is by wrapping the old http.Handler with a new one, and that's how I wanted Goji's middleware to work too. There's no magic "context.Next()", there's no magic dependency injection overrides, it's just http.Handlers all the way down.

Anyways, I'd like to know if you think I'm right: again, I'm really not sure this is actually better than Martini (or $YOUR_FAVORITE_FRAMEWORK), but I think it comes from a slightly different set of principles, and ones that I think are worth considering.

Blackboard isn't all bad—fixing many of its inadequacies was one of my first big side projects in high school.

There was one Blackboard dashboard unit that allowed you to embed arbitrary HTML, and I injected some JS onto the page that allowed you to set a background image, made clicking on links pop open new tabs (instead of whatever abomination of javascript they had), and all kinds of other little tweaks. I even got most of the way through writing a drag-and-drop module rearranger before I graduated. Maybe 60% of what I knew about JS and CSS at the time I learned through trying to add features to Blackboard through script injection.

Naturally, I wasn't the only one frustrated at Blackboard, and the script travelled by word-of-mouth to a pretty sizable chunk of the school. At that point, it was probably the most widely-used thing I'd ever built.

Harvard is seeing a very similar trend. Our intro to CS course, CS 50 [1], had an enrollment of over 600 this Fall (up about 20%), making it one of the largest classes at the College [2]. Even its teaching staff, which numbers over 100, is bigger than most classes (and most of them are undergraduates) [3].

The data isn't in for this term yet, but pre-term planning data suggests that enrollment in CS 51 (our second-term CS course) will go up by about 40% (it experienced similar growth last year), and CS 181, a machine learning class, might nearly triple in size since it was last offered [4].

For smaller departments like Harvard's (and Yale's, which is even smaller still), there just aren't enough professors to go around, and with the enormous influx of students, I think the quality and variety of the courses offered has suffered. Many courses are only offered sporadically--especially systems courses--since there simply aren't enough professors to teach them all.

And that's to say nothing of the (undergraduate) teaching assistants, which bear much of the brunt of this trend. I TFed Harvard's algorithms course (which doubled in size over the two years I taught it), and am TFing our operating systems class this term (which if today's turnout was any guide looks like it might nearly triple in size). The workload was considerable, and I'm sorry to say that the feedback and individual attention I was able to provide students has gotten worse and more tardy because of this.

I don't think Harvard (and Yale, etc.) can cope with this sort of explosive growth for too much longer.

[1] https://www.cs50.net

[2] http://www.thecrimson.com/article/2012/4/13/cs-greatest-grow...

[3] https://www.cs50.net/staff/

[4] http://www.thecrimson.com/article/2012/12/17/ptp-data-obtain...