High-level API design

2018-01-09 - Future - Tony Finch

This is just to record my thoughts about the overall shape of the IP Register API; the details are still to be determined, but see my previous notes on the data model and look at the old user interface for an idea of the actions that need to be available.

Context

The IP Register API will provide programmatic access to the database for third-party clients; I would also like to use it for building the new user interface (i.e. dogfooding the API). My vague plan is for a modern web front end using TypeScript in the browser to directly access the API.

As is usual for modern services, the API will be based on JSON over HTTPS.

API style and transactions

Moving a service in the DNS requires multiple actions: from the DNS UPDATE point of view, delete followed by add; from the SQL point of view, DELETE followed by INSERT, etc. These have to be performed as an atomic transaction to avoid outages due to queries that happen between delete and add.

A transaction in the UI needs to correspond to a transaction in the API, and in SQL, and in the DNS.

This sort-of implies that some common API styles aren't a good fit:

  • Simple RPC doesn't fit because we effectively want to make multiple RPCs in one transaction;

  • REST doesn't fit because we want one request to affect multiple resources.

Instead, the style I favour is more like a DNS UPDATE request, i.e. a list of update actions, although each action will be higher-level.

API endpoints

The API basically needs two endpoints:

  • /query for read-only lookups and searches;

  • /update for transactional modifications.

Query endpoint

The /query endpoint will accept GET requests with URL-encoded lookup and/or search parameters (details TBD).

Update endpoint

I outlined below that the user interface needs two kinds of update: a one-shot update for simple cases; and a shopping-cart style UI for constructing more complicated transactions piecemeal.

The shopping-cart UI implies the need for some per-user state in the database that saves the user's pending transaction. I want an edit action in the UI to translate to an edit action on the server, and not simply upload a replacement copy of the pending transaction. When a user has multiple active instances of the UI open, their state will get out of sync (unless we do complicated things with web sockets) so it's likely that replacing the pending transaction will a overwrite a fresh version with a stale version, whereas an edit action should be safer.

A good way to describe edits to pending transactions is JSON Patch, RFC 6902, since it is standard and simple.

The /update endpoint will support the following HTTP methods:

  • GET a copy of the pending transaction. When there is no pending transaction it will return an empty array of actions, [].

  • PUT a complete replacement of the pending transaction.

  • PATCH to edit the pending transaction with JSON patch.

  • POST a zero-byte body, to perform the pending transaction.

  • POST a transaction to perform it immediately, without affecting the pending transaction. This is the normal method for programmatic API calls, and for simple one-shot UI actions.

Update syntax

An update request is an JSON array of update actions; each action is described by a JSON object (details TBD). It should be easy to translate a DNS UPDATE request into a JSON-over-HTTPS update request.

But also, an update request looks a bit like a JSON Patch request. I hope this won't be confusing!

Update response

In general an action will need to return some result, such as when automatically allocating IP addresses.

This means the response should be an array of results corresponding to the array of actions. A result might be OK (possibly with additional data), or an error, or skip if another action failed and this action was rolled back.

A consequence of this is that the HTTP status code won't express the application-level status like REST APIs often do, but instead it will just refer to the HTTP-level status.

Versioning

This framework is simple enough that I hope I won't need a top-level revision number. Instead I plan to just extend the query parameters and the update actions as required.

The plans I have for changing the data model mostly involve relaxing the rules, e.g. making the metadata fields optional and generalizing them, which is easy to do compatibly.

Initially there will need to be separate actions for boxes and vboxes; when this distinction is abolished, it should be possible to make the actions into close synonyms.

A more tricky area is cross-mzone aliases; we can probably restrict the current unprivileged cases before opening up the API, in order to avoid getting into an even more sticky situation.