Hacker News new | ask | show | jobs
by throwaway4good 1917 days ago
Am I the only who simply does remote procedure calling over http(s) via JSON? Not REST as in resource modelling but simply sending a request serialized as a JSON object and getting a response back as a JSON object.
7 comments

I've done JSON-RPC at scale before and the one downside to it is that you have to write a custom caching proxy for readonly calls that understands your API.

With REST you can just use a normal HTTP caching proxy for all the GETs under certain paths, off the shelf.

Using a hybrid (JSON-RPC for writes and authenticated reads, REST for global reads) would have saved me a lot of time spent building and maintaining a JSON-RPC caching layer.

There is benefit to a GET/POST split, and JSON-RPC forces even simple unauthenticated reads into a POST.

The other issue with JSON-RPC is, well, json. It's not the worst, but it's also not the best. json has no great canonicalization so if you want to do signed requests or responses you're going to end up putting a string of json (inner) into an key's value at some point. Doing that in protobuf seems less gross to me.

Personally I prefer to have explicit control over the caching mechanism rather than leaving it to network elements or browser caching.

That is explicity cache the information in your JavaScript frontend or have your backend explicitly cache. In that way it is easy to understand and your can also control what circumstances a cache is invalidated.

> Personally I prefer to have explicit control over the caching mechanism rather than leaving it to network elements or browser caching.

I'm not talking about browser caching, I'm talking about the reverse proxy that fronts your ("backend") service to the internet. High traffic global/unauthenticated reads, especially those that never change, should get cached by the frontend (of the "backend", not the SPA) reverse proxy and not tie up appservers. (In our case, app servers were extremely fat, slow, and ridiculously slow to scale up.)

I am sure you have good reasons for your concrete design but in the general case: Why not simply build the caching into your backend services rather than having a proxy do it based on the specifics of http protocol? It would be simpler and far more powerful.
Because the backend app servers were extremely fat (memory and disk intensive) and every request they served that could have been served from an upstream cache was a request that they weren't serving that actually required all of their resources to serve.

Caching upstream on (vastly cheaper) instances permitted a huge cost savings for the same requests/sec.

Proxy caching / leveraging caching to external service makes sense when you have a LOT of users scattered around the globe - let Akamai/Cloudflare take care of edge node caching and maintenance. In the end it saves you a lot of engineering time and infrastructure costs not mentioning user experience. YMMV but it pays off in our current setup.
Because if you use HTTP caching, you can use a CDN with 100s of global locations. Which is quite a bit more powerful than any custom solution.
If I'm going to neuter HTTP like that, I at least do RPC over websockets for realtime feel. And I still usually run it through the whole Rails controller stack so I don't drive myself insane.
This is fine at a small scale. When you're one dev or a small team you can understand the whole system and you'll benefit from this simplicity.

When you're many devs, many APIs, many resources, it really pays to have a consistent, well-defined way to do this. GraphQL is very close to what you've described, with some more defined standards. GRPC is close as well, except the serialisation format isn't JSON, it's something more optimised.

As a team grows these sorts of standards emerge from the first-pass versions anyway. These just happen to be pre-defined ones that work well with other things that you could choose to use if you wanted to.

It's fine at medium and large scale, too, as long as the service doesn't change its API very much, and/or it doesn't have too many consumers. It only breaks down when managing change becomes too difficult. IMO way way too many people opt in to the complexity of IDL-based protocols without good reason.
Just be clear - I am not suggesting JSON-RPC as there is no envelope and the name of the invoked procedure is in the HTTP request line.

For example:

   POST /api/listPosts HTTP/1.1
   { userId: "banana", fromDate: 2342342342, toDate: 2343242 }
Reponse:

   HTTP/1.1 200 OK
   [ { id: 32432, title: "Happy banana", userId: "banana" }, ... ]
Or in case of an error:

   HTTP/1.1 500 Internal Server Error
   { type: "class name of exception raised server side", message: "Out of bananas" }
The types can be specified with TypeScript needed.
The point is to get free of resource modelling paradigm, the meaning of various http methods, and potential caching. And also not have the silly overhead of JSON-RPC.
My team has started doing this for workflow orchestration. When our workflows (implemented in Cadence) need to perform some complex business logic (grab data from 3 sources and munge, for example) we handle that with a RPC-style endpoint.
No, I have seen many such approaches. It draws undue criticism when the actual REST API starts to suffer due to people getting lazy, at which point they lump the RPC style calls into the blame.
I am doing the same.

GET /api/module/method?param1&param2

or

POST /api/module/method Body: Json{ param1, param2 }

I do JSON-rpc over webscokets.