A lot actually, even though they all three made an impact in different decades (1990's, 2000's, 2010's), were initiated by three different organisations (OMG, Microsoft, Google), and purported to be much better than any previous or existing solution. In fact, when each of these technologies was released, they were billed as the best way to communicate between nodes of distributed software. Common characteristics include:
- Programming language agnostic, with bindings for all modern languages at the time
- DSL for describing endpoints and messages
- Set of standardized types for message fields
- Client and server code generation
- Allow remote procedure calls
- Facilitate developing operation-oriented APIs
While some interesting features stand out, the main problem that I find with these technologies is the last characteristic in the list. The remainder of this article aims to show why this is such a terrible flaw.
Form influences substance
In order to understand my critique, one must understand that I am not saying that each of these technologies is a fraud, nor that large corporations can't build successful software on top of them. The matter of the fact is that many great examples prove the usefulness of all three.
The problems I point out in this article are mainly ones of form, and not of substance. Such problems should not matter, except that in many cases form seems to influence substance very much, and especially where programming is concerned. So as to not get carried too far into a philosophical discussion, I'll illustrate my point.
The C programming language has proved it's worth many times over. If it weren't for Dennis Ritchie, Brian Kernighan and countless others from Bell Labs, IT would arguably not be as far advanced as it is today, and I would likely not be a computer programmer. Yet who would write a new program in C today? Unless you had to, you wouldn't. You might use Java, or C#, or NodeJS. Or you might use some newfangled language like Rust. Now anything you can code in any programming language out there today, you can code in C: this concept is called Turing-completeness.
So why is the choice of programming language important? There are various factors: the target platform, the team's skills, the libraries available, the CEO's golf partners, and somewhere on your list of criteria there might be the appropriateness with respect to the problem being solved.
Let's say you wanted to write a Web API, that many clients will need to consume. You might choose Java with Spring Boot, or C# with ASP.Net Web API. Both options can result in equally cost of development and total cost of ownership. In reality, one will strive for best practices, the other will become a big ball of mud. I leave the reader to figure out which is which, as an exercise. The only solid reason I can find that decides which outcome occurs is a one of form. One language and its community thirst for elegance, while the others wallow in patchwork monstrosities.
Now let's say you wanted to write a highly optimized HTTP load balancer. You could try Go, and be hit by limitations due to the garbage collector and the thread model. You could write it in C, or C++, which won't burden you with a GC, and will allow you to bend the threading model to your will, but you might lose your sanity. In the end, you could choose Rust. Rust is a singular language where the compiler itself can lead you to best practices while being more accessible than a purely functional language like Haskell.
RPC ignores the experience of the World Wide Web
One of the many critiques of SOAP was that it always communicated using the POST HTTP verb. This meant that it was using the Web while disabling all of its optimisations. The gRPC framework advertises that it uses HTTP/2, but you really have to dig deep to find out how to declare operations as idempotent or safe. Currently, there is an open issue to add caching optimizations to gRPC's wire protocol that have been an integral part of HTTP for a couple decades.
This is but one of many wheels that gRPC has reinvented or will have to rediscover. SOAP and CORBA never even attempted to tackle these issues.
RPC leads to poor API design
The inherent operation orientation of RPC means that API's end up as a list of operations and message types. There are ways of grouping operations together into subsystems — gRPC calls them services — and certain naming conventions can portray information about what an operation's constraints are. It is possible to design and maintain a beautiful and efficient operation oriented API. The form though usually influences developers to add operation after operation mindlessly, giving little thought and documenting even less the constraints imposed by the domain.
Alternatives to the operation-oriented API
The biggest challenger to operation-oriented architecture is resource-oriented. The main reason to push this style of API is that it naturally fits with Domain Driven Design.
There are many misconceptions of ReST :
- It's too difficult to implement
- Its constraints are pointless
- It must be implemented as JSON payloads over HTTP
- Not all requests can be modelled as a creation, read, update, or deletion, so you end up needing endpoints representing RPC anyway
- You can't generate a client from a standard description like WSDL or Protocol Buffers language
- HATEOAS constrains the API to be inefficient, especially compared to an API built on GraphQL
I will not try to refute these statements here. If the reader is really in search of the truth, they will find it quite easily elsewhere.
Rather I would like to underline the importance of resource orientation. Just as the notion of class channels towards better software design, the resource gives much-needed structure to APIs. A resource, much like an object, correlates with something found in the physical world. It has constraints, states, and transitions that should be documented, or can at least be inferred from the API's structure. Resource identity leads to message validation and reuse of validation logic. When implemented over HTTP, a resource usually has a unique URI with a prefix shared by all resources of its type. A controller tends to handle all requests to URIs with that prefix, thus the responsibility of data integrity is clearly assigned to a single component. These same concepts remain when using other protocols.
The gRPC technology, a.k.a. the modern iteration of the ideas that brought you CORBA and SOAP, isn't a problem for extremely disciplined developers.
The problem is that it tends towards inefficient and obscure API design.