
Post-REST - mpweiher
https://www.tbray.org/ongoing/When/201x/2018/11/18/Post-REST
======
cryptonector
REST for the control plane works because there are few enough "resources" that
you can have a URI for each and CRUD works. Much more importantly, the
pressure to have complex multi-resource transactions is low because the
control plane tends to have simple schemas and there is no need to do complex
multi-resource transactions with any kind of atomicity.

The data plane is not at all like that. There can be many many resources, and
very complex transactions.

PostgREST, a REST interface to PostgreSQL, shows you can have REST for the
data plane, but it achieves that by forcing one to have simple transactions
with any and all complexity wrapped into PG functions so that the REST
interface can remain simple.

The place where REST falls down, really, is transactions. For simple enough
schemas and transactions involving only one relation, it's easy enough to have
URIs for resources and collections. But for anything much more complex than
that REST simply fails. Instead one ends up writing an end-point that accepts
POSTs whose request bodies encode complex transactions, and this ends up
resembling SOAP (basically RPC over HTTP) rather than REST.

Imagine an HTTP/1.1 verb named TRANSACT whose body consists of a concatenation
of the requests that you really want to do, and with a header controlling /
reflecting transaction control options (e.g., atomicity). If HTTP had such a
thing, then it would be a lot easier to build a proper RESTful interface to
the data plane. But HTTP has no such thing, and probably never will.

Incidentally, it's useful to consider how NFSv3 had RPCs for every little
thing, while NFSv4 has only one RPC (COMPOUND) that is... a lot like the
TRANSACT I imagine above -- it's just not that crazy a thing to imagine. The
transaction complexity problem in HTTP is not unique to HTTP!

~~~
geezerjay
> The place where REST falls down, really, is transactions.

I don't agree. The standard way of supporting transactions with REST is to
provide a transactions endpoint where clients start a transaction by POSTing a
new transaction resource, POST their DTOs to build up a unit of work, and when
they are done just commit the transaction. That's pretty much a one-size-fits-
all solution as that's the standard way to implement transactions at the
database level.

~~~
dustfinger
I was going to say the same thing and was pleased to see that someone beat me
to it. If you combine unit of work pattern with continuation tokens then you
can handle bulk transactions involving very large numbers of records with a
simple rest interface.

------
burtonator
I think one big win could be to just hide the underlying APIs with better
tooling.

For example, with GRPC your client bindings are automatically compiled and you
just use them.

Is it using protocol buffers? Do you care?

To a certain extent we do care. A lot of these things "just work" out of the
box but there are some practical real world implications here.

At Datastreamer we push about about 100GB of JSON to our customers per day.
This is all news and blog content.

Sometimes our customers complain that using JSON is a waste. Realistically
though it costs them about $150 extra per month to parse the JSON vs using
protocol buffers.

We made this decision because implementing JSON is a LOT easier than
implementing protocol buffers.

Most of our customers end up putting a junior or intermediate engineer on the
implementation and they're already familiar with JSON.

However, if the tooling hides the encoding and even the wire protocol this
means we can upgrade faster.

We're still not at a point yet where the tooling is free. You tend to get
stuck around something like GRPC or GraphQL anyway.

Hopefully this is resolve in the future and we can get all this infrastructure
optimization for free.

~~~
denormalfloat
> Is it using protocol buffers? Do you care?

I don't, but other people do. For anything low latency, the cost of
encoding/decoding starts to become extremely difficult to avoid.

But that said, I'm pretty sure GRPC can use JSON internally, so it's up to you
which encoding to use.

~~~
philwelch
> For anything low latency, the cost of encoding/decoding starts to become
> extremely difficult to avoid.

Whether this is an argument against JSON and in favor of Protobuf or vice-
versa depends on which language you're operating in. Most benchmarks I've seen
show that Protobuf is faster to encode and decode for Java and Golang while
JSON is faster for Python and Javascript.

IMO, if you're worried about low latency, you're also likely using a compiled
language where Protobuf will be faster anyway.

------
deathanatos
> _Set­ting up and tear­ing down an HTTP con­nec­tion for ev­ery lit­tle
> op­er­a­tion you want to do is not free._

