Why you should consider using RPC over REST for internal API

Published

REST is a very popular architecture style for communication. HTTP - as the most commonly used protocol for REST - gives a lot of features for free: cache, basic authentication, metadata channel (via headers), security (SSL/TLS), CORS etc. It fits almost perfectly for public API. But are those features really necessary for internal API?

Have you heard about RPC? Ask your teammates about it and you will probably hear "RPC? Do you mean SOAP?" or "RPC? Dude, no one uses it anymore. Remember CORBA?". That's right. A lot of people hate RPC because of ancient technologies. But times have changed and nowadays we have really good implementations of RPC: GRPC , JSON-RPC, Thrift. Is it worth trying it again? What's wrong with REST then?

Before we move further I'd like to state what I mean by "internal API". Internal API is a communication interface between internal services with restricted access, for instance, between web app and microservice for user management.

Public API vs internal API

Set of requirements is different for each of them.

Internal API Public API
Caching No, to prevent fetching stale data Desirable
Authentication Not necessary in private networks Required
Impersonation Desirable No
Abuse detection/prevention No Desirable
Ease to use As easy as possible during development process Publish API SDK for customers
Versioning Not necessary Desirable
Performance As fast as possible You can sacrifice some performance for usage flexibility (HTTP content type negotiation, e-tag caching, CORS, HATEOAS)

Having that in mind it's easy to conclude that you don't have to use the same mechanisms for both APIs.

REST verbs are very limited

For REST in order to perform an operation on a resource you need to set a HTTP Method like GET (default), PUT, POST, DELETE or custom (read further). What if you need to validate entity before saving or activating an user? There is no HTTP method for validation and activation. We need some workaround (BTW REST got a lot of them).

You can use sub-resources

# activate user
PUT /user/1/activate 
# validate entity
POST /entity/validate {...}

but this one is really weird. Exposing "action" as a fake resource is a hack and definitely not intuitive.

Let's try custom method then.

ACTIVATE /user/1
VALIDATE /entity {...}

Unfortunatelly custom methods might not be supported by your http server (or a client) so you're forced to use X-HTTP-Method-Override header .

And now compare it to RPC

validateEntity {...}
activateUser 1

We've got a clear winner here.

REST arguments passing is complicated

With REST you have 4 channels to pass the arguments in REST

  • HTTP Headers
  • URL path
  • URL query
  • HTTP Body

Examples of common operations

# Set access to "admin" for user 1 in team 2
PUT /user/1/teams/2/access "admin"

# Insert element at the end of the queue
POST /queue?last=true {...}
# or
X-Position: last
POST /queue {...}

# much simpler for RPC
setUserAccess 1, 2, "admin"
putToQueueAtTheEnd {...}

Handling such cases manually throughout the codebase gets messy very quickly so it's much better to create custom client

REST requires custom client

You don't want to build all URLs manually, set x-http-method-override header or perform other boring operations in order to make a simple call. You'll need a custom client for every service in every programming language 1 and that is counter-productive. In case of schema-less RPC libraries2 you don't have to, since making API calls is very simple.

rpcService.call('setUserAccess', userId, teamId, group);

Bulk actions

RPC - Just do it.

createUsers [{...}, {...}] 

You're not limited to HTTP with RPC

Do you want to use pure TCP (or websockets) and binary format3 without HTTP overhead for the best possible performance? Not a problem. Do you want to use ZeroMQ with all its goodness for RPC? A piece of cake.

Can you do the same with REST? Well, you need to implement all REST key components: HTTP headers, URL, HTTP body, HTTP negotiation, Cache etc.

Summary

RPC is a very simple API design approach with absolutely no limitations. For public API stay with REST but for internal ones give a try to JSON-RPC, DDP or even GRPC and I bet you won't be disappointed.

Footnotes

  1. You can use client generators for schema-based REST APIs (RAML) assuming it can handle corner cases as discussed above.

  2. RPC libraries (Thrift or GRPC) based on schema (IDL) might introduce building phase for a client on certain platforms like Java.

  3. HTTP 2.0 is now binary

Łukasz Kużyński - Wookieb
Thoughts, tips about programming and related subjects to make your job easier and more pleasant so you won't burnout quickly.
Copyright © Łukasz Kużyński - Wookieb 2023 • All rights reserved.