What Are RPC And REST For?

What Are RPC And REST For?

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 a GET 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.


Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply