Skip to content

Instantly share code, notes, and snippets.

@aaronromeo
Last active August 31, 2016 12:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aaronromeo/c222ca13d69dcef564d429cdd0ee70cc to your computer and use it in GitHub Desktop.
Save aaronromeo/c222ca13d69dcef564d429cdd0ee70cc to your computer and use it in GitHub Desktop.

Understanding the problem and assumptions

  • CAP Theorem problem

    • Partition tolerance will fail as a result of network connections
    • This solution maximises for availability of data
    • The assumption here is that the consistency of data is less important
  • Users need to be logged in to make changes

  • Sync server is responsible for reducing events

  • Two solutions proposed

    • If no newsfeed or watching other users is required, a movie's favourites are only retrieved when a movie is read
    • If watching other users is required, the solution is more fragile, and should probably result in a push from the server to the device to ensure a proper success response code (which isn't possible in the case of the server getting the results)
  • TODO Add in device tracking

Scenarios

Scenario 1 - Modifying the same movie

  • User A has 2 devices (D1 & D2).
  • The devices have the following events on them
    • D1
      • E1 - { type: 'ADD', movie_imdb_id: 'aaaa', timestamp: 1472608060 }
      • E2 - { type: 'DEL', movie_imdb_id: 'aaaa', timestamp: 1472608072 }
    • D2
      • E3 - { type: 'ADD', movie_imdb_id: 'aaaa', timestamp: 1472608064 }
  • Syncing order is E1, E2, E3

Scenario 2 - Updating multiple movies

  • User A has 3 devices (D1, D2 & D3).
  • The devices have the following events on them
    • D1
      • E3 - { type: 'ADD', movie_imdb_id: 'aaaa', timestamp: 1472607000 }
    • D2
      • E1 - { type: 'ADD', movie_imdb_id: 'bbbb', timestamp: 1472608000 }
    • D3
      • E2 - { type: 'ADD', movie_imdb_id: 'cccc', timestamp: 1472609000 }
  • Syncing order is E1, E2, E3

Models

  • User
  • Movies
  • Favorite
  • Event
  • Device Solution 2
  • UserDeviceEvent Solution 2

User

Relationships

  • has_many :favorites
  • has_many :movies, through: :favourites
  • has_many :events
  • has_many :devices Solution 2

Important Fields

field name type constraint
id integer uniq
username string uniq
encrypted_password string
facebook_id or avatar string uniq
Ideally it would be nice to be able to assoicate a user to a Facebook user (or some OpenID user) which allows Mov.io.ly to pull a friend list. Barring that being possible, associating a user with an Avatar would be sufficient

Important Validations

  • Validates unique username
  • Validates presence username
  • Validates presence password

Movie

Relationships

  • has_many :favourites
  • has_many :users, through: :favourites
  • has_many :events

Important Fields

field name type constraint
id integer uniq
imdb_id string uniq
Assume IMDB ID is unique and never changes

Important Validations

  • Validates unique IMDb ID

Favourite

Relationships

  • belongs_to :user
  • belongs_to :movie

Important Fields

field name type constraint default
id integer uniq
user_id integer
movie_id integer
is_deleted boolean false
event_timestamp timestamp false

Important Validations

  • validates :movie_id, uniqueness: { scope: [:user_id] }

Notes

  • Updates to this table only apply if the event driving the update has a timestamp greater than the current event_timestamp date

Event

Relationships

  • belongs_to :user
  • belongs_to :movie
  • has_many :user_device_events Solution 2

Important Fields

field name type constraint
id integer uniq
action string
user_id integer
movie_id integer
device_timestamp timestamp

Important Validations

  • Validates presence action
  • Validates action in ['ADD', 'DEL']
  • Validates presence movie
  • Validates presence user
  • Validates presence device_timestamp

Device Solution 2

Relationships

  • belongs_to :user

Important Fields

field name type constraint
id integer uniq
user_id integer
name string

Important Validations

  • validates :name, uniqueness: { scope: [:user_id] }

UserDeviceEvent Solution 2

Relationships

  • belongs_to :user
  • belongs_to :device
  • belongs_to :event

Important Fields

field name type constraint
user_id integer
device_id integer
event_id integer

Important Validations

  • validates :event_id, uniqueness: { scope: [:user_id, :device_id] }

API endpoints

View A User’s favourite movies

GET /users/:user_id/favorite-movies/

Sample response body

{
    movies: [
        {
            id: 123,
            imdb_id: 'aaaa'
        },
        {
            id: 234,
            imdb_id: 'bbbb'
        },
        {
            id: 345,
            imdb_id: 'cccc'
        }
    ]
}

View movie details

GET /movies/:movie_id/

Sample response body

Example 1:
{
    movie: {
        id: 123,
        name: 'Some movie name',
        avatar: 'https://something.s3.amazonaws.com/asdfsadfsadfsafasf/123/',
        rotten_tomatoes_url: 'http...',
        favourite: null
    }
}

Example 2:
{
    movie: {
        id: 123,
        name: 'Some movie name',
        avatar: 'https://something.s3.amazonaws.com/asdfsadfsadfsafasf/123/',
        rotten_tomatoes_url: 'http...',
        favourite: {
            id: 3456,
            is_deleted: false,
            timestamp: 1472608061
        }
    }
}

View all the users who have added a movie as a favourite

GET /movies/:movie_id/favorite-users/

Sample response body

{
    users: [
        {
            id: 123,
            username: 'aaaa',
            avatar: 'https://something.s3.amazonaws.com/asdfsadfsadfsafasf/123/'
        },
        {
            id: 234,
            username: 'bbbb',
            avatar: 'https://something.s3.amazonaws.com/asdfsadfsadfsafasf/234/'
        },
        {
            id: 345,
            username: 'cccc',
            avatar: 'https://something.s3.amazonaws.com/asdfsadfsadfsafasf/234/'
        }
    ]
}

Syncing API Endpoint

  • Decisions should always defer to latest timestamp for conflict resolution
  • Timestamps are mobile device's or webserver's timestamp Caveat, this needs to be a correct timestamp
  • POSTs should be paginated

Push pending/un-sync'ed events to server

POST /users/:user_id/sync-events/

Sample request data

{
    events: [
        {
            action: 'ADD',
            movie_id: 123,
            timestamp: 1472608060
        },
        {
            action: 'ADD',
            movie_id: 345,
            timestamp: 1472608061
        },
        {
            action: 'DEL',
            movie_id: 123,
            timestamp: 1472608072
        }
    ]
}

Sample response body

{}

Status 201 for successful addition

Solution 2 Get pending/un-sync'ed events form server for a device

GET /users/:user_id/device/:device_id/sync-events/

Sample response body

{
    events: [
        {
            action: 'ADD',
            movie_id: 123,
            timestamp: 1472608060
        },
        {
            action: 'ADD',
            movie_id: 345,
            timestamp: 1472608061
        },
        {
            action: 'DEL',
            movie_id: 123,
            timestamp: 1472608072
        }
    ]
}

Mock Ups

alt text

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