HN user

cypriss

109 karma
Posts2
Comments10
View on HN

I agree that 'context' and passing this context between middleware is the biggest missing item from the standard http+"pat".

This library (Goji) solves it with a map[string]interface{}. This works, but has the downside that you need to type-assert your values from this map if you want to use them.

I wrote a library a while ago (http://www.github.com/gocraft/web) that uses reflection so that your contexts can be strongly typed. Martini offers a similar approach.

As another commenter mentioned, I learned about Martini midway through writing this.

I really like Martini - if it existed a bit earlier, I probably would have just used it and been happy.

That being said, there are some differences between the packages. First, Martini has great plugin support because 3rd parties can inject their arbitrary type into handlers. On the other hand, gocraft/web has nested routers and middleware, which are really important for my own projects.

This may be temporary, but gocraft/web also has faster performance because it does less reflection, and because it's router is O(log(N)) instead of O(N). (See https://github.com/cypriss/golang-mux-benchmark , especially as you add more routes).

Finally, I prefer a more consistent signature to my handlers. Martini optimizes for flexibility. And depending on how much you want to inject into your handlers, your argument list can get really long.

Overall, I think Martini is absolutely great and think it's one of the best Go web libs out there.

I believe this is solved with gocraft/web: https://github.com/gocraft/web

The middleware would be, per the example there:

  func (c *YourContext) UserRequired(rw web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
      user := userFromSession(r)  // Pretend like this is defined. It reads a session cookie and returns a *User or nil.
      if user != nil {
          c.User = user
          next(rw, r)
      } else {
          rw.Header().Set("Location", "/")
          rw.WriteHeader(http.StatusMovedPermanently)
          // do NOT call next()
      }
  }
Compatibility with Handler/HandlerFunc is kept at the top level, but not at every step along the middleware chain.

It seems like what you've done there is create two mutations (signup and delete_account) within a single namespace. You can certainly do that with the Mutations gem.

What I think you're getting at is: can you just replace controllers with mutations? That's an interesting concept that I've thought a bit about. In practice, we have found controllers to still be useful in collecting some information and doing 'wiring'. They're also nice because your SOA shouldn't really be concerned with the presentation of the response.

Finally, where do you put your mailings? You're right that controllers > models, but I'd also say that mutations > controllers > models.

We're using this in Rails 2.3 without Strong Parameters. Mutations whitelist input just like strong parameters do. I don't really see why you would use both, although you certainly could.

The duplicate of validations between models and mutations is interesting. Currently at UV, we have some duplication -- however, in other cases we're relying entirely on the validations of the Mutation. I do think it's possible to rely entirely on validation from mutations -- I need to get some more real-world experience with this to know how well it will work out :)

We use Mailgun at UserVoice.com for incoming mail.

Of particular help to us:

- All emails are converted to UTF-8

- There's always a 'plain' (text only) version of the email sent to us, even if the original email only included HTML.

- They offer a 'stripped-text' field, which lets us easily see the new part of an email thread.

- Pretty great support

This type of programming is incredibly useful when writing efficient code in low level languages (eg, linear algebra in C). When writing Ruby web apps, though, I haven't found a need yet to think about cache lines or associativity.