Tailored Shapes

Putting words to shapes

PEST

| Comments

I had a thought.

REpresentational State Transfer is a very cool concept, especially if you want to build a very read heavy API. But it suffers from exactly the same problems as a lot of other technologies once two or more actors want to do change the same piece of data at the same time. There are no transactions and there are no clues as to what the client thought the data was when they changed it. What are you supposed to do?

It also has legacy problems in terms of support for the more exotic verbs “PUT”, “DELETE” have wider support, but you can still have problems with legacy browsers and firewalls.

This left me asking myself some questions.

How do we scale massively parallel writes from independent actors?

Persistent data structures.

How do they work?

Trees?

What else represents a tree?

URLS.

Hmmmmm.

So what would you get if you put a RESTful interface on top of a persistent tree?

PErsistent State Transfer (PEST)!

Read

GET http://somedomain/someresource.json
200
{"head": {}, "history": []}

I’m going to call this the atom. It represents the current state of the object and how to interrogate its history. It is not cachable at all and is the single point of flux.

CREATE

POST http://somedomain/someresource.json
BODY {"key": "value"}
302 "http://somedomain/someresource/18jsh18s2.json"
{"key":"value"}

You get a cachable response in the form of a 302. The resulting value at /someresource/18jsh18s2.json is permanent and could be cached forever.

GET http://somedomain/someresource.json
200
{"head": {"key": "value"}, "history": ["http://somedomain/someresource/18jsh18s2.json"]}

This is the response you would get if you interrogated the atom now.

Update

POST http://somedomain/someresource/18jsh18s2.json
BODY {"key": "another value"}
302 "http://somedomain/someresource/128sj1054.json"
{"key": "another value"}

Pushing to the value gives you a 301. A permanent redirect. Your browser and client should feel free to treat all requests to this end point as meaning that you really meant the newer 128sj1054.json.

GET http://somedomain/someresource.json
200
{"head": {"key": "another value"}, "history": ["http://somedomain/someresource/128sj1054.json", "http://somedomain/someresource/18jsh18s2.json"]}

The atom has been updated, as has its history.

Delete

POST http://somedomain/someresource/18jsh18s2.json
BODY {}
302 "http://somedomain/someresource/81sh13sga.json"
{}

Deletion just marks the entity as empty. Didn’t mean to delete? Repost the value at history[1].

GET http://somedomain/someresource.json
200
{"head": {}, "history": ["http://somedomain/someresource/81sh13sga.json", "http://somedomain/someresource/18jsh18s2.json"]}

Conflict

Posting to a resource already modified by another actor

POST http://somedomain/someresource/18jsh18s2.json
BODY {"key": "another value"}
403

GET http://somedomain/someresource.json
{"head": {"key": "winning value"}, "history": ["http://somedomain/someresource/281s1sk1.json", "http://somedomain/someresource/18jsh18s2.json"]}

Now resubmit, and hope for the best!

This has some fun features:

  • Only GET and POST
  • Super cache-able. You can push your responses out to the CDN and treat them as static content.
  • No transactions, but a clear path for conflict resolution
  • I’ve used psuedo-hashes of the content as their ID. They could be integers, timestamps, salted-hashes.
  • I’ve used JSON, but there is nothing preventing the use of other formats
  • Time is now built in to the value in order to represent state.
  • Clients can complete their processing on data from their copy of head in complete confidence

I could foresee using query parameters in the same way REST does. For example:

GET http://somedomain/someresource.json?start=1
200
{"head": {}, "history": ["http://somedomain/someresource/18jsh18s2.json"]}

or history searches

GET http://somedomain/someresource.json?key=value
200
{"results": ["http://somedomain/someresource/18jsh18s2.json"]}

Comments