Saying gRPC hides the internals is a joke. You’ll get internals all right, when you’re blasting debug logging trying to figure out what the f is going on causing 1/10 requests to fail and fine tuning 10-20 different poorly named and timeout / retry settings.
Hours lost fighting with maven plugins. Hours lost debugging weird deadline exceeded. Hours lost with LBs that don’t like the esoteric http2. Firewall pain meaning we had to use Standard api anyway. Crappy docs. Hours lost trying to get error messages that don’t suck into observability.
I wish I’d never heard of it.
When you run the protobuf builder...
* The client stub is a concrete final class. It can't be mocked in tests.
* When implementing a server, you have to extend a concrete class (not an interface).
* The server method has an async method signature. Screws up AOP-oriented behavior like `@Transactional`
* No support for exceptions.
* Immutable value classes yes, but you have to construct them with builders.
The net result is that if you want to use gRPC in your SOA, you have to write a lot of plumbing to hide the gRPC noise and get clean, testable code.
There's no reason it has to be this way, but it is that way, and I don't want to write my own protobuf compiler.
Thrift's rpc compiler has many of the same problems, plus some others. Sigh.
I believe this is deliberate, you are supposed to substitute a fake server. This is superior in theory since you have much less scope to get error reporting wrong (since errors actually go across a gRPC transport during the test).
Of course.. at least with C++, there is no well-lit-path for actually _doing_ that, which seems bonkers. In my case I had to write a bunch of undocumented boilerplate to make this happen.
IIUC for Stubby (Google's internal precursor to gRPC) those kinda bizarre ergonomic issues are solved.
The reason to use it may be that you are required to by the side you cannot control, or this is the only thing you know. Otherwise it's a disaster. It's really upsetting that a lot of things used in this domain are the first attempt by the author to make something of sorts. So many easily preventable disasters exist in this protocol for no reason.
The modern .NET and C# experience with gRPC is so good that Microsoft has sunset its legacy RPC tech like WCF and gone all in on gRPC.
Validating the output of the bindings protoc generated was more verbose and error prone than hand serializing data would have been.
The wire protocol is not type safe. It has type tags, but they reuse the same tags for multiple datatypes.
Also, zig-zag integer encoding is slow.
Anyway, it’s a terrible RPC library. Flatbuffer is the only one that I’ve encountered that is worse.
But as the article mentions OpenAPI is also an RPC library with stub generation.
Manual parsing of the json is imho really Oldskool.
But it depends on your use case. That’s the whole point: it depends.
When is this ever an issue in practice? Why would the client read int32 but then all of a sudden decide to read uint32?
Your experience of gRPC seems to be very different from mine. How much of the difference in experience do you think might be down to Java and how much is down to gRPC as a technology?
At my company we found some workarounds to the issues brought up on GP but it's annoying the tooling is a bit subpar.
I like a lot of things about it and used it extensively instead of the inferior REST alternative, but I recommend to be aware of the limitations/nuisances. Not all issues will be simply solved looking at stackoverflow.
Some web socket libraries support automatic fallback to polling if the infrastructure doesn’t support web sockets.
If you don't, use GraphQL.
I disagree with the way this article breaks down the options. There is no difference between OpenAPI and REST, it's a strange distinction. OpenAPI is a way of documenting the behavior of your HTTP API. You can express a RESTful API using OpenAPI, or something completely random, it's up to you. The purpose of OpenAPI is to have a schema language to describe your API for tooling to interpret, so in concept, it's similar to Protocol Buffer files that are used to specify gRPC protocols.
gRPC is an RPC mechanism for sending protos back and forth. When Google open sourced protobufs, they didn't opensource the RPC layer, called "stubby" at Google, which made protos really great. gRPC is not stubby, and it's not as awesome, but it's still very efficient at transport, and fairly easy too extend and hook into. The problem is, it's a self-contained ecosystem that isn't as robust as mainstream HTTP libraries, which give you all kinds of useful middleware like logging or auth. You'll be implementing lots of these yourself with gRPC, particularly if you are making RPC calls across services implemented in different languages.
To me, the problem with gRPC is proto files. Every client must be built against .proto files compatible with the server; it's not a discoverable protocol. With an HTTP API, you can make calls to it via curl or your own code without having the OpenAPI description, so it's a "softer" binding. This fact alone makes it easier to work with and debug.
The way that REST was defined by Roy Fielding in his 2000 Ph.D dissertation ("Architectural Styles and the Design of Network-based Software Architectures") it was supposed to allow a web-like exploring of all available resources. You would GET the root URL, and the 200 OK Response would provide a set of links that would allow you to traverse all available resources provided by the API (it was allowed to be hierarchical- but everything had to be accessible somewhere in the link tree). This was supposed to allow discoverability.
In practice, everywhere I've ever worked over the past two decades has just used POST resource_name/resource_id/sub_resource/sub_resource_id/mutatation_type- or PUT resource_name/resource_id/sub_resource/sub_resource_id depending on how that company handled the idempotency issues that PUT creates- with all of those being magic URL's assembled by the client with knowledge of the structure (often defined in something like Swagger/OpenAPI), lacking the link-traversal from root that was a hallmark of Fielding's original work.
Pedants (which let's face it, most of us are) will often describe what is done in practice as "RESTful" rather than "REST" just to acknowledge that they are not implementing Fielding's definition of REST.
REST is an interesting idea, but I don't think it is a practical idea. It is too hard to design tools and libraries that helps/encourages/forces the user implement HATEOAS sensibly, easily and consistently.
Yes, exactly. I've never actually worked with any group whom had actually implemented full REST. When working with teams on public interface definitions I've personally tended to use the so-called Richardson's Maturity Model[0] and advocated for what it calls 'Level 2', which is what I think most of us find rather canonical and principal of least surprise regarding a RESTful interface.
[0] - https://en.wikipedia.org/wiki/Richardson_Maturity_Model
That threw me off too. What the article calls REST, I understand to be closer to HATEOAS.
> I've open sourced a Go library for generating your clients and servers from OpenAPI specs
As a maintainer of a couple pretty substantial APIs with internal and external clients, I'm really struggling to understand the workflow that starts with generating code from OpenAPI specs. Once you've filled in all those generated stubs, how can you then iterate on the API spec? The tooling will just give you more stubs that you have to manually merge in, and it'll get harder and harder to find the relevant updates as the API grows.
This is why I created an abomination that uses go/ast and friends to generate the OpenAPI spec from the code. It's not perfect, but it's a 95% solution that works with both Echo and Gin. So when we need to stand up a new endpoint and allow the front end to start coding against it ASAP, the workflow looks like this:
1. In a feature branch, define the request and response structs, and write an empty handler that parses parameters and returns an empty response.
2. Generate the docs and send them to the front end dev.
Now, most devs never have to think about how to express their API in OpenAPI. And the docs will always be perfectly in sync with the code.
OpenAPI is a spec not documentation. Write the spec first then generate the code from the spec.
You are doing it backwards, at least in my opinion.
If you design the API first, you can take the OpenAPI spec through code review, making the change explicit, forcing others to think about it. Breaking changes can be caught more easily. The presence of this spec allows for a lot of work to be automated, for example, request validation. In unit tests, I have automated response validation, to make sure my implementation conforms to the spec.
Iteration is quite simple, because you update your spec, which regenerates your models, but doesn't affect your implementation. It's then on you to update your implementation, that can't be automated without fancy AI.
When the spec changes follow the code changes, you have some new worries. If someone changes the schema of an API in the code and forgets to update the spec, what then? If you automate spec generation from code, what happens when you express something in code which doesn't map to something expressible in OpenAPI?
I've done both, and I've found that writing code spec-first, you end up constraining what you can do to what the spec can express, which allows you to use all kinds of off-the-shelf tooling to save you time. As a developer, my most precious resource is time, so I am willing to lose generality going with a spec-first approach to leverage the tooling.
In the OpenAPI world, the equivalent must be writing one's own OpenAPI spec generator that scans an annotated server codebase, probably bundled with a client codegen tool as well. I know I've written one (mine too was a proper abomination) and it sounds like so have a few others in this thread.
This is why I have never used generators to generate the API clients, only the models. Consuming a HTTP based API is just a single line function nowadays in web world, if you use e.g. react / tanstack query or write some simple utilities. The generaged clients are almost never good enough. That said, replacing the generator templates is an option in some of the generators, I've used the official openapi generator for a while which has many different generators, but I don't know if I'd recommend it because the generation is split between Java code and templates.
This is against "interface first" principle and couples clients of your API to its implementation.
That might be OK if the only consumer of the API is your own application as in that case API is really just an internal implementation detail. But even then - once you have to support multiple versions of your own client it becomes difficult not to break them.
What if you could query any ole' API like this?:
Apipe.new(GitHun) |> from("search/repositories") |> eq(:language, "elixir") |> order_by(:updated) |> limit(1) |> execute()
This way, you don't have to know about all the available gRPC functions or the 3rd party API's RESTful quirks while retaining built-in documenting and having access to types.https://github.com/cpursley/apipe
I'm considering building a TS adapter layer so that you can just drop this into your JS/TS project like you would with Supabase:
const { data, error } = await apipe.from('search/repositories').eq('language', 'elixir').order_by('updated').limit(1)
Where this would run through the Elixir proxy which would do the heavy lifting like async, handle rate limits, etc.That's not quite true. You can build an OpenAPI description based on JSON serialization of Protobufs and serve it via Swagger. The gRPC itself also offers built-in reflection (and a nice grpcurl utility that uses it!).
I'm using it for a small personal project! Works very well. Thank you!
Migrated away from Swaggo -> oapi during a large migration to be interface first for separating out large vertical slices and it’s been a godsend.
That said, I absolutely would not use grpc for anything customer or web facing. RPC is powerful because it locks you into a lot of decisions and gives you "the one way". REST is far superior when you have many different clients with different technology stacks trying to use your service.
You can even do this with gRPC if you’re using Buf or Connect - one of the server thingies that try not to suck; they will accept JSON via HTTP happily.
Compared to what? What else did you try?
The problem is that clients generally have a bunch of verbs they need to do. You have to design your objects and permissions just right such that clients can do all their verbs without an attacker being able to PATCH "payment_status" from "Requires Payment" to "Payment Confirmed".
RPC uses verbs, so that could just be the SubmitPayment RPC's job. In REST, the correct design would be to give permission to POST a "Payment" object and base "payment_status" on whether that has been done.
I remember being grilled for not creating "jsony" interfaces:
message Response { string id = 1; oneof sub { SubTypeOne sub_type_one = 2; SubTypeTwo sub_type_two = 3; } }
message SubTypeOne { string field = 1; }
message SubTypeTwo { }
In your current model you just don't have any fields in this subtype, but the response looked like this with our auto translator: { "id": "id", "sub_type_two": { } }
Functionally, it works, and code written for this will work if new fields appear. However, returning empty objects to signify the type of response is strange in the web world. But when you write the protobuf you might not notice
Uhh... Why? Protobuf supports streaming replies and requests. Do you mean that you need to know the message size in advance?
Because of poor HTTP/2 support in those languages? Otherwise, it's not much more than just a run of the mill "Web API", albeit with some standardization around things like routing and headers instead of the randomly made up ones you will find in a bespoke "Look ma, I can send JSON with a web server" API. That standardization should only make implementing a client easier.
If HTTP/2 support is poor, then yeah, you will be in for a world of hurt. Which is also the browser problem with no major browser (and maybe no browser in existence) ever ending up supporting HTTP/2 in full.
The project was a mess for a hundred reasons and never got any sort of scale to justify gRPC.
That said, I've used gRPC in bits outside of work and I like it. It requires lot more work and thought. That's mostly because I've worked on so many more JSON APIs.
very fair critique
It fixes a lot of the problematic stuff with grpc and I'm excited for webtransport to finally be accepted by safari so connectrpc can develop better streaming.
I initially thought https://buf.build was overkill, but the killer feature was being able to import 3rd party proto files without having to download them individually:
deps:
- buf.build/landeed/protopatch
- buf.build/googleapis/googleapis
The automatic SDK creation is also huge. I was going to grab a screenshot praising it auto-generating SDKs for ~9 languages, but it looks like they updated in the past day or two and now I count 16 languages, plus OpenAPI and some other new stuff.Edit: I too was swayed by false promises of gRPC streaming. This document exactly mirrored my experiences https://connectrpc.com/docs/go/streaming/
We developed a small WebSocket-based wrapper for ConnectRPC streaming, just to make it work with ReactNative. But it also allows us to use bidirectional streaming in the browser.
If you're thinking from the API author's point of view, I might agree with you if there was a ubiquitous JSON annotation standard for marking optional/nullable values, but I am sick of working with APIs that document endpoints with a single JSON example and I don't want to inflict that on anyone else.
You can’t just give someone a simple command to call an endpoint—it requires additional tooling that isn’t standardized. Plus, the generated client-side code is some of the ugliest gunk you’ll find in any language.
Hard disagree from the backend world.
From one protocol change you can statically determine which of your downstream consumers needs to be updated and redeployed. That can turn weeks of work into a hour long change.
You know that the messages you accept and emit are immediately validated. You can also store them cheaply for later rehydration.
You get incredibly readable API documentation with protos that isn't muddled with code and business logic.
You get baked in versioning and deprecation semantics.
You have support for richer data structures (caveat: except for maps).
In comparison, JSON feels bloated and dated. At least on the backend.
However, I used to work in a company that used HTTP internally, and moving to gRPC would have sucked. If you're the one adding gRPC to a new service, that's more of a pain than `import requests; requests.get(...)`. There is no quick and hacky solution for gRPC, you need a fully baked, well integrated solution, rolled out across everyone who will need it.
The tooling is rough, and the documentation is sparse. Not saying REST doesn’t have its fair share of faults, but gRPC feels like a weird niche thing that’s hard to use for anything public-facing. No wonder none of the LLM vendors offer gRPC as an alternative to REST.
The same is achievable with a registry of OpenAPI documents. The only thing you need to ensure is that teams share schema definitions. This holds for gRPC as well. If teams create new types just copying some of the fields they need your analysis will be lost as well.
I mean, ideally (hopefully) in the JSON case there's some class defined in code that they can document in the comments
If it's a shitty shop that's sometimes less likely. Nice thing about protos is that the schemas are somewhere
GRPC is a standard in all the ways that matter. It (or Thrift) is a breath of fresh air compared to doing it all by hand - write down your data types and function signatures, get something that you can actually call like a function (clearly separated from an actual function function - as it should be, it behaves differently - but usable like one). Get on with your business logic instead of writing serialisation/deserialisation boilerplate. GraphQL is even better.
Letting clients introduce load into the system without understanding the big O impact of the SOA upstream is a foot gun. This does not scale and results in a massive waste of money on unnecessary CPU cycles on O(log n) FK joins and O(n^2) aggregators.
Precomputed data in the shape of the client's data access pattern is the way to go. Frontload your CPU cycles with CQRS. Running all your compute at runtime is a terrible experience for users (slow, uncachable, geo origin slow too) and creates total chaos for backend service scaling (Who's going to use what resource next? Nobody knows!).
just a casual sentence at the end? How about no. It's in the name, a query-oriented API, useless if you don't need flexible queries.
Why don't you address the problem they talked about, what is the cli tool I can use to test grpc, what about gui client?
even in go its a pain in the ass to have to regen and figure out versioning shared protos and it only gets worse w each additional language
but every startup thinks they need 100 microservices and grpc so whatever
The secret is: don't worry about it. There is no need to regenerate your proto bindings for every change to the protos defs. Only do it when you need to access something new in your application (which only happens when you will be making changes to the application anyway). Don't try and automate it. That is, assuming you don't make breaking changes to your protos (or if you do, you do so under a differently named proto).
Roy Fielding, who coined the term REST:
"A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API). From that point on, all application state transitions must be driven by client selection of server-provided choices that are present in the received representations or implied by the user’s manipulation of those representations."
https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypert...
I know it's a dead horse, but it's so funny: the "API specification" given to clients, in a truly RESTful system, should only be the initial entry point URI/URL.
https://hypermedia.systems/components-of-a-hypermedia-system...
It's an important aspect of a truly RESTful network architecture
For example, if I,
GET <account URL>
that might return the details of my account, which might include a list of links (URLs) to all subscriptions (or perhaps a URL to the entire collection) in the account.(Obviously you have to get the account URL in this example somewhere too, and usually you just keep tugging on the objects in whatever data model you're working with and there are a few natural, easy top-level URLs that might end up in a directory of sorts, if there's >1.)
See ACME for an example; it's one of the few APIs I'd class as actually RESTful. https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1.
Needing a single URL is beautiful, IMO, both configuration-wise and easily lets one put in alternate implementations, mocks, etc., and you're not guessing at URLs which I've had to do a few times with non-RESTful HTTP APIs. (Most recently being Google Cloud's…)
HAL[0] is very useful for this requirement IMHO. That in conjunction with defining contracts via RAML[1] I have found to be highly effective.
0 - https://datatracker.ietf.org/doc/html/draft-kelly-json-hal
1 - https://github.com/raml-org/raml-spec/blob/master/versions/r...
from that point forward the client discovers resources (articles, etc) that can be manipulated (e.g. comments posted and updated) via hypermedia responses from the server in responses
I don't know that I fully agree? The configuration, perhaps, but I think the API specification will be far more than just a URL. It'll need to detail whatever media types the system the API is for uses. (I.e., you'll need to spend a lot of words on the HTTP request/response bodies, essentially.)
From your link:
> A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state
That. I.e., you're not just returning `application/json` to your application, you're returning `<something specific>+json`. (Unless you truly are working with JSON generically, but I don't think most are; the JSON is holding business specific data that the application needs to understand & work with.)
That is, "and [the] set of standardized media types that are appropriate for the intended audience" is also crucial.
(And I think this point gets lost in the popular discourse: it focuses on that initial entry URL, but the "describe the media types", as Fielding says, should be the bulk of the work — sort of the "rest of the owl" of the spec. There's a lot of work there, and I think sometimes people hearing "all you need is one URL" are right to wonder "but where's the rest of the specification?")
I like gRPC too, and honestly for a commercial project it is pretty compelling. But for a personal or idealistic project I think that REST is preferable.
Also, if you use languages outside of Google's primary languages, you're likely not going to get as good of an experience.
RPCs have more maintainable semantics than REST as a virtue of not trying to shoehorn your data model (cardinality, relationships, etc.) into a one-size-fits-all prescriptive pattern. Very few entities ever organically evolve to fit cleanly within RESTful semantics unless you design everything upfront with perfect foresight. In a world of rapidly evolving APIs, you're never going to hit upon beautiful RESTful entities. In bigger teams with changing requirements and ownership, it's better to design around services.
The frontend folks don't maintain your backend systems. They want easy to reason about APIs, and so they want entities they can abstract into REST. They're the ultimate beneficiaries of such designs.
The effort required for REST has a place in companies that sell APIs and where third party developers are your primary customers.
Protobufs and binary wire encodings are easier for backend development. You can define your API and share it across services in a statically typed way, and your services spend less time encoding and decoding messages. JSON isn't semantic or typed, and it requires a lot of overhead.
The frontend folks natively deal with text and JSON. They don't want to download protobuf definitions or handle binary data as second class citizens. It doesn't work as cleanly with their tools, and JSON is perfectly elegant for them.
gRPC includes excellent routing, retry, side channel, streaming, and protocol deprecation semantics. None of this is ever apparent to the frontend. It's all for backend consumers.
This is 100% a frontend / backend tooling divide. There's an interface and ergonomic mismatch.
REST is kind of like HTML... source available by default, human-readable, easy to inspect
GRPC is for machines efficiently talking to other machines... slightly inconvenient for any human in the loop (whether that's coding or inspecting requests and responses)
The different affordances make sense given the contexts and goals they were developed in, even if they are functionally very similar.
https://buf.build/blog/protobuf-es-the-protocol-buffers-type...
and also canonical content streaming was in grpc. in http there was no common accepted solution at old times.
SSE was first built into a web browser back in 2006. By 2011, it was supported in all major browsers except IE. SSE is really just an enhanced, more efficient version of long polling, which I believe was possible much earlier.
Websocket support was added by all major browsers (including IE) between 2010 and 2012.
gRPC wasn't open source until 2015.
- 1. multiplexing protocol implemented on top of HTTP/2 - 2. serialization format via protobuf
For most companies, neither 1 or 2 is needed, but the side effect of 2 (of having structured schema) is good enough. This was the idea behind twrip - https://github.com/twitchtv/twirp - not sure whether this is still actively used / maintained, but it's protobuf as json over HTTP.
so once you wrote grpc, you get open api rpc for free.
- JSON
- .NET Binary Format for XML (NBFX)
- JSON Schema
JSON: The least-commonly used format is JSON—only a small minority use it, even though the word JSON is used (or abused) more broadly. A signature characteristic of JSON is that the consumer of JSON can never know anything about the data model.NBFX: A second serialization model is NBFX. The great thing about NBFX is that nobody has to worry about parsing XML text—they just have to learn NBFX.
JSON Schema: Probably the most popular way to serialize data is to use something like JSON Schema. A consumer of JSON Schema just reads the schema, and then uses JSON to read the data. It should be obvious that this is the total opposite of JSON, because again, in JSON it's illegal to know the format ahead of time.
This makes stable APIs so much easier to integrate with.
Sure. Until you need some fields to be optional.
> This makes stable APIs so much easier to integrate with.
Only on your first iteration. After a year or two of iterating you're back to JSON, checking if fields exist, and re-validating your data. Also there's a half dozen bugs that you can't reproduce and you don't know why they happen, so you just work around them with retries.
They don’t have sane support for protocol versioning or required fields, so every field of every type ends up being optional in practice.
So, if a message has N fields, there are 2^N combinations of fields that the generated stubs will accept and pass to you, and its up to business logic to decide which combinations are valid.
It’s actually worse than that, since the other side of the connection could be too new for you to understand. In that case, the bindings just silently accept messages with unknown fields, and it’s up to you to decide how to handle them.
All of this means that, in practice, the endpoints and clients will accumulate validation bugs over time. At that point maliciously crafted messages can bypass validation checks, and exploit unexpected behavior of code that assumes validated messages are well-formed.
I’ve never met a gRPC proponent that understands these issues, and all the gRPC applications I’ve worked with has had these problems.
https://stackoverflow.com/a/62566052
When your API changes that dramatically, you should use a new message definition on the client and server and deprecate the old RPC.
Every time this has happened to me, it's because of one-sided contract negotiation and dealing with teams where their incentives are not aligned
i.e. they can send whatever shit they want, and we have to interpret it and make it work
'rest' isn't anything (complementary)
brother.
But in practice when most people say REST they just mean "JSON RPC over HTTP". I avoid calling things REST now and just use "JSON HTTP API" to avoid the "well, actually..." responses. (and yes, these APIs are by far the most common.)
[0] https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypert...
Fake REST (i.e., JSON RPC) is really ridiculously common.
I will say that Amazon's flavor (Coral-RPC) works well and doesn't come with a ton of headache, its mostly "add ${ServiceName}Client to build" and incorporate into the code. Never mind its really odd config files
Related note, I've never understood why Avro didn't take off over GRPC, I've used Avro for one project and it seems much easier to use (no weird id/enumerations required for fields) while maintaining all the code-gen/byte-shaving
So literally gRPC[1]? You make it sound like there is a difference. There isn't, really.
What gRPC tried to bring to the table was establishing conventions around the details neither HTTP or JSON define, where otherwise people just make things up haphazardly with no consistency from service to service.
What gRPC failed on in particular was in trying to establish those conventions on HTTP/2. It was designed beside HTTP/2 with a misguided sense of optimism that browsers would offer support for HTTP/2 once finalized. Of course, that never happened (we only got half-assed support), rendering those conventions effectively unusable there.
[1] I'll grant you that protobufs are more popular in that context, but it is payload agnostic. You can use JSON if you wish. gRPC doesn't care. That is outside of its concern.
GraphQL is akin to gRPC: a non-HTTP protocol tunnelled over HTTP. Unlike gRPC, I’m unconvinced that GraphQL is ever really a great answer. I think what the latter does can be done natively in HTTP.
GCP (and I believe Azure, too) offer `GET /thing?$fields=alpha,beta.charlie` style field selection but now there's a half-baked DSL in a queryparam and it almost certainly doesn't allow me to actually express what I want so I just give up and ask for the top-level key because the frustration budget is real
I for sure think that GraphQL suffers from the same language binding problem as gRPC mentioned elsewhere: if you're stack isn't nodejs, pound sand. And the field-level security problem is horrific to fix for real
There is no way it is the most widely adopted method. To ever get to see a REST service in the wild is like winning the lottery.
Making "procedure calls" remote and hiding them underneath client libraries means that programmers do not consider the inherent problems of a networked environment. Problems like service discovery, authentication, etc are hidden beneath something that "looks like" a local procedure call.
That's one problem, the other is that procedure calls are focusing on the verbs, not the nouns (called "entities" or "resources" in the article).
If you can't express an FSM about a noun and what causes its state to change, then how the hell do you know what it does or how changes to its environment affect it?
If you don't know whether some procedure call is idempotent, how the hell can you write code that handles the various network failure modes that you have to deal with?
The other problem with "procedure calls" is they are imperative and grow without any constraints on their implementation without very careful design and review.
The functionality of the "procedure" is unbound and has unknown dependencies and side effects.
I see a lot of people here saying one is better than the other. But it depends on your use case and company size.
GRPC is a lot more complex to start using and hides internals. However it has some major advantages too like speed, streaming, type safety, stub generation. Once you have it in place adding a function is super easy.
The same can be said of OpenAPI. It’s easier to understand. Builds upon basic REST tech. However JSON parsing is slow, no streaming and has immature stub generation.
From my experience a lot of users who use OpenAPI only use it to generate a spec from the handwritten endpoints and do manual serialization. This is the worst of the two worlds. - manual code in mapping json to your objects - manual code mapping function parameters to get params or json - often type mapping errors in clients
Those engineers often don’t understand that OpenAPI is capable of stub generation. Let alone understand GRPC.
GRPC saves a lot of work once in place. And is technical superior. However it comes at a cost.
I’ve seen OpenAPI generated from routes, with generated clients libs work really well. This requires some time to setup because you can hardly use OpenAPIGenerator out of the box. But once setup I think it hits a sweet spot: - simple: http and json - can be gradually introduced from hardcoded manual json serialization endpoint (client and server) - can be used as an external api - allows for client lib generation
But it really depends on your use case. But to dismiss GRPC so easily mainly shows you have never encountered a use case where you need it. Once you have it in place it is such a time saver. But the same holds for proper OpenAPI RPC use.
However my inner engineer hates how bad the tooling around OpenAPI is, the hardcoded endpoints often done instead of server stubs, and the amount of grunt work you still need todo to have proper client libs.
I like gRPC in terms of an API specification, because one only needs to define the “what”, whereas OpenAPI specs are about the “how”: parameter in path, query, body? I don’t care. Etc.
Plus the tooling: we ran into cases where we could only use the lowest common denominator of OpenAPI constructs to let different tech stacks communicate because of orthogonal limitations across OpenAPI codegenerators.
Plus, Buf’s gRPC linter that guarantees backwards compatibility.
Plus fewer silly discussions with REST-ish purists: “if an HTTP endpoint is idempotent should deleting the same resource twice give a 404 twice?” - dude, how’s that helping the company to make money?
Plus, easier communication of ideas and concepts between human readers of the (proto) spec.
It helps by trying to map standard metaphors to your company's concepts instead of inventing bespoke return types for your company's concepts. You still need to decide whether or not to indicate that the resource is either not there, or was never there.
It's a fundamentally flawed model, as it smears the call details across multiple different layers:
1. The URL that contains path and parameters
2. The HTTP headers
3. The request body that can come in multiple shapes and forms (is it a JSON or is it a form?)
As a result, OpenAPI descriptions end up looking horrifying, in the best traditions of the early EJB XML descriptors in Java. And don't get me started on leaky abstractions when you want to use streaming and/or bulk operations.
In comparison, gRPC is _simple_. You declare messages and services, and that's it. There's very little flexibility, the URLs are fixed. A service can receive and return streams of messages.
The major downside of gRPC is its inability to fully run in browsers. But that's fixed by ConnectRPC that adds all the missing infrastructure around the raw gRPC.
Oh, and the protobuf description language is so much more succinct than OpenAPI.
To avoid the complexity you mentioned, one would have to adopt some other tool like OpenAPI and it's code generators. At that point, you might as well use something simpler and plain better: like gRPC.
No it isn't. Evidence: I'm reading this in a web browser.
"...REST is intended for long-lived network-based applications that span multiple organizations. If you don’t see a need for the constraints, then don’t use them."
Bikeshedding the spelling of resource identifiers? Or what "verb" should be used to express specialized domain semantics? Yeah, _that_ is certainly plague bullshit.
And you might not that this site is _not_ REST-ful. It's certainly HTTP, but not REST.
> Bikeshedding the spelling of resource identifiers? Or what "verb" should be used to express specialized domain semantics?
Or whether we want to use If-Modified-Since header or explicitly specify the condition in the JSON body. And 6 months later, with some people asking for the latter because their homegrown REST client doesn't support easy header customization on a per-request basis.
Or people trying (and failing) to use multipart uploads because the generated Ruby client is not actually correct.
There is _way_ too much flexibility in REST (and HTTP in general). And REST in particular adds to this nonsense by abusing the verbs and the path.
> No it isn't. Evidence: I'm reading this in a web browser.
REST is not HTTP endpoints and verbs.
The performance benefit they mention comes at the cost of (un)debugability of the binary protocol, and the fact that the interface definition language requires client code generation just further deepens the existing moats between teams because of diverging tooling and explicit boundaries drawn up by said contract.
IMO gRPC mostly ends up used as a band-aid for poor cross-team collaboration, and long-term worsens the symptoms instead of fixing the core issue. The fact that it's PITA to use is secondary, but significant too.
gRPC's design while a great technical achievement, is overly complex.
It really is WSDL all over again. Where if you buy in to a specific vendor's tooling, and don't leave it, things actually do mostly work as advertised. You want to piecemeal anything, and holy crap at the unexpected pitches.
> The least-commonly used API model is REST
Is that true? I don't think it is frankly, though I suppose if any API that would be a REST api _if it didn't have an openapi spec_ is somehow no longer a REST api, then maybe? But as previously stated, I just don't think that's true.
> A signature characteristic of [REST APIs] is that clients do not construct URLs from other information
I don't think this is true in practice. Let us consider the case of a webapp that uses a REST api to fetch/mutate data. The client is a browser, and is almost certainly using javascript to make requests. Javascript doesn't just magically know how to access resources, your app code is written to construct urls (example: getting an ID from the url, and then constructing a new url using that extracted ID to make an api call to fetch that resource). In fact, the only situation where I think this description of how a REST api is used is _defensibly_ true (and this is hella weak), is where the REST api in question has provided an openapi spec, and from that spec, you've converted that into a client library (example: https://openapi-ts.dev). In such a situation, the client has a nice set of functions to call that abstract away the construction of the URL. But somewhere in the client, _urls are still being constructed_. And going back to my first complaint about this article, this contrived situation combines what the article states are two entirely distinct methods for designing apis (rest vs openapi).
Re: the article's description of rpc, I actually don't have any major complaints.
He uses "REST" correctly. He uses "OpenAPI" as a shorthand for the class of web APIs that are resource-based and use HTTP verbs to interact with these resources.
> I don't think this is true in practice.
'recursivedoubts: https://news.ycombinator.com/item?id=42799917
The blogger is completely correct. In a true REST (i.e., not JSON-RPC) API, the client has a single entry URL, then calls the appropriate HTTP verb on it, then parses the response, and proceeds to follow URLs; it does not produce its own URLs. Hypertext as the engine of application state.
For example, there might be a URL http://foocorp.example/. My OrderMaker client might GET http://foo.example/, Accepting type application/offerings. It gets back a 200 response of type application/offerings listing all the widgets FooCorp offers. The offerings document might include a URL with an order-creation relationship. That URL could be http://foocorp.example/orders, or it could be http://foocorp.example/82347327462, or it could be https://barcorp.example/cats/dog-attack/boston-dysentery — it seriously doesn’t matter.
My client could POST to that URL and then get back a 401 Unauthorized response with a WWW-Authenticate header with the value ‘SuperAuthMechanism system="baz"’, and then my client could prompt me for the right credentials and retry the POST with an Authorization header with the value ‘SuperAuthMechanism opensesame’ and receive a 201 response with a Location header containing a URL for the new empty order. That could be http://foocorp.example/orders/1234, or it could be https://grabthar.example/hammer — what matters is that my client knows how to interact with it using HTTP verbs, headers and content types, not what the URL’s characters.
Then my client might POST a resource with content type application/order-item describing a widget to that order URL, and get back 202 Accepted. Then it might POST another resource describing a gadget, and get back 202 Accepted. Then it might GET the original order URL, and get back a 200 OK of type application/order which shows the order in an unconfirmed state. That resource might include a particular confirm URL to PUT to, or perhaps my client might POST a resource with content type application/order-confirmation — all that would be up to the order protocol definition (along with particulars like 202, or 200, or 201, or whatever).
Eventually my client non-idempotently PUTs or POSTs or whatever, and from then on can poll the order URL and see it change as FooCorp fulfills it.
That’s a RESTful API. The World Wide Web itself is a RESTful API for dealing with documents and also complete multimedia applications lying about being documents, but the RESTful model can be applied to other things. You can even build a RESTful application using the same backend code in the example, but which talks HTML to human beings whose browsers ask for text/html instead of application/whatever. Or you might build a client which asks for ‘text/html; custom=orderML’ and knows how to parse the expected HTML to extract the right information, and everything shares common backend code.
Or you might use htmx and make all this reasonably easy and straightforward.
That’s what REST is. What REST is not, is GETting http://api.foocorp.example/v1/order/$ORDERID and getting back a JSON blob, then parsing out an item ID from the JSON blob, then GETting http://api.foocorp.example/v1/item/$ITEMID and so forth.
To me the critical part of REST is the use of http semantics in API design, which makes it very un-RPC like.
The idea of a naive api client crawling through an API to get at the data that it needs seems so disconnected from the reality of how _every api client I've ever implemented_ works in a practical sense that it's unfathomable to me that someone thinks that this is a good idea. I mean, as a client, I _know_ that I want to fetch a specific `order` object, and I read the documentation from the API provider (which may in fact be me as well, at least me as an organization). I know the URL to load an order is GET /orders/:id, and I know the url to logout is DELETE /loginSession. It would never make sense to me to crawl an API that I understand from the docs to figure out if somehow the url for fetching orders has changed.
I do think we need some kind of description of REST 2.0 that makes sense in today's world. It certainly does not involve clients crawling through entry urls and relationships to discover paths that are clearly documented. It probably does involve concepts of resources and collections of resources, it certainly mandates specific uses for each http method. It should be based on the de facto uses of REST in the wild. And this thing would _definitely_ not look like an rpc-oriented api (eg soap, grpc).
>A REST API must not define fixed resource names or hierarchies (an obvious coupling of client and server). Servers must have the freedom to control their own namespace. Instead, allow servers to instruct clients on how to construct appropriate URIs, such as is done in HTML forms and URI templates, by defining those instructions within media types and link relations.
Most APIs that people call "RESTful" -- regardless of whether they come with an OpenAPI spec -- don't obey HATEOAS. A typical OpenAPI spec describes the possible request paths and verbs. However, you probably wouldn't be able to discover all that information just by starting from the entry point and parsing the `hrefs` in the response bodies.
IMHO that's not true. We could argue that the REST name is abused, but it's the word commonly used to describe a stateless API that use HTTP with URI trough verbs (GET, POST, PUT, DELETE, PATCH).
This article seems opinionated towards gRPC
But he addressed some issues with OpenAPI I constantly struggle with. And the fact that seemingly none is able to say what the standard is for certain patterns. And don’t get me started with OData …
I've never understood why so many code generators are so fiddly. They are supposed to parse text and produce text as output. You would think that it would be possible to do this without involving all manner of junk dependencies.
It reminds me of what I refer to as "the most important slide in software engineering". It was a slide by Sterling Hughes (PHP, MyNewt etc) from a presentation I can no longer remember the details of. But the slide simply said "nobody cares if it works for you". In the sense that if you write code that other people are supposed to use, do make an effort to put yourself in their place. Sterling was probably 16-17 at the time, and worked as a paid intern where I worked. But despite his young age, he managed to express something that most of us will never fully take on board.
Whenever I get an OpenAPI yaml file instead of a client library for some system I know things are going to be frustrating.
Would've been nice if they talked about how schema evolution is different in both cases, bidirectional streaming, or performance differences for different workloads
Specifically, if you can't maintain those .proto mess inside one single source of truth, you're probably fucked.
If devs are afraid of updating .proto and adding many `context` or `extra` or `extension` fields, you are fucked. Get rid of gRPC ASAP!
Look are your .proto definitions, if there are tons of <str,str> mapping or repeated key-value pairs, just forget gRPC, use JSON.
Need performance? Use msgpack!
OpenAPI is not similar to gRPC because it's noun-oriented, not verb-oriented. gRPC is more like SOAP: ignore HTTP semantics and write method calls and we'll sort it out. OpenAPI is somewhere on the path to full REST: few verbs; lots of nouns.
An RPC API can happily exist over plain old HTTP/1 (no protobuf required) and it also doesn't mention the primary benefit of RPC over REST/RESTish (IMO) - and that's the ability to stack multiple RPC calls into a single request.
It's different if you've drunk the microservices koolaid but for normal projects it doesn't help generate front-end client API libs like you'd hope.
Isn't that WHY you go to investors? To get the funding to hire to get it to market?
I was writing some python code to interface with etcd. At least at the time there wasn't a library compatible with etcd 3 that met my needs, and I only needed to call a couple of methods, so I figured I'd just use grpc directly, no big deal right?
So I copied the proto files from the etcd project to mine, then tried to figure out how to use protoc to generate python client code. The documentation was a little lackluster, especially on how to generate annotations for use with mypy or pyright, but whatever, it wasn't too hard to figure out the right incantation.
Except it didn't work. The etcd proto files had some annotations or includes or something that worked fine with the golang implementation, but didn't work with the Python implementation. I thought the proto files were supposed to be language agnostic. Well after a couple hours of trying to get the files working as is, I gave up and just modified the proto files. I deleted most of it, except for the types and methods I actually needed, got rid of some annotations, and I think I ended up needing to add some python specific annotations as well.
Then I finally got some python code, and a separate file for type annotations. But I still have issues. Eventually, I figured out that what was happening was that the package hierarchy of the proto files, and imports in those files has to match the python package names, and it uses absolute, rather than relative, imports. Ok, so surely there is an option to pass to protoc to add a prefix package to that, so I can use thes files under my own namespace right? Nope. Alright, I guess I have to update these protoc files again. It'll be a pain if I ever need to update these to match changes upstream.
Ok, now the code is finally working, let's make sure the types check. No. MyPy gives me errors. In the generated code. At first I assume I did something wrong, but after much investigation, I determine that protoc just generates type annotations that are not just wrong, but invalid. It annotates global variables as class variables, which MyPy, rightly, complains doesn't make sense.
To fix this I resort to some hackery that I saw another python project use to fix the import issue I mentioned earlier: I use sed to fix the pyi file. Is it hacky? Yes, but at this point, I don't care.
I assume that other people have had a better experience, given its popularity, but I can't say I would be happy to use it again.
Bizarre definitions of commonly used words. Huge emphasis on in-house tech, which is mediocre at best. Extraordinary claims supported by fictional numbers.
I think, there used to be a culture where employees scored some brownie points by publishing blogs. You'd need those points to climb the ranks or to just even keep your job. This blog reads as one of those: nothing of substance, bunch of extraordinary claims and some minutia about Google's internal stuff that's of little consequence to anyone outside the company.
I mean... choosing gRPC of all things to illustrate RPC, when there's actual Sun's RPC in every Linux computer is just the cherry on top.
> REST uses a resource identifier to identify the particular resource involved in an interaction between components.
(And it goes on to cite URLs as an example of a resource identifier in REST as applied to the modern web; note that "REST" is an architectural style to describe the design of systems, the web is an application of that style.)
Many allegedly RESTful APIs simply don't do that, and instead you'll see something like,
{"id": 32, …}
Particularly so when combined with tightly coupled URL construction.There are other facets of REST that you could compare to most JSON/HTTP APIs and find that they don't obey that facet, either.
The idea many of you were literally raised with, that you have to look up an application's specific functions, and write your own code to specifically map to the other application's specific functions? That basically didn't exist before, like, 2000.
Look at any network protocol created before HTTP (that wasn't specific to a single application). A huge number of them (most of them?) are still in wide use today. And basically none of them require application-specific integration. FTP, SSH, Telnet, SMTP, DNS, TFTP, HTTP, POP3, SUNRPC, NNTP, NTP, NetBIOS, IMAP, SNMP, BGP, Portmap, LDAP, SMB, LDP, RIP, etc. All layer-7, all still used today, decades after they were created. And every single application that uses those protocols, is not custom-built to be aware of every other application that uses that protocol. They all just work together implicitly.
There's almost no benefit to even using gRPC, OpenAPI, REST, etc. You could come up with a completely new L7 protocol, and just say "if you want to be compatible with my app, you have to add support for my new protocol. here's my specification, good luck.". Sure there are benefits on the backend for transmogrifying, manipulating, re-routing, authenticating, monitoring, etc the underlying protocols. But as far as the apps themselves are concerned, they still have to do a ton of work before they can actually communicate with another app. One other app.
Now it's a feature. People brag about how many integrations they did to get app A to work with apps B, C, D, E, F, G. Like Oprah for protocols. "You get custom code, and you get custom code, and you get custom code, and you get custom code! You all need custom code to work with my app!"
You could say, oh, this is actually wonderful, because they're using a common way to write their own layer-8 protocols! But they're not even protocols. They're quirky, temporary, business logic, in a rough specification. Which is the way the big boys wanted it.
Corporations didn't want to have to abide by a specification, so they decided, we just won't support any applications at all, except the ones we explicitly add code to support. So application A can talk to apps B and C, but nothing else. It's ridiculous. We regressed in technical capability.
But it has to be this way now, because the OS is no longer the platform, the Web Browser is. No protocol can exist if it's not built into the browser. The bullshit people try to sell you about "middleboxes" is bullshit because middleboxes only matter when all the apps are on a Web Browser. Take away the web browser and middleboxes have no power. If the entire internet tomorrow stopped using HTTP, there would literally be no choice but to do away with middleboxes. But we won't go there, because we won't get rid of the web browser, because we like building abstractions on abstractions on abstractions on abstractions on abstractions. People get dumber, choices get smaller, solutions get more convoluted.
C'est la vie. The enshittification of technology marches on.
I don't really understand this criticism. FTP and HTTP are equivalent, and you can serve all the apps on HTTP by implementing HTTP, just as you can send any file over FTP by implementing FTP. The apps that sit on top of HTTP are of course going to have custom integration points. They all do different things.
Some of those APIs might be REST APIs in the strict hypermedia/ HATEOAS sense as popularized twenty years ago by some proponents of this. However, looking back that mostly did not get very popular. I actually met with Jim Webber a couple of times. He co-authored "REST in Practice", which is sort of the HATEOAS bible together with the og. HTTP spec by mr. REST Roy Fielding. Lovely guy but I think he moved on from talking a lot about that topic. He's been at neo4j for more than a decade now. They don't do a lot of HATEOAS over there. I remember having pointless debates about the virtues of using the HTTP Patch method with people. Thankfully that's not a thing anymore. Even Jim Webber was on the fence about that one.
Most people these days are less strict on this stuff and might create generic HTTP REST APIs that may or may not do silly things as making every request an HTTP POST like SOAP, Graphql, and indeed Grpc tend to do. Which is very un HATEOAS like but perfectly reasonable if you are doing some kind of RPC.
Most APIs trying to do some of notion of REST can and probably should be documented. For example using OpenAPI.
Most modern web frameworks support OpenAPI directly or indirectly and are nominally intended to support creating such REST APIs. There's very little reason not to support that if you use those. Things like Spring Boot, FastAPI, etc. all make this pretty easy. Your mileage may vary with other frameworks.
Grpc is a binary RPC protocol that gets used a lot for IMHO mostly invalid reasons and assumptions. Some of those assumptions relate to assuming applications spend a lot of time waiting for network responses and parsing to happen and that making responses smaller and easier to parse makes a significant impact. That's only true for a very narrow set of use cases.
In reality, textual responses compress pretty well and things like JSON parsers are pretty fast. Those two together mean that the amount of bytes transferred over the network does not really change significantly when you use Grpc and the time waiting for parsing relative to waiting for the network IO is typically orders of magnitudes less. Which leaves plenty of CPU time for parsing and decompressing stuff. This was a non issue 20 years ago. And it still is. I routinely added compression headers to web servers twenty years ago because there were no downsides to doing that at the time (minimal CPU overhead, meaningful network bandwidth savings). Parsers were pretty decent 20 years ago. Etc.
Using RPC style APIs (not just grpc) has two big issues:
- RPC protocols tend to be biased to specific implementations and languages and rely on code generation tools. This can make them hard to use and limited at the same time.
- They tend to leak internal implementation details because the APIs they expose are effectively internal APIs.
The two combined makes for lousy APIs. If you want an API that is still relevant in a decade or so, you might want to sit down and think a little. A decade is not a lot of time. There are lots of REST APIs that have been around for that long. Most RPC APIs from that long ago are a bit stale at this point. Even some of the RPC frameworks themselves have gone a bit stale. Good luck interfacing with DCOM or Corba services these days. Or SOAP. I'm sure there's a poor soul out there wasting time on supporting that shit in e.g. Rust or some other newish language. But don't get your hopes up.