NoSQL Databases with DynamoDB
Relevant source files
The following files were used as context for generating this wiki page:- packages/@aws-cdk/aws-dynamodb/README.md
- packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts
- packages/@aws-cdk/aws-appsync/README.md
- packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts
- packages/@aws-cdk/aws-lambda-event-sources/README.md
- packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json
The @aws-cdk/aws-dynamodb module provides constructs for defining Amazon DynamoDB resources within an AWS CDK application. It allows developers to create and configure DynamoDB tables, including their keys, indexes, billing modes, and integrations with other AWS services like AWS Lambda and AWS AppSync, using a high-level, object-oriented abstraction.
This library simplifies the process of provisioning NoSQL databases by encapsulating AWS best practices and reducing boilerplate CloudFormation code. It supports features such as auto-scaling, global tables, data streaming, and various encryption options, enabling developers to define robust and scalable database solutions imperatively in their preferred programming language.
Sources: packages/@aws-cdk/aws-dynamodb/README.md, README.md:16-21
The Table Construct
The primary construct for defining a DynamoDB table is dynamodb.Table. A minimal table requires a partition key.
import * as dynamodb from '@aws-cdk/aws-dynamodb';
const table = new dynamodb.Table(this, 'Table', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }
});
Sources: packages/@aws-cdk/aws-dynamodb/README.md:13-17
Table Schema and Keys
A table's schema is defined by its primary key. The primary key can be a simple key (partition key only) or a composite key (partition key and sort key).
partitionKey: The primary key of the table.sortKey: The sort key (or range key) of the table.
The keys are defined using the Attribute interface, which requires a name and a type from the AttributeType enum.
| AttributeType | Description |
|---|---|
STRING | 'S' |
NUMBER | 'N' |
BINARY | 'B' |
Sources: packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:20-21, packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:80-94
The following diagram shows the basic structure of the Table construct and its relationship with its key attributes.
This diagram illustrates the Table construct's composition, including its primary key attributes.
Sources: packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:20-21,80-94
Importing Existing Tables
Existing DynamoDB tables can be imported into a CDK application using factory methods like Table.fromTableName or Table.fromTableArn. This allows granting permissions and interacting with tables that were not created within the current CDK stack.
const table = Table.fromTableArn(this, 'ImportedTable', 'arn:aws:dynamodb:us-east-1:111111111:table/my-table');
// now you can just call methods on the table
table.grantReadWriteData(user);
If you need to use the table's stream, you must use Table.fromTableAttributes and provide the tableStreamArn.
Sources: packages/@aws-cdk/aws-dynamodb/README.md:21-26, packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:1096-1100
Billing and Capacity
DynamoDB offers two billing modes, configured via the billingMode property.
| BillingMode | Description |
|---|---|
PROVISIONED | (Default) The table has configured read and write capacity units. Suitable for predictable workloads. |
PAY_PER_REQUEST | On-demand pricing where you pay only for what you use. No capacity planning is needed. |
Sources: packages/@aws-cdk/aws-dynamodb/README.md:41-46, packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:413-426
Autoscaling
For tables with PROVISIONED billing mode, you can configure autoscaling to automatically adjust read and write capacity based on demand. This is achieved using the autoScaleReadCapacity and autoScaleWriteCapacity methods.
const table = new dynamodb.Table(this, 'MyTable', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PROVISIONED,
});
// Scale up/down read capacity based on utilization
table.autoScaleReadCapacity({ minCapacity: 50, maxCapacity: 500 })
.scaleOnUtilization({ targetUtilizationPercent: 75 });
// Scale up/down write capacity based on utilization
table.autoScaleWriteCapacity({ minCapacity: 50, maxCapacity: 500 })
.scaleOnUtilization({ targetUtilizationPercent: 75 });
Sources: packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:809-824, packages/@aws-cdk/aws-dynamodb/README.md:52-56
The following diagram shows the components involved in DynamoDB autoscaling.
This diagram shows how Application Auto Scaling uses CloudWatch metrics to adjust the provisioned capacity of a DynamoDB table.
Sources: packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:809-836
Secondary Indexes
Secondary indexes allow you to query data in a table using an alternate key, in addition to queries against the primary key.
Global Secondary Indexes (GSI)
GSIs have a partition key and an optional sort key that can be different from the table's primary key. They are added using the addGlobalSecondaryIndex method.
table.addGlobalSecondaryIndex({
indexName: 'MyGSI',
partitionKey: { name: 'gsiHashKey', type: AttributeType.STRING },
readCapacity: 42,
writeCapacity: 1337,
});
Sources: packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:503-512
Local Secondary Indexes (LSI)
LSIs share the same partition key as the table but have a different sort key. They must be created when the table is created. A table can have a maximum of 5 LSIs.
table.addLocalSecondaryIndex({
indexName: 'MyLSI',
sortKey: { name: 'lsiSortKey', type: AttributeType.NUMBER },
});
Sources: packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:701-708
Projection Types
Both GSIs and LSIs require a projectionType to specify which attributes are copied from the table into the index.
| ProjectionType | Description |
|---|---|
ALL | (Default) All attributes are projected into the index. |
KEYS_ONLY | Only the index and primary keys are projected. |
INCLUDE | The index, primary keys, and a specified list of nonKeyAttributes are projected. |
Sources: packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:553-558,582-588,731-736
Data Streaming and Processing
DynamoDB can capture item-level modifications and emit them to a stream. This is useful for data replication, analytics, and triggering downstream workflows.
DynamoDB Streams
Enable DynamoDB Streams by setting the stream property on the table. The StreamViewType determines what information is written to the stream record.
| StreamViewType | Description |
|---|---|
NEW_IMAGE | The entire item, as it appears after it was modified. |
OLD_IMAGE | The entire item, as it appeared before it was modified. |
NEW_AND_OLD_IMAGES | Both the new and the old images of the item. |
KEYS_ONLY | Only the key attributes of the modified item. |
Sources: packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:192-206
DynamoDB Streams can be used as an event source for an AWS Lambda function to process these changes in real-time.
import { DynamoEventSource } from '@aws-cdk/aws-lambda-event-sources';
const table = new dynamodb.Table(..., {
stream: dynamodb.StreamViewType.NEW_IMAGE
});
const myFunction = new lambda.Function(...);
myFunction.addEventSource(new DynamoEventSource(table, {
startingPosition: lambda.StartingPosition.TRIM_HORIZON,
}));
Sources: packages/@aws-cdk/aws-lambda-event-sources/README.md:104-116
Kinesis Data Streams
Alternatively, changes can be sent to a Kinesis Data Stream by setting the kinesisStream property.
import * as kinesis from '@aws-cdk/aws-kinesis';
const stream = new kinesis.Stream(this, 'Stream');
const table = new dynamodb.Table(this, 'Table', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
kinesisStream: stream,
});
Sources: packages/@aws-cdk/aws-dynamodb/README.md:162-171
The following diagram illustrates the flow of data from a DynamoDB table modification to a Lambda function via DynamoDB Streams.
This flowchart shows how a write operation on a DynamoDB table triggers a Lambda function via a stream.
Sources: packages/@aws-cdk/aws-lambda-event-sources/README.md:95-116
Encryption
All data in DynamoDB is encrypted at rest by default. You can customize the encryption key using the encryption property.
| TableEncryption | Description |
|---|---|
DEFAULT | Encryption by an AWS owned CMK (no additional charges). This is not a valid value for this property. |
AWS_OWNED | (Default) Encryption by an AWS owned CMK. |
AWS_MANAGED | Encryption by an AWS managed CMK in your account (KMS charges apply). |
CUSTOMER_MANAGED | Encryption by a customer managed CMK that you control (KMS charges apply). |
When using CUSTOMER_MANAGED, you can either provide your own KMS key via the encryptionKey property, or the CDK will create one for you.
import kms = require('@aws-cdk/aws-kms');
// CDK creates a key for you
const table1 = new dynamodb.Table(stack, 'MyTable1', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
encryption: TableEncryption.CUSTOMER_MANAGED,
});
// You provide the key
const encryptionKey = new kms.Key(stack, 'Key');
const table2 = new dynamodb.Table(stack, 'MyTable2', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
encryption: TableEncryption.CUSTOMER_MANAGED,
encryptionKey,
});
Sources: packages/@aws-cdk/aws-dynamodb/README.md:120-151, packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:285-303
Global Tables
DynamoDB Global Tables provide a fully managed, multi-region, multi-active database. You can create a global table by specifying the replicationRegions property. A custom resource is used to manage the replication process.
const globalTable = new dynamodb.Table(this, 'Table', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
replicationRegions: ['us-east-1', 'us-east-2', 'us-west-2'],
});
When creating a global table, stream must be set to NEW_AND_OLD_IMAGES. The default billing mode is PAY_PER_REQUEST. If PROVISIONED mode is used, write auto-scaling must be enabled.
Sources: packages/@aws-cdk/aws-dynamodb/README.md:73-81, packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:1330-1342,1424-1435
Permissions
The Table construct provides several grant* methods to easily manage IAM permissions for other resources to interact with the table.
grantReadData(principal)grantWriteData(principal)grantReadWriteData(principal)grantFullAccess(principal)grant(principal, ...actions)
These methods automatically grant permissions to the table itself and any configured secondary indexes. If the table is encrypted with a customer-managed key, the grants also include the necessary kms:Decrypt and kms:Encrypt permissions.
Sources: packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:983-1025, packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts:373-410
AppSync Integration
DynamoDB tables are frequently used as a backend data source for AWS AppSync GraphQL APIs. The @aws-cdk/aws-appsync module provides direct integration.
You can add a DynamoDB table as a data source to an AppSync API using api.addDynamoDbDataSource.
const api = new appsync.GraphqlApi(stack, 'Api', { /* ... */ });
const demoTable = new db.Table(stack, 'DemoTable', { /* ... */ });
const demoDS = api.addDynamoDbDataSource('demoDataSource', demoTable);
Sources: packages/@aws-cdk/aws-appsync/README.md:32-42
Once the data source is created, you can create resolvers that map GraphQL operations (queries, mutations) to DynamoDB operations using Velocity Template Language (VTL) mapping templates. The appsync.MappingTemplate class provides helpers for common DynamoDB operations.
// Resolver for a "scan" query
demoDS.createResolver({
typeName: 'Query',
fieldName: 'getDemos',
requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(),
responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(),
});
// Resolver for a "put" mutation
demoDS.createResolver({
typeName: 'Mutation',
fieldName: 'addDemo',
requestMappingTemplate: appsync.MappingTemplate.dynamoDbPutItem(
appsync.PrimaryKey.partition('id').auto(),
appsync.Values.projecting('input')
),
responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultItem(),
});
Sources: packages/@aws-cdk/aws-appsync/README.md:45-60
The following sequence diagram shows the flow of a GraphQL request through AppSync to a DynamoDB data source.
This diagram illustrates how AppSync processes a GraphQL query, uses a mapping template to call a DynamoDB table, and transforms the response back to the client.