The Personalization Service is a key-value storage service for frontend applications for end-user-specific data. It can be thought of as a browser local storage but shared between clients across multiple devices

Personalization Service

Overview

The Personalization Service is a key-value storage service for frontend applications for end-user-specific data. It can be thought of as a browser local storage but shared between clients across multiple devices.

Some supported use-cases:

  • User preferences

  • User profile data

  • Favorites

  • Usage history

  • Playback progress

Personalization Service is a managed multi-tenant service; it works together with the User Service and extends its capabilities.

Personalization Service C4 Context Diagram
Figure 1. Personalization Service C4 Context Diagram

Data Model

Key-Value pairs are stored internally in MongoDB. Key is represented by string, that contains any number of word characters separated by colon. The value can be a primitive type (string, number, boolean), a JSON, or an array of items.

Note
Value size of primitive type and JSON is limited to 10 KB. For value of type array - each item size is limited to 10 KB.

Key Scope. Stored keys can have one of the following scopes:

Scope Description

User

Keys saved under User scope are specific to the user and can be shared between user’s profiles and applications.

Profile

Profile scope allows to save key-value pairs specific to the user’s profile, but can be shared between different applications. User can get access to values saved in this scope, only if authorized with same profile, as the one used for saving the key-value.

Application

This scope is used for storing key-value pairs, that are specific to application. Keys stored in Application scope cannot be shared between multiple applications and profiles. User authorized with same profile and within same application, that were used for saving pair, can get access to keys.

Interfaces

Personalization Service exposes three APIs:

  • Data API - allows to set, retrieve and delete key-value pairs

  • Progress API - special asynchronous interface to support the playback progress use case

  • Array API - specialized API supporting list and array structures: storing Favorites, Watch history, etc

APIs are based on GraphQL.

Data API

Data API provides the general methods for manipulating the key-value pairs. Value supported by this API, are primitive type and JSON.

GraphQL Schema (click to expand)
"""
JSON custom scalar type with value size constraint.
"""
scalar JSON

enum KeyScope {
  USER
  PROFILE
  APPLICATION
}

interface QueryResponse {
  key: String!
  value: JSON
}

type KeyValueResponse implements QueryResponse {
  key: String!
  value: JSON!
}

interface MutationResponse {
  acknowledged: Boolean!
}

type SetKeyResponse implements MutationResponse {
  acknowledged: Boolean!
  insertedCount: Int!
  updatedCount: Int!
}

input SetKeyInput {
  scope: KeyScope!
  key: String! @constraint(pattern: "^\\w+(:\\w+)*$")
  value: JSON!
}

input DeleteKeyInput {
  scope: KeyScope!
  key: String! @constraint(pattern: "^\\w+(:\\w+)*$")
}

type DeleteKeyResponse implements MutationResponse {
  acknowledged: Boolean!
  deletedCount: Int!
}

input GetKeyInput {
  scope: KeyScope!
  keys: [String!]! @constraintArray(pattern: "^\\w+(:\\w+)*$")
}

type GetKeyResponse {
  data: [KeyValueResponse!]!
}

type Query {
  """
  Returns a value of a key or values of multiple keys.
  """
  getKey(input: GetKeyInput!): GetKeyResponse!
}

type Mutation {
  """
  Sets key to hold specified value.
  """
  setKey(input: SetKeyInput!): SetKeyResponse!

  """
  Deletes a key.
  """
  deleteKey(input: DeleteKeyInput!): DeleteKeyResponse!
}

Progress API

Progress API provides an efficient solution to a specific use case - playback progress.

This use case is relevant for any video playback solution. This API supports storing end-users’s playback progress for each video they started to watch. To achieve this frontend player periodically sends its current playback position. The server stores the latest reported position and provides upon request.

Progress API is an asynchronous API that is optimized for storing frequent progress updates from a large number of concurrent clients. API takes advantage of a Redis queue, which only retains the last relevant value for the playback progress.

Value can be only of type integer.

GraphQL Schema (click to expand)
"""
JSON custom scalar type with value size constraint.
"""
scalar JSON

enum KeyScope {
  USER
  PROFILE
  APPLICATION
}

interface MutationResponse {
  acknowledged: Boolean!
}

input SetProgressInput {
  scope: KeyScope!
  key: String! @constraint(pattern: "^\\w+(:\\w+)*$")
  value: Int!
}

type SetProgressResponse implements MutationResponse {
  acknowledged: Boolean!
}

input DeleteProgressInput {
  scope: KeyScope!
  key: String! @constraint(pattern: "^\\w+(:\\w+)*$")
}

type DeleteProgressResponse implements MutationResponse {
  acknowledged: Boolean!
  deletedCount: Int!
}

input GetProgressInput {
  scope: KeyScope!
  keys: [String!]! @constraintArray(pattern: "^\\w+(:\\w+)*$")
}

