Async vs Sync is not enough. Network partitions have multiple failure modes depending on a range of factors, but one of the most pernicious is the “undetectable cloud break”, an invisible break between two routers. Having a well defined “api” that is the clear “we are communicating over the network here” makes it much easier to reason about and handle these issues.
There is an enormous difference between “this I/O call failed” and “this I/O call took 10 minutes to fail”.
The RPC debate is confused because the two sides of the debate have different ideas of what RPC is. The anti-RPC crowd is arguing about RPC systems from the 80's, which tried to make RPC look exactly like a local function call, including blocking until the call was complete. The pro-RPC crowd is mostly speaking from experience using modern RPC systems which do not do this; they use asynchronous callback APIs, have some mechanism for reporting network errors, etc. Modern RPC systems are called RPC because they encourage you to organize your network API conceptually into something like function calls, but they certainly do try to hide the fact that there is a network here that can fail in all the ways networks fail.
I'll agree with you that it's not a trivial change, far from it. You need switching your mindset from local to remote, from procedures to agent messages, from exceptions to non-local error-handling, but it remains quite simple and readable.
I feel that Telefunc is bringing developers a good chunk of the way towards that goal. And, who know, maybe there is another simple model that is not exactly agents, and that would also work nicely.
Are HTTP and its cousin REST examples of RPC frameworks? I don’t think so, and I may have misunderstood your point. But if you do consider them to be RPC, can you explain your reasoning?
Despite drawbacks here and there, I do have appreciation for RPC, e.g. JSON-RPC is widely used in the distributed/decentralized tech space I work in. It’s fairly well-specified and well-suited for a variety of use cases:
A beef patty is not a hamburger without the bun, pickles and condiments.
* semantics of what the request / response looks like and how failures communicate
* how are the APIs composed? Not the local function call, but the functionality calling the API implies (see cap’n’proto’s page on composable remote APIs - it’s really brilliant).
* can you communicate across programming languages?
* how is ownership of remote resources managed?
* how is routing / dispatch of API calls managed?
For example “fetch” in JS land looks a hell of a lot to me like a normal function call to me. And I certainly then wrapped it with easier to read APIs that looked more normal and handled routing and error handling for me. The main difference with HTTP is that RPC systems traditionally autogenerate a bunch of code for you. But in HTTP land you now have efforts like OpenAPI to do similar things. So HTTP isn’t RPC but the autogenerated code from a description of the API makes it RPC? That gets you into the ship of Theseus paradox which isn’t helpful. At what amount of wrapping it with local functional calls does distributed API calls transform into RPC?
To me it turns out that async / sync is a key distinction and the original sin of RPC in the 80s. It’s also why I view the coloring problem as a good thing (at least given where the tech stack is today). Making functions that may be doing I/O stand out and needing thought is a good thing. The problem with many RPC/IPC mechanisms from the 80s is they tried to make the code seem synchronous not that it looks like a function call. But I haven’t seen any RPC systems where async vs sync is the distinguishing characteristic (eg cap’n’proto and fetch are both async).
To me, HTTP, REST, SOAP, COM, gRPC, cap’n’proto, JSON-RPC, XPC, Mach etc all try to solve the same problem of distributed communication and processing, just by making different trade offs.
HTTP tried to standardize RPC semantics but you can clearly see it’s optimized particularly around static document retrieval and typical browser tasks. I’ll buy that static document retrieval need not be classified as a remote API but there’s a lot of non static document retrieval that’s layered on the same semantics that is proper API calls (notably REST) tries to get everything to conform to that so that middleware has some convention and you get browser interop. It works surprisingly well because a good majority of apps are CRUD and don’t need anything more.
JSON-RPC tries to solve routing/dispatch to be more automated if I recall correctly because REST is such a cluster (look at all the routing libraries which to me are an anti pattern). + JSON-RPC is particularly easy in JS which is usually the language on one side of the transaction.
COM solves all of the above and things like distributed ownership of resources, except it historically didn’t have a good security model (I don’t know it well enough to know what’s improved there but I don’t assume it’s stayed static since the 90s even if it hasn’t found traction outside Microsoft).
They all try to solve different parts of the problem or have different sets of tradeoffs, but to me it’s fundamentally the same problem. It’s the same reason I don’t view distributed systems as something that requires different machines, CPUs, processes or anything about geography. You can have distributed systems within one process, you can have it within processes on the same CPU, you can have it between CPUs on the same device, etc. Heck, to me even the kernel and user space form a distributed system with syscalls as a primitive RPC mechanism. JNI is a form of RPC too although you’ve seen traditional systems like gRPC supplant it even within process because it moves complexity out of the JNI layer into a simpler system with easier to follow rules.
Now you can sometimes makes simplifying assumptions to reduce complexity, reduce power usage etc. and not all distributed systems necessarily have the same sets of problems. But fundamentally to me it’s all the same problem space and why I don’t distinguish the terminology so much.
Latency is actually mostly solvable with something like cap’n’proto that pipelines the promises so that you can have a normal looking composable functions without the latency cost.
Joel Spolsky advice is usually fine but it’s important to understand his commentary tends to be about what ideas are in vogue / dominant at the time of the post and tend to have a more limited perspective (vs listening to researchers in the field actively investigating the problems and investing in ways to find solutions).
For example, we have an explicit class that manages RPC calls so that at the call site it looks like a regular function. But since it’s I/O it returns an async so it makes it clear that it’s I/O (which you have to do with non NodeJS JavaScript). Additionally, it returns a promise of a Result object type just like any other failable call which you have to handle properly. Works pretty well (although in retrospect I’m now wishing we had used structured clone instead of JSON to send messages around).