DEV Community

Cover image for DynamoDB access modeling for multi-tenant services
Krzysztof Żuraw for Saleor Commerce

Posted on

DynamoDB access modeling for multi-tenant services

Recently at Saleor we encountered the challenge of implementing multi-tenancy in Saleor apps. In this blog post, I'll share my experience with access modelling when using DynamoDB for multi-tenant services.

Problem

When developing a Saleor app that can be installed for multiple tenants, we faced several requirements:

  1. Ensure data isolation between tenants
  2. Provide quick access times for serverless functions (which are main components of Saleor apps)
  3. Achieve good scalability

Given these requirements, we chose DynamoDB as our database solution. Its flexible schema, low latency, and ability to scale horizontally made it an ideal fit for our multi-tenant Saleor app.

Modelling Approach

Before diving into implementation, it's crucial to model our data and access patterns. Let's take the example of storing app configurations for multiple tenants.

Access Patterns

We identified two main access patterns:

  1. Read operations:
    • API routes: Retrieve API keys for third-party services from saved configuration
    • UI: Display current configuration values to end-users (via tRPC)
  2. Write operations:
    • UI: Update app configuration (via tRPC)

Data Structure

Our app configuration typically consists of:

  • Encrypted fields (e.g., API keys for third-party services)
  • Mappings between Saleor channels and app configuration

DynamoDB Table Design

Following recommendations from The DynamoDB Book we decided to store all data for each app in a single DynamoDB table. Here's how we structured our table:

Primary Key (PK)

We use a composite primary key in the format:

{saleorApiUrl}#{saleorAppId}
Enter fullscreen mode Exit fullscreen mode

This structure ensures that:

  • Each tenant's data is isolated
  • New app installations don't access data from previous installations
  • App can fetch multiple entities from DynamoDB in one request

Sort Key (SK)

Our sort key follows the format:

APP_CONFIG#{configKey}
Enter fullscreen mode Exit fullscreen mode

Where configKey represents the configuration version. This approach allows for easy future migrations to new configuration versions.

Benefits of This Design

  1. Multi-tenancy: The saleorApiUrl in the PK enables multi-tenancy by separating data for different Saleor instances.
  2. Versioning: The configKey in the SK allows for multiple versions of configurations.
  3. Flexibility: We can easily add other entity types by using different SK prefixes (e.g., TRANSACTION#{transactionId} for storing webhook-related information).

Implementation Example

Here's a TypeScript example of how we might query our DynamoDB table:

import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

async function getAppConfig(saleorApiUrl: string, saleorAppId: string, configKey: string) {
  const command = new GetCommand({
    TableName: "AppTable",
    Key: {
      PK: `${saleorApiUrl}#${saleorAppId}`,
      SK: `APP_CONFIG#${configKey}`,
    },
  });

  const response = await docClient.send(command);
  return response.Item;
}
Enter fullscreen mode Exit fullscreen mode

Summary

When modelling DynamoDB access for multi-tenant services:

  1. Identify access patterns: Understand how your data will be read and written.
  2. Define data structure: Determine what needs to be stored in DynamoDB.
  3. Design table structure: Choose appropriate primary and sort keys to enable efficient querying and ensure data isolation between tenants.

By following these steps, we've created a scalable and efficient DynamoDB structure for our multi-tenant Saleor app. This approach allows us to maintain data isolation, achieve quick access times, and easily adapt to future requirements.

Top comments (0)