Skip to content
CJ Brewer edited this page Jul 13, 2016 · 6 revisions

Webcom's Backend

Webcom's backend implements a RethinkDB ODM. In order to accomplish that, the codebase implements some complex RethinkDB queries that are worth mentioning here. This particular ODM, by default, does not delete entities. It archives them. This can be configurable one day.

Entity structure

Objects stored in RethinkDB are flattened objects with each key being either a relationship or attribute. For example, the following is how a video might be stored.

{
  name: 'Jurassic Park',
  year: 1993,
  director: {
    id: '20',
    archived: false,
  },
  customers: [
    {
      id: '425',
      archived: false,
    },
    {
      id: '247',
      archived: true,
    },
  ],
}

From the above structure, we have immediate insight regarding what the attributes are, what types of relationships are which (i.e. belongsTo or hasMany) and which relationships are currently pointing to archived entities.

An entity with a hasMany relationship is archived

The ODM archives hasMany relationships. This is so the client can easily filter out the 'deleted/archived' records.

// example RethinkDB query
r.table('enterprise').get('100').update(row => ({
  'listings': row('listings').map(data => (
    r.branch(data('id').eq('1111'),
      {
        id: data('id'),
        archived: true,
      }, {
        id: data('id'),
        archived: data('archived'),
      })
  )),
}))

// relationship before archive
{
  id: '100',
  name: 'Company 1',
  meta: {
    archived: true,
  },
  relationships: {
    listings: [
      {
        id: '1111',
        archived: false,
      },
      {
        id: '2222',
        archived: false,
      },
    ],
    ...
  },
}

// relationship after archive
{
  id: '100',
  name: 'Company 1',
  meta: {
    archived: true,
  },
  relationships: {
    listings: [
      {
        id: '1111',
        archived: true,
      },
      {
        id: '2222',
        archived: false,
      },
    ],
    ...
  },
}

An entity with a belongsTo or hasOne relationship is archived

The ODM archives belongsTo or hasOne relationships. This is so the client can know to filter out the 'deleted/archived' record.

// example RethinkDB query
r.table('listing').get('1111').update({
  'company': {
    id: '100'
    archived: true,
  },
})

// relationship before archive
{
  id: '1111',
  name: 'Listing 1',
  meta: {
    archived: true,
  },
  relationships: {
    company: {
      id: '100',
      archived: false,
    },
    ...
  },
}

// relationship after archive
{
  id: '1111',
  name: 'Listing 1',
  meta: {
    archived: true,
  },
  relationships: {
    company: {
      id: '100',
      archived: true,
    },
    ...
  },
}

Merging relationships

The ODM merges relationships when sending data back to the client. This is so that the client can easily normalize entity data.

r.table('users').get(1)
  .merge(function(user) {
    return r({}).merge(r.args([
      r.branch(user.hasFields('pets'), {
        pets: r.table('animals').getAll(r.args(user('pets').map(function(pet) { return pet('id') }))
          .filter(function(pet) { return r.not(pet('meta')('archived')) })
          .coerceTo('array'),
      }, {}),
      r.branch(user.hasFields('company').and(user('company')('archived')), {
        company: r.table('companies').get(user('company')('id'))
      }, {})
    ]))
  })

Determining what to append vs. splice

Assume that some entity has a relationship array with ids ['6', '7', '8']. The client sends a PATCH with the ids ['5', '7']. We need to determine which ids need to be appended and which ids need to be spliced:

r(['6', '7', '8']).setUnion(['5', '7']).difference(r(['6', '7', '8'])); // ADD [5]
r(['6', '7', '8']).setUnion(['5', '7']).difference(r(['5', '7'])); // DELETE [6, 8]