Basic API

by Richard Taylor : 2019-12-20

The most basic API needs to be able to create and store things, then retrieve them later. I'll make it really simple by only worrying about one type of thing for now, the Item.

A Way In
Is this the way in?

What I want to explore is the structure of the API. You can only change things around freely if they are quite small. So it needs to be minimal; but still complex enough to surface the main choices.

New Modules

You can see from this commit that I have added three new modules.

api
storage
translator

But where are the tests? There are no unit tests for these modules, yet.

That's because I was trying to get it straight in my mind which modules I needed. Should the api module accept objects or strings or dictionaries? Without seeing it in use it is hard to say. I could have written some unit tests for api to examine its usage, but in this case it seemed like the picture would be clearer if I examined how the api fit inside the http module.

So I did have tests, but they were integration tests. For example, this "test" shows that the POST handler is working as expected:

# curl -d '{}' http://localhost:2207/v1/items | jq
{
  "id": "2d146ed4-7fc2-49fd-a929-b2f5af16f27b",
  "properties": null,
  "created_at": "2019-12-24T17:09:38.472232+00:00",
  "created_by": "e1aafc4d-32e4-44be-a00d-48ecc253f7cb"
}

And then this shows that I can GET the data back later:

# curl http://localhost:2207/v1/items/2d146ed4-7fc2-49fd-a929-b2f5af16f27b | jq
{
  "id": "2d146ed4-7fc2-49fd-a929-b2f5af16f27b",
  "properties": null,
  "created_at": "2019-12-24T17:09:38.472232+00:00",
  "created_by": "e1aafc4d-32e4-44be-a00d-48ecc253f7cb"
}

What did I learn? Well, looking at http I decided that passing strings into api would be a mistake. The http module is the thing that knows about the incoming and outgoing data formats (JSON, XML, whatever) as it sees the HTTP headers. So that module should convert those formats into objects for the api; which then only needs to understand objects not JSON, XML etc.

Also, having said that, I don't want the actual code for translating from text to objects to live in the http module. It should live in a separate module - hence the appearance of the translator module which does only that - it translates.

And the third module? It seems like a no-brainer to not tie the API to a paricular data persistence technology right from the start. So I created a storage module to house the interface to persitent storage and implemented it as an in-memory store to begin with.

Abstractions

The basic API clearly now works. I can POST an Item in and then GET it back later. In the process of making this work I have established some key abstractions which separate different functionality.

  1. Server - all the server code is in the http module. That is the only place that has to worry about HTTP headers and return codes.
  2. Translator - converting from standard formats to objects and back again is all done here.
  3. API - the server interacts with the data through this versioned interface. It is so important to build in versioning from the start with any API. You will need it sooner or later!
  4. Storage - the API interacts with the persisted data through this interface.

Unfinished Business

There is something I have thrown in to this commit which doesn't do much and is mostly there as a statement of intent. Users; all the API calls have a "requesting_user" parameter, even though there is no way to log in or register users. This is because I want to build in strong logging as soon as possible. Actual authorisation can come later.

With the basic API structure now definied, what we need next are more tests.