Skip to content

Instantly share code, notes, and snippets.

@peralmq

peralmq/model.md Secret

Last active August 29, 2015 14:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save peralmq/04c390554bf6f1b0866e to your computer and use it in GitHub Desktop.
Save peralmq/04c390554bf6f1b0866e to your computer and use it in GitHub Desktop.
Clue's cycle data model for CouchDB

The Clue app has several categories and each category has a selection type. These selections are all bound to a particular day and we collectively call them day measurements. The data model should support that every selection can be changed independently by users.

Selection types

Here follows a list of selection types and examples based on category:

  • single select - one selection per day (ex. Period, Fluid, Pill) - Check out [1] for an example
  • multi select - several selections per day (ex. Sex, Pain) [2]
  • tags - several selections per day and user customizable values (ex. Tags) [3]

Flat data structure

We want to have the data modelled in a very flat data structure where we have a CouchDB document per selection and day (we don't nest a day's selections in a day object, because we think that will lead to more conflicts, is this correct?).

So instead of:

{
  "day": {
    "value": 200,
    "selection1": "headache",
    "selection2": "cramp"
  }
}

we do something like:

{
  "day": 200,
  "selection1": "headache",
},
{
  "day": 200,
  "selection2": "cramp",
},

IDs

We also want to incorporate as much of our domain's constraints as possible into the CouchDB model, because CouchDB is better than we are at detecting conflicts. This has led us to make use of the document's _id-key to enforce that unique selections end up in same document. One problem we've faced and solved is to map our different types of selections to describable unique identifiers. This is what we came up with:

  • single selections uses category + day, i.e. period|2015-01-30
  • multiple selections uses selection + day, i.e. headache|2015-01-30
  • tags uses the tag type + day, i.e. tag|2015-01-30|BACK PAIN

In a CouchDB document it will look like this:

{
  "_id": "headache|2015-01-30"
}

Intra CouchDB document conflict resolution

As mentioned before, we rely on CouchDB's conflict detection even for our domain logic. But how to resolve the conflicts? Our first approach will be to do this resolution as automatic as possible and not bother the end-user at all. Time will tell if this is good from a UX point of view. We will store a client local timestamp (called $timestamp) in all documents and upon a conflict we will let the revision containing the last timestamp win. We will use CouchDB's tomb-stoning to mark the looser(s) of the conflict, that is the _deleted key.

So, we have the following two conflicting revisions of the same document:

{
  "_id": "period|2015-01-30",
  "_rev": "1-abc",
  "body": "heavy",
  "$timestamp": "2015-02-04T01:56:24+00:00"
}

and

{
  "_id": "period|2015-01-30",
  "_rev": "2-bcd",
  "body": "light",
  "$timestamp": "2015-02-04T01:57:24+00:00"
}

The latter wins and the first one is tomb-stoned.

{
  "_id": "period|2015-01-30",
  "_rev": "1-abc",
  "body": "heavy",
  "$timestamp": "2015-02-04T01:56:24+00:00",
  "_deleted": true
}

Un-selection

We have a special state for selections and that is that they can be selected and un-selected. Our approach to model un-selection is to add a $removed to the document. (We first tried to model it with the _deleted but that had too many other implications in CouchDB. Is there a better way? Would pruning work here?).

Here is an example of un-selection.

{
  "_id": "headache|2015-01-30",
  "_rev": "13-6c4ceb1f5eee6b62d52ab321c99a2513",
  "$timestamp": "2015-01-30T18:31:50.083+05:30",
  "$removed": true
}

Pruning

CouchDB supports pruning and compacting the database. This means removing history and revisions from the document trees. Is this a good idea for us to use? We would very much like to do this on our clients as it saves memory. Can/Should we also prune and compact in the backend? If not, would only pruning/compacting on the client then create a lot of extra requests upon sync?

End result

{
  "_id": "headache|2015-01-30",
  "_rev": "13-6c4ceb1f5eee6b62d52ab321c99a2513",
  "$timestamp": "2015-01-30T18:31:50.083+05:30"
}

References

[1] Single select Single select [2] Multi select Multi select [3] Tag select Tag select

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment