HomeGuidesAPI ReferenceGraphQL
Submit Documentation FeedbackJoin Developer CommunityOptimizely GitHubOptimizely NuGetLog In

Storage

This topic describes the 3 types of storage that are available to store your data in Optimizely Connect Platform (OCP).

OCP apps have access to 3 types of storage to support storing sensitive data, configuration data, and general data structures necessary for the operation of the app. Apps must use the provided storage layers and should not attempt to store data needed by the app outside OCP or on the file system.

🚧

Important

Storing or transmitting data outside OCP to any vendor other than the target of the integration is strictly forbidden.

Like other OCP App APIs, when using the stores, data is automatically partitioned by install so you do not have to worry about cross-contamination.

  • Secrets Store
    • Suitable for sensitive information not related to a settings form.
  • Settings Store
    • Data backing the settings form. Suitable for any configuration-related data (including passwords and API keys), especially data you need to present to the user through the settings form.
  • Key Value Store
    • General purpose storage for simple data structures, lists, and sets. Designed for high throughput and moderately large data sets when necessary, but limited to ~400KB per record.

Best Practices

  1. Cache results when you can. Keep in mind every operation against a store is a network call. It is highly recommended to cache when you are able. For example, throughout the duration of a job.
  2. Minimize requests. When possible, co-locate your data to reduce the number of reads and writes. Especially for high-frequency code, such as a busy function, make sure you optimize where you store your data to reduce the number of store functions you need to call. For example, if you need both a third-party account id and token when you make every outbound API call, consider storing them together in the same object in the secret store, even though the account id may not be sensitive data.
  3. Use TTLs. The Key Value store supports a TTL on every key. For any data that does not have to live for the duration of the app installation, setting a TTL ensures it gets cleaned up even if something unexpected happens.
  4. Use patch whenever possible. When updating a value in the store, patch guarantees an atomic change. Using patch (with or without a callback handler) to update your data is always safe and avoids the race conditions from using get followed by put, where another request could modify the data in between.
  5. Use interfaces for your data. Using interfaces to get and put data into the stores will remove any doubt about what type of data is stored there and reduce bugs with compile-time type checks.

🚧

Caution

Avoid caching App.storage as a constant outside of the scope of a function. Doing so can cause access to break in the case of a reinstall.

Example (Secret Store)

import {storage, ValueHash} from '@zaius/app-sdk';
import fetch from 'node-fetch';
​
export interface YotpoCredentials extends ValueHash {
  client_id: string;
  client_secret: string;
}
​
export interface UToken extends ValueHash {
  token?: string;
  issued?: number;
}
​
interface YotpotAuthResult {
  access_token: string;
  token_type: 'bearer';
}
​
/**
 * Authenticate with Yotpo and get a an auth token for future API calls
 * @param credentials
 * @param force true if we should discard the existing token and ask for a new one
 */
export async function authenticate(credentials: YotpoCredentials, force = false): Promise<string> {
  // Sometimes we will want to get a fresh token (credentials changed)
  if (!force) {
    const uToken = await storage.secrets.get<UToken>('utoken');
    if (uToken.token && !isExpiring(uToken)) {
      // we already have a valid token saved, use it
      return uToken.token;
    }
  }
​
  const result = await fetch('https://api.yotpo.com/oauth/token', {
    method: 'POST',
    body: JSON.stringify({...credentials, grant_type: 'client_credentials'}),
    headers: {'Content-Type': 'application/json'}
  });
​
  if (result.status === 200) {
    const token = (await result.json() as YotpotAuthResult).access_token;
    await storage.secrets.put<UToken>('utoken', {token, issued: new Date().getTime()});
    return token;
  } else {
    const body = await result.json();
    throw new Error(body.status && body.status.message || 'Unable to authorize with Yotpo');
  }
}
​
function isExpiring(token: UToken) {
  return !token.issued || new Date().getTime() - token.issued > 7 * 86400 * 1000;
}

Next