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.