RESTful API/Data Modeling – How to model an subordinate resource that will only ever have one element

api-designdata modelingrest

I'm creating an API and data model for a multi-user document-creation application and I'm having trouble with a particular part of the data model/API design. Below is my current line of thinking:

There are two similar actions that can happen while a user is writing a document:

  1. A user can manually save a document. This creates a revision and there can be many revisions for a single document.
  2. An autosave is triggered periodically while a user is editing the document. Because autosaves can happen fairly frequently and have redundant data, there can only be one autosave for a single document.

These actions are used to create a couple of features:

  1. Users can revert to an old revision of the document.
  2. Autosaves prevent the user from losing their work.
  3. Basic document locking – if a user creates an autosave for a document, then another user cannot begin editing that document. When a user triggers a manual save, creating a revision, the autosave associated with the document will be deleted, and therefore another user could start working on the document then.

Here's an outline of the data model I have:

 _________           ______________________
|Documents|         |DocumentRevisions     |
|---------| ------< |----------------------|    
|id: int  |         |id: int               |
|_________|         |documentBody: string  |
     |              |createdAt: datetime   |
     |              |______________________|
 ____|________________
|DocumentDrafts       |
|---------------------|
|id: int              |
|documentBody: string |
|_____________________|

And here are some of the endpoints I have:

  • GET /documents/
  • POST /documents//revisions
    • This is called on a manual save
  • PUT /documents//draft
    • This is called on autosave

These are the things I don't like about this current model:

  • The POST /documents/<documentId>/revisions endpoint has to also modify the DocumentDrafts table to reflect the fact that the document is now in a "clean" state and can be modified by another user
  • The PUT /documents/<documentId>/draft endpoint breaks a few rules of REST including the use of the singular word "draft" (because there can only be one draft of a document)

I think the requirement that only one autosave can exist for a document at any point is really tripping me up and I'm not sure how to model it correctly. Is there a better way? Do I need to lift the requirement of a single autosave per document?

Best Answer

The POST /documents/<documentId>/revisions endpoint has to also modify the DocumentDrafts table to reflect the fact that the document is now in a "clean" state and can be modified by another user

Different layer. It is important to understand that it is usually not desirable to tightly couple the API layer to the service and/or data layers.

The PUT /documents/<documentId>/draft endpoint breaks a few rules of REST including the use of the singular word "draft" (because there can only be one draft of a document)

There is no such rule. For starters, REST says exactly nothing about what URIs should look like. Beyond that, it is generally accepted in web API design that singular words are reasonable when only a single instance of the resource exists.

In your shoes, I'd use these endpoints:

/documents
/documents/{id}
/documents/{id}/draft
/revisions
/revisions/{id}

A user can GET a document. This will contain a link or a subresource with the most recent revision. To start editing, they POST to draft. This will either return 201 Created (they are now editing) or 409 Conflict (somebody else is already editing). The user who is editing will PUT to draft. To cancel editing without saving, the user can DELETE draft. To commit the draft, they POST to /revisions. A side effect of this will be to DELETE the draft. Alternately, you can require a manual DELETE of draft by the client to terminate the edit.

Now the only problem you have is what happens when the user starts an edit and goes on vacation without cancelling or committing it. :-)

Related Topic