type ProgressData {
  key: String!
  value: Int!
}

type GetProgressResponse {
  acknowledged: Boolean
  data: [ProgressData!]!
}

type Query {
  """
  Returns value of a progress key.
  """
  getProgress(input: GetProgressInput!): GetProgressResponse!
}

type Mutation {
  """
  Sets key to hold specified progress value.
  """
  setProgress(input: SetProgressInput!): SetProgressResponse!

  """
  Deletes a (progress) key.
  """
  deleteProgress(input: DeleteProgressInput!): DeleteProgressResponse!
}

Array API

Array API provides functionality to store list of items as a value of a key. Endpoints provide possibility to set array of items to the key, adding item to already existing array, deleting, or updating item by auto-generated identifier. Endpoint Get Array supports pagination of array items.

Personalization Service supports sorting of array items. Each item in array can have up to three sort values, saved to predefined properties when adding new array item, or updating existing item. Sorting can be performed by one string value, one number and one date value.

Array can contain items of primitive and JSON types.

GraphQL Schema (click to expand)
"""
JSON custom scalar type with value size constraint.
"""
scalar JSON

"""
A point in time as described by the [ISO
8601](https://en.wikipedia.org/wiki/ISO_8601) standard. May or may not include a timezone.
"""
scalar Datetime

enum SortProperty {
  sortString
  sortNumber
  sortDate
  dateModified
  id
}

enum SortDirection {
  asc
  desc
}

enum KeyScope {
  USER
  PROFILE
  APPLICATION
}

input ArrayItem {
  value: JSON!
  sortString: String
  sortNumber: Int
  sortDate: Datetime
}

interface MutationResponse {
  acknowledged: Boolean!
}

input SetArrayItemInput{
  scope: KeyScope!
  key: String! @constraint(pattern: "^\\w+(:\\w+)*$")
  value: JSON!
  id: String
  sortString: String
  sortNumber: Int
  sortDate: Datetime
}

type SetArrayItemResponse implements MutationResponse{
  acknowledged: Boolean!
  modifiedCount: Int!
  insertedCount: Int!
}

input SetArrayInput{
  scope: KeyScope!
  key: String! @constraint(pattern: "^\\w+(:\\w+)*$")
  values: [ArrayItem!]!
}

type SetArrayResponse implements MutationResponse{
  acknowledged: Boolean!
  insertedCount: Int!
}

input GetArrayItemInput {
  scope: KeyScope!
  key: String! @constraint(pattern: "^\\w+(:\\w+)*$")
  id: String!
}

type GetArrayItemResponse {
  key: String!
  value: JSON!
  id: String
  sortString: String
  sortNumber: Int
  sortDate: Datetime
  dateModified: Datetime!
}

input GetArrayInput {
  scope: KeyScope!
  key: String! @constraint(pattern: "^\\w+(:\\w+)*$")
  skip: Int
  take: Int
  sort: SortProperty
  sortDirection: SortDirection
}

type GetArrayResponse {
  key: String!
  totalCount: Int!
  skip: Int!
  take: Int!
  sort: SortProperty!
  sortDirection: SortDirection!
  data: [GetArrayItemResponse!]
}

input DeleteArrayInput{
  scope: KeyScope!
  key: String! @constraint(pattern: "^\\w+(:\\w+)*$")
}

type DeleteArrayResponse implements MutationResponse{
  acknowledged: Boolean!
  deletedCount: Int!
}

input DeleteArrayItemInput{
  scope: KeyScope!
  key: String! @constraint(pattern: "^\\w+(:\\w+)*$")
  id: String!
}

type DeleteArrayItemResponse implements MutationResponse{
  acknowledged: Boolean!
  deletedCount: Int!
}

type Query {
  """
  Returns a array of a key.
  """
  getArray(input: GetArrayInput!): GetArrayResponse!

  """
  Returns a array item of a key.
  """
  getArrayItem(input: GetArrayItemInput!): GetArrayItemResponse!
}

type Mutation {
  """
  Set array to the key.
  """
  setArray(input: SetArrayInput!): SetArrayResponse!
  """
  Adds new item to array, or updates existing, based on item id.
  """
  setArrayItem(input: SetArrayItemInput!): SetArrayItemResponse!

  """
  Deletes an array key.
  """
  deleteArray(input: DeleteArrayInput!): DeleteArrayResponse!

  """
  Deletes an array value by specified id.
  """
  deleteArrayItem(input: DeleteArrayItemInput!): DeleteArrayItemResponse!
}

Authentication & Authorization

Personalization Service is a managed service facing frontend applications. All interface communication requires a JWT bearer token issued by the User Service.

Pricing

The service fee follows the tiered model depending on the number of end-users whose data is stored. See Mosaic Pricing for more details.