REST seems to always be in the way. Am I doing it wrong? What's its value? Is there some perspective I don't have which makes it better?
— Josh Cheek (@josh_cheek) May 5, 2014
A great thing about being a programmer today is the wide variety of libraries, packages, and patterns we have access to. Which is great, because we’re building bigger, more distributed systems as a norm than ever before.
But code is ultimately about communication with other developers (that’s why they are called programming languages). The following three programs output the same thing (credit to Peter Welch for the last two):
puts "Hello World!"
>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-] >++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++ .------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++.
Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. Ook! Ook. Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. Ook. Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. Ook! Ook. Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook. Ook? Ook. Ook? Ook. Ook. Ook! Ook. Ook! Ook? Ook! Ook! Ook? Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook.
My bets are, I'm mostly like to high-five my past self for only one of these three examples. But while our choice of languages and communication are usually easy to see, how we interface with other code is not always easy.
In the original Gang of Four Design Patterns book, one of the key principles was "Design to Interfaces". Oftentimes this gets confused to mean you should create interfaces which your objects implement - but that is literally an implementation detail. What the authors really meant was this:
Start with expressiveness first.
What this really means is - control your own API, the way you want your code to interface. Then adapt theirs to fit yours. While I gave some examples in an earlier blog post on how this can be used to scale component teams, the technique is not limited to just scaling.
As an example, in this great StackOverflow post the poster has a Car
object that can be created, updated and viewed. But there are also additional actions, such as the ability to purchase a car.
If we start with expressiveness first, it leads us to think about what really happens when we purchase a car. We aren't actually manipulating the car itself (at least, not yet). Instead, we have some set of operations that when bundled together form the concept of purchase. So we would set up the notion that the action of purchasing a car creates a new trigger for these set of operations, leading to the RESTful path:
POST /purchase
Of course, our code should not be littered with RESTful calls. We should instead centralize those into our own API we call. For example, using Ruby's rest-client gem, we can write the following:
require 'rest_client' class Car def purchase RestClient.post("#{Config.server}/purchase", self.to_json) end end
So our code base would merely call car.purchase
and not have to know anything about REST.
But we can take this one step further, and this is where the real power of expressiveness comes into play. Let's imagine we worked through the above example, and then find out that the vendor we work with provides an API which looks like:
POST /api/v1.1/car/123/purchase
While not the end of the world, that isn't the best way they could structure that. We could modify our Car
class above to call the updated structure, but then we are tying ourselves to that interface. Instead, we can continue to use the RESTful interface we think is correct, and use an Adapter to give us the interface we want.
For example, we could set up a tiny Sinatra server that looks like this:
require 'sinatra' require 'rest-client' post '/purchase' car = params[:id] RestClient.post("#{Config.server}/api/v1.1/car/#{car}/purchase", params) end
Now we rely on a RESTful service which is a thin adapter around the real service. This can be really helpful if multiple applications we are building all rely on the same RESTful services which are out of our control.
So, if your code is telling you to write something, listen to it and write the code you want to write. Only after you've done that should you go back and adapt it to the nastiness you really have to deal with. You gain expressiveness, readability, and as a bonus, an added layer of separation from your dependencies.
(Note: I don't feel this should end without a brief note about performance. Start with expressiveness, and then optimize for performance. There is no doubt an art to the balance between clean code and defensive programming, where everything is built up front and wrapped to the nth degree. But if we start with the code our problem is telling us to write, we can easily read it to figure out where we can make it faster - and better)
I think I did something similar to the example, here are the relevant routes: https://gist.github.com/JoshCheek/26484f367ca8dad280b1
e.g. I did `/enrollments` instead of `/courses/:id/enroll` but then within the enrollment, there’s various steps you have to perform, so I made them like “sub-resources”, but they don’t have a full CRUD lifecycle, so I modeled it like they always exist, and you can only update them.
I think there are two issues for me: (1) support for non-get requests is lacking, so using them adds a lot of overhead (2) I am very apprehensive about state, so I tend to prefer verb names over noun names, but REST is resource oriented, so it’s about the nouns. This feels very OO to me, but I’ve moved pretty strongly away from OO, so it’s difficult for me to translate my thinking into it.
Anyway, thanks for the post! And if you have any thoughts on my API, I’d welcome the feedback :)