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:
- Ensure data isolation between tenants
- Provide quick access times for serverless functions (which are main components of Saleor apps)
- 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:
-
Read operations:
- API routes: Retrieve API keys for third-party services from saved configuration
- UI: Display current configuration values to end-users (via tRPC)
-
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}
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}
Where configKey
represents the configuration version. This approach allows for easy future migrations to new configuration versions.
Benefits of This Design
-
Multi-tenancy: The
saleorApiUrl
in the PK enables multi-tenancy by separating data for different Saleor instances. -
Versioning: The
configKey
in the SK allows for multiple versions of configurations. -
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;
}
Summary
When modelling DynamoDB access for multi-tenant services:
- Identify access patterns: Understand how your data will be read and written.
- Define data structure: Determine what needs to be stored in DynamoDB.
- 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)