Resource-ful Handlers
Joshua HawxwellIt seems that most routers for web apps provide an API that gives equal importance to the path and the method, which in my opinion is totally wrong. A path names a resource whereas the method is an action you can (possibly) perform on that resource.
Routing should happen in layers:
- At the top level you are finding the correct resource for the request.
- Then work out the action to perform;
GET
= retrieve,POST
= create, etc. - Finally content negotiation using the
Content-Type
andAccept-*
headers.
The third layer is not always needed, but the first two generally are. But most of the time they are defined as if they are equals.
Previously I was writing my applications with routing like,
booksHandler := handlers.Books(db, es)
r.Path("/books/{id}").Methods("GET").Handler(persona.Protect(booksHandler.Get))
r.Path("/books/{id}").Methods("PATCH").Handler(persona.Protect(booksHandler.Update))
r.Path("/books/{id}").Methods("DELETE").Handler(persona.Protect(booksHandler.Delete))
A struct is created with different fields for the handlers which should be used
for the various http methods. But there is no reason for the top-level routing
layer to know about whether this supports DELETE
, it should simply say
/books/{id}
is a book.
Contrast with the current version:
route.Handle("/books/:id", persona.Protect(handlers.Books(db, es)))
Now the different actions that can be made on a book are defined in a separate handler:
func Books(db data.Db, es *events.Source) http.Handler {
h := booksHandler{db, es}
return mux.Method{
"GET": h.Get(),
"PATCH": h.Update(),
"DELETE": h.Delete(),
}
}
This uses my mux library which defines some simple map[string]http.Handler
types that implement http.Handler
. It now acts like a resource, so the router
is left the simple job of mapping request paths to resources.