What are they for??
RPC-based APIs are great for actions (that is, procedures or commands).
REST-based APIs are great for modeling your domain (that is, resources or entities), making CRUD (create, read, update, delete) available for all of your data.
REST is not only CRUD, but things are done through mainly CRUD-based operations. REST will use HTTP methods such as GET
, POST
, PUT
, DELETE
, OPTIONS
and, hopefully, PATCH
to provide semantic meaning for the intention of the action being taken.
RPC, however, would not do that. Most use only GET
and POST
, with GET
being used to fetch information and POST
being used for everything else. It is common to see RPC APIs using something like POST /deleteFoo
, with a body of { "id": 1 }
, instead of the REST approach, which would be DELETE /foos/1
.
This is not an important difference; it’s simply an implementation detail. The biggest difference in my opinion is in how actions are handled. In RPC, you just have POST /doWhateverThingNow
, and that’s rather clear. But with REST, using these CRUD-like operations can make you feel like REST is no good at handling anything other than CRUD.
Well, that is not entirely the case. Triggering actions can be done with either approach; but, in REST, that trigger can be thought of more like an aftereffect. For example, if you want to “Send a message” to a user, RPC would be this:
POST /SendUserMessage HTTP/1.1
Host: api.example.com
Content-Type: application/json
{"userId": 501, "message": "Hello!"}
But in REST, the same action would be this:
POST /users/501/messages HTTP/1.1
Host: api.example.com
Content-Type: application/json
{"message": "Hello!"}
There’s quite a conceptual difference here, even if they look rather similar:
- RPC
We are sending a message, and that might end up storing something in the database to keep a history, which might be another RPC call with possibly the same field names — who knows? - REST
We are creating a message resource in the user’s messages collection. We can see a history of these easily by doing aGET
on the same URL, and the message will be sent in the background.
This “actions happen as an afterthought” can be used in REST to take care of a lot of things. Imagine a carpooling app that has “trips.” Those trips need to have “start,” “finish” and “cancel” actions, or else the user would never know when they started or finished.
In a REST API, you already have GET /trips
and POST /trips
, so a lot of people would try to use endpoints that look a bit like sub-resources for these actions:
POST /trips/123/start
POST /trips/123/finish
POST /trips/123/cancel
This is basically jamming RPC-style endpoints into a REST API, which is certainly a popular solution but is technically not REST. This crossover is a sign of how hard it can be to put actions into REST. While it might not be obvious at first, it is possible. One approach is to use a state machine, on something like a status
field:
PATCH /trips/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{"status": "in_progress"}
Just like any other field, you can PATCH
the new value of status
and have some logic in the background fire off any important actions:
module States
class Trip
include Statesman::Machine
state :locating, initial: true
state :in_progress
state :complete
transition from: :locating, to: [:in_progress]
transition from: :in_progress, to: [:complete]
after_transition(from: :locating, to: :in_progress) do |trip|
start_trip(trip)
end
after_transition(from: :in_progress, to: :complete) do |trip|
end_trip(trip)
end
end
end
Statesman is an incredibly simple state machine for Ruby, written by the GoCardless team. There are many other state machines in many other languages, but this is an easy one to demonstrate.
Basically, here in your controllers, lib
code or DDD logic somewhere, you can check to see if "status"
was passed in the PATCH
request, and, if so, you can try to transition to it:
resource.transition_to!(:in_progress)
When this code is executed, it will either make the transition successfully and run whatever logic was defined in the after_transition
block, or throw an error.
The success actions could be anything: sending an email, firing off a push notification, contacting another service to start watching the driver’s GPS location to report where the car is — whatever you like.
There was no need for a POST /startTrip
RPC method or a REST-ish POST /trips/123/start
endpoint, because it could simply be handled consistently within the conventions of the REST API.
Use Both REST And RPC
The idea that you need to pick one approach and have only one API is a bit of a falsehood. An application could very easily have multiple APIs or additional services that are not considered the “main” API. With any API or service that exposes HTTP endpoints, you have the choice between following the rules of REST or RPC, and maybe you would have one REST API and a few RPC services. For example, at a conference, somebody asked this question:
We have a REST API to manage a web hosting company. We can create new server instances and assign them to users, which works nicely, but how do we restart servers and run commands on batches of servers via the API in a RESTful way?
There’s no real way to do this that isn’t horrible, other than creating a simple RPC-style service that has a POST /restartServer
method and a POST /execServer
method, which could be executed on servers built and maintained via the REST server.