"Doctor, it hurts when I do this." So don't do that; many HTTP libraries come
with connection pooling / persistence built in, so it's quite common to not
even need to handle this yourself. Should we also stop using relational
databases because setting up a connection takes time?

As for the rest of it, sure, you might have a queue, or some orchestration
layer. Is that your consumer-facing API? Just b/c I push a message into some
queue implementation does not mean I want to expose that implementation to my
caller.

~~~
jimbokun
He has an entire section on "Post-REST: Per­sis­tent con­nec­tions" which you
seem to have missed.

"As for the rest of it, sure, you might have a queue, or some orchestration
layer. Is that your consumer-facing API? Just b/c I push a message into some
queue implementation does not mean I want to expose that implementation to my
caller."

REST for external APIs, and queues for internal, more tightly coupled micro
services seems like a good way to architect things.

~~~
otabdeveloper1
> He has an entire section on "Post-REST: Per­sis­tent con­nec­tions" which
> you seem to have missed.

That section is really stupid. He goes on about HTTP/2 and QUIC while
seemingly being ignorant of HTTP/1.1's existence.

Keep-alive has existed for decades. Use it, it solves the problem definitively
without adding any new ones.

(People have yet to figure out what the point of HTTP/2 and QUIC was. Except
for Google's not-invented-here syndrome, of course.)

------
turblety
I get the points of why people don't like REST, and especially the latency
argument is quite true, however somewhat mitigated by http2. But I like that
REST is quite easy for developers to reason about.

I actually built a websockets library (more proof of concept) [1] that uses a
rest like syntax, implementing something similar to an express API. It exposes
both a websockets server and http. It's FAR from anywhere near complete and
just a concept. I have no doubt there are better solutions out there, but I
just don't think REST is all that bad.

But I do also love @burtonator's idea of maybe not using JSON, but hiding the
complexity of something like RPC.

[1]
[https://github.com/markwylde/restocket](https://github.com/markwylde/restocket)

~~~
denormalfloat
REST is a good fit if the operations you want to do are CRUD, or if the data
model you have is hierarchical. This is for most people, but it breaks down
when there involve multiple steps. A bank transaction is a good example,
because it involves multiple updates that need to be done atomically. Mapping
this to REST is complex. It's possible of course, but more complicated than
the equivalent RPC counterpart. Hence, REST is not better in some common
cases.

~~~
anentropic
REST resources don't have to map 1-to-1 with your db tables

The transaction itself can be a resource

This seems almost maybe an argument against taking microservices too far until
a single service can't own the whole transaction

~~~
vidarh
This is a great point, and I think this is a major point of confusion with
REST.

People think of resources as a table or row with CRUD operations rather than
in the wider sense as an object with a generic set of operations to trigger
transitions to a new state.

It's worth reiterating that REST operations are conceptually much more generic
than CRUD. Nothing says that what you GET or PUT/POST need correspond directly
to attributes of an object or columns in a database - in the widest sense they
simply represent the current state of a resource and a state transition you
wish to invoke respectively.

The point is not to (only) encode persistent state, but _any_ state. In a
sense representing state that is not persisted on the server is more essential
- resources that are persistent on the server can be operated on without
always sending the full state. But information that is not persisted on the
server _needs_ to be encoded and transmitted with the request/response in some
way.

------
crb002
Reposting here. At Global Day of Code Retreat this past weekend I was
surprised that our Game of Life implementation in SQL was the easiest.

Why are we doing data schema type erasure at the Application layer? The
"flexibility" and "encapsulation" aren't worth the manual hell of hand coding
it back in all the way to the front end.

Why not expose SQL schemas all the way to the front end? Even if the
Application layer makes a view that the data store doesn't know about,
everything downstream could benefit through automation.

From there you get into issues mostly having to do with APIs that can be
paginated, and how to optimize those connections to balance caching with
network latency.

~~~
jmull
> Why not expose SQL schemas all the way to the front end? Your API is a
> promise and if you break your promise bad things happen.

If your API is SQL against some tables, you're promising you won't make any
breaking changes to that -- or else you're promising you can roll out an
update to all the servers and clients at the same time.

SQL against tables has a very large surface area, and there are many details
that are different depending on the version of your database software. So,
you'll find you can't update your database software without also updating (and
testing) all of your clients. The more complex and more widely distributed
your software is, the more time-consuming and expensive it will be to update
anything. If you have clients outside your direct control and running on the
same release cycle, you'll find you can't update your database software at all
without breaking your clients.

~~~
TheDong
> SQL against tables has a very large surface area, and there are many details
> that are different depending on the version of your database software

So you only offer an ANSI SQL compatible parser, not specific to any database
(and it would then be compatible / compile-able to postgres, sqlite,
whatever), and you only expose specific views into the database.

Once you create such a public view, you treat it as immutable, and all API
changes require new views. Each old view must function forever.

This is not actually all that different from REST apis today; we version them,
old versions must exist forever (or all clients must be updated), you can't
update your server software that serves the rest API without being darn sure
it's backwards compatible...

The only real difference is we encode our CRUD operations in a non-sql
language right now (ad-hoc json schemas usually) vs encoding them as the ANSI
subset of SQL against some fictional database views.

All of your complaints apply equally well to REST apis I think.

~~~
jmull
You’re ignoring the massive surface area of SQL, ANSI or no. You’re going to
have to maintain that — for each of your API versions, including any/all
semantics, quirks, etc clients may rely on.

It’s a much larger commitment than you need to be making for many, many APIs.

------
tuukkah
> _To start with, it’s not as though client de­vel­op­ers can com­pose
> ar­bi­trary queries, lim­it­ed on­ly by the se­man­tics of GraphQL, and
> ex­pect to get uni­form­ly de­cent per­for­mance. (To be fair, the same is
> true of SQL.)_

Maybe I'm missing something, but isn't that exactly what you can expect from a
GraphQL API as a sweet spot between SQL and REST? Semantics of GraphQL enable
a schema-dependent set of queries, not arbitrary queries.

> _Any­how, I see GraphQL as a con­ve­nience fea­ture de­signed to make
> syn­chronous APIs run more ef­fi­cient­ly._

For asynchronicity, GraphQL has subscriptions.

That being said, I agree GraphQL (of today at least) won't be a solution to
every problem and I bet transports such as HTTP/2 and HTTP/3 will cause new
things to be built which don't look like just a faster REST, GraphQL or MQTT.
I would mention event sourcing and persistent logs in addition to just
stream/queue processing.

Tooling (like API management gateways and API consoles) is not still all there
for GraphQL and MQTT either.

~~~
cryptonector
> Semantics of GraphQL enable a schema-dependent set of queries, not arbitrary
> queries.

I mean, in SQL useful queries are generally schema-dependent. Sure, you could
have XPath/JSONPath/whatever extensions that allow you to query something
closer to free-form documents, but your queries will still be schema-dependent
in some way.

~~~
tuukkah
SQL schema defines data and you cannot limit which queries are available on
that data, whereas GraphQL schema defines both data and which queries are
available.

------
anentropic
I stumbled on this the other day [http://rsocket.io/](http://rsocket.io/)

Does anyone have an opinion if it's a good idea and likely to gain adoption?

It sounds to me like it implements the sort of thing being discussed in this
post

~~~
woody83
RSocket is gaining use at Facebook and is beginning to get traction at
Alibaba. Spring 5.2 appears that it will also have first-class support for
RSocket. There is also a company, founded by some of the creators of RSocket,
that are building an open-source application development platform for it
called Proteus.

------
rb808
The article is named Post-REST, but the summary says: "...REST still pro­vides
a good clean way to de­com­pose com­pli­cat­ed prob­lem­s, and its ex­treme
sim­plic­i­ty and re­silience..."

Is there a reason not to use REST then? I didn't quite understand.

~~~
kh_hk
Yes and no. Should you keep using HTTP calls for adding an item to a cart?
Possibly. Should you use HTTP calls to communicate between services, let's
say, to poll a resource every second and know if a worker has finished, when
there are better systems and there's good enough technology available to
support a higher throughput? Not so much.

~~~
bduerst
So in summary - HTTP calls for front-end, but not back-end to back-end
services?

~~~
lotyrin
Not exactly.

Transactional CRUD (post a form, submit an email, add to cart) is pretty well
adopted and standardized as HTTP, so probably use that (even backend to
backend, if the contract fits CRUD resource semantics).

RPC (non-CRUD interactions) or streaming updates (e.g. a spinner in a app
representing a pending job) or low latency (chat messages / presence status,
etc.) don't really fit to HTTP requests so great (requiring e.g. polling) so
you should look at alternatives (probably something proxied over a websocket
in the case of a browser front-end app).

------
Dowwie
Where are the tech manager blog posts that discuss time, money, and effort in
addition to tech pros and cons?

GraphQL and SPAs are unconstrained tech solutions with questionable upside.
Only well funded organizations can afford to throw money at projects that are
more about attracting engineers than improving the bottom line.

You don't need a huge marketing campaign and a non-profit institution to drive
adoption of technology that has a compelling value proposition.

------
VBprogrammer
I was recently working on an API where we decided to use protobuf. I remember
the first time I saw the message as encoded by protobuf; it really made me
aware of how wasteful JSON can be. I know that usually that all comes out in
the gzipping but there is nothing quite like seeing half a page of JSON become
1 line of gibberish to demonstrate the point.

~~~
XCSme
Protobuf is really slow with encoding/decoding and also does unnecessary
validation of the data. If you are just starting out with binary data
serialization I recommend FlatBuffers instead of protobuf.

~~~
Matthias247
Really slow compared to what? Json? A handwritten binary encoding? For most
use cases protobuf should be fast enough. I also don’t understand the
unnecessary validation part. If you receive data over the network you should
always validate it. Even if it’s from within the same company network

------
peterwwillis
Problems with these assumptions:

\- QUIC. Yes, it's UDP, so its connections don't behave the way TCP ones do,
but it's still based on IP and UDP connections, so it still breaks down under
a number of circumstances (rotating backends, mobile clients changing
connection types, firewalls/proxies, etc)

\- HTTP-based. If you use HTTP, you must accept its warts, like being
stateless. Even considering a transport protocol that can multiplex
connections, you still need to track all these individual stateless calls.
Perhaps a protocol that took care of state might give you some benefits (such
as workflowing?)

\- Workflow orchestration is just distributed centralized state machines. It
shouldn't have much to do with a replacement for REST, because the API isn't
necessarily controlling a state machine, and the API (in this case HTTP) isn't
stateful anyway...

Why don't we evolve APIs into a sort of language? Why make 4 separate requests
(PUT this, GET that, munge it, POST a result, GET to confirm) when you know
exactly what you want to do? We can instead build a language or spec where we
can shove a variety of instructions (even a "workflow") into a request, let
the backend handle the method of accomplishing it for us, and retrieve the
result. This can be extended throughout microservice architectures.

The transport protocol is a separate issue. So you want lower latency, and you
want less round trips, and you want to survive network interrupt. Design a
transport protocol for that (QUIC is a good base). If you want it to survive
even more interruption and allow clients to split networks, start looking at
intelligent tunnels with advanced routing mechanisms and spread them along the
path. But that's a lot more funky than simply replacing TCP.

~~~
jimbokun
"If you use HTTP, you must accept its warts, like being stateless."

Being stateless is one of the best things about HTTP, I wouldn't consider it a
wart at all.

State should be very, very isolated. Ideally, stored in a database somewhere.

"Why make 4 separate requests (PUT this, GET that, munge it, POST a result,
GET to confirm) when you know exactly what you want to do?"

At that point, you are implementing a database with transactions. So expose it
over a SQL interface and be done with it (see Cockroach DB). This is solving a
very different problem from most REST APIs. Exposing the ability for external
customers to run arbitrary transactions against your servers is very dangerous
in terms of provisioning, latency, and availability requirements.

Unless your business is providing a database as a service.

~~~
peterwwillis
State isn't always isolated, and for good reason. Design and operational
parameters may dictate state as part of the expected functionality. Sometimes,
because of the stateless nature of a protocol, extra work has to be done to
constantly re-evaluate an operation in progress to see if it should be
terminated early or changed. Since the operation isn't stateful, some other
separate component has to act as a sort of babysitter. Then you either have to
have the inefficiency of repeated calls, or implement your own pseudo-stateful
protocol to wait for some kind of event to be passed up to alert it of a state
change. Using the same hammer for every kind of nail is dumb.

And no, you don't need a database and transactions just to perform four
operations and return a result. Not every transaction will be affected by a
change of state in the middle of the transaction. Sometimes transactions are
duplicated, or replace a result entirely, and the latest one can win. A
transaction in progress can also provide data to another transaction later,
and the first transaction can be paused or terminated. We don't see complex
interactions like this because the databases and protocols we use have strict
separations between their parts, but we don't _have_ to do things this way.
The more functionality you surrender to a database, the less flexibility you
have in the future, because now your big database is the bottleneck for
operations.

We need more design paradigms that allow flexible operations, and protocols
that can work with such operations to manage transactions by also working with
state. I say we "need" this because it seems the state of the art of these
systems has languished under proclamations about what we can and can't do, due
to assumptions about how we must go about them, and what technology is readily
available for use. But we're talking about a _new_ protocol, so we don't have
to carry the baggage of those assumptions with us.

~~~
jimbokun
"And no, you don't need a database and transactions just to perform four
operations and return a result."

The actual request was for a composable language, with 4 operations as one
example. But a language isn't limited to just four operations.

By the way, this exists in the FHIR spec as a transaction Bundle:

[https://www.hl7.org/fhir/bundle-
transaction.json.html](https://www.hl7.org/fhir/bundle-transaction.json.html)

But that, of course, now means you are implementing transactions. Which is one
of the hardest things to implement in a distributed environment in a correct,
scalable, and performant manner. Frankly, I would be very hesitant to trust
such an implementation, if it wasn't the core competency of the developers who
wrote it.

(Or unless they are just calling something else that implements the
transactions, but at that point again you are just a very thin layer on top of
a database.)

------
adamcharnock
Plugging a personal project: I’ve been working on a project called Lightbus
[1] which has been very useful for me in creating evented systems. It is still
early days, but so far myself and a couple of others have found it useful.

[1]: [https://lightbus.org](https://lightbus.org)

------
tannhaeuser
If REST ultimately results in heavy use of message queues and async URL
responses, the question is why not use eg. AMQP or other messaging protocol
straight away rather than bother using HTTP facades/services as unecessary
SPoFs at least between backends.

~~~
jimbokun
Depends on how tightly coupled you want to be.

Directly exposing your queue to external consumers seems very dangerous.

Having a well defined and well documented REST API for external consumers, and
using queues for communicating between tightly coupled services seems like a
much better solution.

~~~
tannhaeuser
Well-defined and well-documented isn't exactly what I'd use to describe "REST"
APIs.

~~~
Dowwie
Why not?

------
pier25
Turns out most people are perfectly happy with their little SUV even if you
can't transport a couple of tons of steel or win a Formula 1 race.

It's ridiculous to think that anything can cover all use cases.

------
dogpuncher
> The idea is you get a re­quest, you val­i­date it, maybe you do some
> com­pu­ta­tion on it, then you drop it on a queue and for­get about it.

> The next stage of re­quest han­dling is im­ple­ment­ed by ser­vices that
> read the queue and ei­ther route an an­swer back to the orig­i­nal
> re­quester or pass­es it on to an­oth­er ser­vice stage.

In this scenario, what are the mechanisms by which a service could route an
answer back to the original requester?

~~~
Aeolun
My question exactly. Can’t exactly use long polling for this.

------
vemv
Looks like event-driven systems are all the rage now!

I feel there's still too much competition/innovation in the message queue
space though. Too many choices, with maddeningly different characteristics.

And seemingly you can tweak most of those to behave somewhat similarly between
them, which makes judgement even harder.

~~~
jimbokun
"I feel there's still too much competition/innovation in the message queue
space though."

Interesting, to me Kafka seems like the clear winner and the obvious choice
for all of my queuing needs.

What am I missing out on from other queuing systems?

~~~
jimsmart
I recently evaluated a bunch of different messaging systems, and found NATS to
have the widest support for different client languages, plus those clients are
also maintained by the same parent company. With other MQs we have used, we
have found much variety in the quality of the client libs. YYMV.

