I started using empty structs as type parameters so I could avoid reflection:
router.HandleFunc("/api/user/add", makePostHandler(&User{}))
router.HandleFunc("/api/group/add", makePostHandler(&Group{}))
…where makePostHandler took any empty struct of type fulfilling Persistable, and returned a function closure which called methods on the empty struct to perform the work. The Persistable interface was made up of sub-interfaces defining methods to decode an object, validate it, insert it into the database, and so on – the steps the handler needed to take.
Then I thought, well, why not use a similar technique to implement Map operations and other higher-order functions? See this article for how this applies to sorting too. I added some more methods to the interface and implemented them in my data types. Before long I was using Map with a function argument to generate views of objects on the web or dump objects as JSON via the API, again without needing to write the same code multiple times.
The problem is, while I now only had to write my handler functions once, I’d added a whole lot of complexity to every data type ever touched by this technique. All my model data structures gradually had to satisfy more and more interfaces so that they could be passed around the codebase.
What I really wanted was a way to be able to say (of a method argument) that it had to satisfy two interfaces. But an argument could only have one interface type, so I started trying to break things up using interface composition – I’d have a Mappable interface, a Persistable interface, and a MappablePersistable interface for things which had to satisfy both. Next, I needed different PUT/POST behavior depending on whether the API was supposed to INSERT or UPSERT, so I needed PersistableUpserter, PersistableInserter, MappablePersistableUpserter, and so on… and then I realized that this was madness.
So, that was the clever thing to do to save writing half a dozen simple handler functions, that turned out to be a bit of a disaster. I could have started using interface{}
all over the place, but I really wanted to keep type safety.
After reading around some more I arrived at a description of using the data mapper pattern in Go. You keep your core objects as simple as possible – just a struct with some fields – and build a UserMapper
, GroupMapper
and so on to handle HTTP and database stuff. You have to write them, but it’s mostly boilerplate code, and you only ever have to write the ones you’re actually going to use. You also now have to write handlers for PostUser
, PostGroup
and so on, and it’s a bit repetitive, but again they’re only a few lines long and all standard stuff.
The big win is that the User code doesn’t have to know anything about HTTP methods or anything about databases, and the Handler code doesn’t need to know anything about User objects or databases. The Mapper links the worlds of HTTP and database, and insulates the two areas of concern from each other. If you need to change the INSERT behavior or payload encoding for a particular type of object, it’s all in one file and you can change it without the risk of breaking how other objects behave.
As for higher order functions, I just wrote the code twice for the two specific types I really needed to be able to Map on.
Note that there are totally valid cases where empty structs as type parameters, HTTP handler generation, and higher order functions are the best solutions for the problem. However, I’d categorize them all as clever code, and one thing I’ve learned over the years is that it’s best to avoid clever code unless you really need it, even if it’s shorter.