Skip to main content

UNDERSTANDING DYNAMODB AUTOMATION IN SWAMP

DynamoDB has two roles in Swamp. It can be a resource that Swamp manages on your behalf, with tables defined as behaviors and changed by methods. It can also be the place where Swamp keeps its own state, replacing the repository's default datastore. The roles use different extensions and answer different questions. In the first, DynamoDB is what your automation works on. In the second, it is the backing store your automation runs on. One AWS account can serve both roles, and it helps to keep them apart when thinking about permissions and failure.

A global table as a behavior

The @swamp/aws/dynamodb extension provides one type, @swamp/aws/dynamodb/global-table. Its global arguments carry the whole table definition: TableName, KeySchema, AttributeDefinitions, BillingMode, Replicas, global and local secondary indexes, StreamSpecification, TimeToLiveSpecification, SSESpecification, WarmThroughput and VectorIndexes. The names keep AWS's capitalisation, so the AWS documentation for global tables maps directly onto the fields of a definition.

The extension has no separate type for an ordinary table. Every table is a global table, and Replicas is the one required argument. A table in a single region is a global table with one replica. This is closer to how DynamoDB treats replication today than to the older split between tables and global tables. One consequence is that you never need to migrate the definition when a table later gains a second region. Adding a region is an edit to Replicas followed by update, not a change of type.

Per-replica settings such as point-in-time recovery, deletion protection, table class, contributor insights, Kinesis streaming and tags sit inside each Replicas entry. Settings that must agree across regions, such as key schema, attribute definitions, billing mode and stream view type, sit at the top level.

Methods and their lifecycle

The six methods split into two groups. create, update and delete change AWS. get, list and sync only read. All six write the table's resource state to Swamp data, so a run that changes a table also leaves a record of the state it produced. That record is versioned like other Swamp data, and workflows and CEL expressions can read it. Behaviors, Types, and Methods covers the general concepts, and The Data Layer covers how the resource state is stored.

The read methods have different purposes. sync takes no arguments and refreshes the state of the table the behavior defines. get and list work by identifier or by enumeration, so they can read tables the behavior did not create. As a result, a behavior can be used only for discovery, with its write methods never called, and that is a reasonable way to bring an existing estate into view before deciding what Swamp should own.

Credentials and regions

By default the behavior uses the AWS default credential chain: environment variables, shared config and SSO profiles, or an instance role. This suits an interactive session after aws sso login and suits compute that already has a role. Authenticate to AWS with SSO describes that path.

The accessKeyId, secretAccessKey and sessionToken global arguments override the chain. They exist for cases where the environment cannot hold the right identity, such as a shared runner that manages several accounts. Their values belong in a vault and are referenced with expressions like ${{ vault.get('aws', 'secret-access-key') }}, which keeps them out of the definition and out of recorded output. Use AWS Secrets Manager shows one backing for such a vault. region follows the same pattern. It overrides the environment and profile region and defaults to us-east-1.

Decisions when modelling tables

One behavior per table is the natural unit. Each table then has its own definition, its own history in Swamp data and its own blast radius when a method runs. Grouping several tables behind one behavior is not possible with this type, and it would blur which run changed which table even if it were.

Capacity is the next choice. PAY_PER_REQUEST removes capacity planning and can be capped with ReadOnDemandThroughputSettings and WriteOnDemandThroughputSettings. Provisioned capacity, set with ReadProvisionedThroughputSettings and WriteProvisionedThroughputSettings and their autoscaling settings, costs less for steady, predictable load but needs tuning. Many teams start on demand and move a table to provisioned capacity once its traffic is understood.

MultiRegionConsistency decides what readers in other regions see. EVENTUAL is the long-standing global table behaviour: writes replicate asynchronously and each region accepts writes independently. STRONG gives reads that reflect the latest write across regions at the cost of write latency and tighter AWS constraints on regions. GlobalTableWitnesses relates to that mode. Most workloads that tolerate a short replication delay are better served by EVENTUAL.

Streams, indexes and point-in-time recovery can be added later, but they are harder to reason about after data depends on them. A stream with NEW_AND_OLD_IMAGES is worth enabling up front if anything downstream may need change data. Point-in-time recovery is configured per replica, so a table can have it in its primary region and not elsewhere.

DynamoDB as the Swamp datastore

The second role is handled by @webframp/dynamodb-datastore, a datastore provider. A repository's datastore holds its runtime data, run state and locks. Datastore Architecture covers why that is pluggable. The DynamoDB provider uses a single-table design, so locks, file chunks, file metadata and sync state share one table, distinguished by key.

Locking uses conditional writes with fencing tokens. A writer that loses its lock cannot overwrite the work of the writer that took it over, because its token is stale. TTL on lock items acts as defence in depth: a crashed holder's lock eventually expires even if no one cleans it up. DynamoDB items are limited to 400KB, so files above maxChunkBytes are split across several items and reassembled on read. When a tracer provider is configured, the provider emits OpenTelemetry spans for DynamoDB calls, lock acquisition and release, and push and pull sync, so datastore latency appears alongside the rest of a run.

The datastore's configuration keys (region, tableName, endpoint, autoCreateTable, maxChunkBytes) are listed in Datastore Configuration.

The bootstrap workflow and least privilege

The datastore can create its own table with autoCreateTable, but then every identity that runs Swamp needs permission to create tables. The @webframp/dynamodb-datastore-bootstrap extension takes the other approach. Its provisioner creates the table, with on-demand billing, a pk/sk key, a gsi1 index for sync walks and TTL on ttl, once, using an administrative identity. It also creates a managed policy that grants only the seven runtime actions the datastore uses, scoped to that one table. The workflow then switches the repository with a command/shell step.

The workflow creates the policy but does not attach it, because it cannot know which users, roles or runners will operate the repository. Attaching it is a deliberate step for whoever owns those identities.

The bootstrap also shows how DynamoDB work composes in Swamp. A provisioning behavior and a shell behavior are ordered into a workflow, with inputs passed through. Table behaviors from @swamp/aws/dynamodb can be composed the same way, for example creating a table and then running a step that depends on it. The Workflow Execution Model describes how those steps are scheduled and how their data flows between them.