sample repo to use DynamoDB with append only logs.
examples contain either Node.js or Python.
DynamoDB write triggers
EventBridge reads
Here is the Speakeasy JS video from April 2021.
I previously built level-eventstore and wanted the same benefits of append only logs, but using serverless.
By using DynamoDB with DynamoDB Streams we can build append only logs. Although we cannot implement a strict append only log we can order log items by when they are written, and with conditional writes we can perform optimistic locking.
We can preserve the item order when reading from DynamoDB using a sort key.
- Logs are saved in DynamoDB.
- Publish / Subscribe changes using EventBridge.
example payload written to DynamoDB
{
"pk": "users#12#stream",
"sk": 1,
"type": "signup",
"log": "users",
"payload": {
"id": "12",
"name": "test",
"email": "test@example.com"
}
}
- pk (partition key) is
log name#id#stream
. - sk (sort key).
- type is the name of the event useful for event handlers.
- log name of log.
- payload should contain the id and any optional extra fields.
When items are written to DynamoDB they are written to the DynamoDB stream for the shard they belong to in the order they are written.
The lambda is then triggered which will publish the changed keys to EventBridge.
install prerequisites
in AWS
export AWS credentials before running cli.sh
npm i
./cli.sh
npm exec sls deploy -- --stage dev -c serverless-node.yml
npm i
./cli.sh
npm exec sls remove -- --stage dev -c serverless-node.yml
npm i
./cli.sh
rm -rf venv
virtualenv venv
. venv/bin/activate
pip3 install -r requirements.txt
npm exec sls deploy -- --stage dev -c serverless-python.yml
rm -rf venv
npm i
./cli.sh
npm exec sls remove -- --stage dev -c serverless-python.yml
Query DynamoDB
./cli.sh
./dynamodb-local-query.sh
generate fake user data
Generate fake user data locally or in aws.
./create-fake-data.sh
offline
./cli.sh
npm exec sls offline start -- --stage=local -c serverless-node.yml
./cli.sh
virtualenv venv
. venv/bin/activate
pip3 install -r requirements.txt
npm exec sls offline start -- --stage=local -c serverless-python.yml
Add item using aws cli.
./cli.sh
export AWS_ACCESS_KEY_ID=x
export AWS_SECRET_ACCESS_KEY=x
export AWS_DEFAULT_REGION=us-east-1
aws dynamodb put-item \
--table-name local-dynamodb-logs \
--item """
{
\"pk\": { \"S\": \"users#12#stream\" },
\"sk\": { \"N\": \"${sk:-1}\" },
\"type\": { \"S\": \"signup\" },
\"log\": { \"S\": \"users\" },
\"payload\": { \"M\": {
\"id\": { \"S\": \"12\"},
\"name\": { \"S\": \"test\"}
\"email\": { \"S\": \"test@example.com\"}
}}
}""" \
--condition-expression "attribute_not_exists(pk)" \
--endpoint http://localhost:8000
Query DynamoDB
./cli.sh
./dynamodb-local-query.sh
Cleanup
Remove dynamodb data and docker-compose processes.
Also needed if you have new AWS credentials.
./cli.sh stop
retries offline and in AWS
per stack retry limit is configurable using the offline eventbridge plugin.
custom:
serverless-offline-aws-eventbridge:
retryDelayMs: 1000
maximumRetryAttempts: 5
using onFailure failures can be handled by your application.
failure payloads look like this
eventHandler:
handler: src/handler.handler
destinations:
onFailure: retry
iamRoleStatements:
- Effect: Allow
Action:
- "lambda:InvokeFunction"
Resource:
- !Sub "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:dynamodb-logs-${opt:stage}-retry"
{
"version": "1.0",
"timestamp": "<timestamp>",
"requestContext": {
"requestId": "<uuid>",
"functionArn": "arn:...",
"condition": "RetriesExhausted",
"approximateInvokeCount": 3
},
"requestPayload": {
"version": "0",
"id": "<uuid>",
"detail-type": "stream changes",
"source": "dynamodb-log",
"account": "<account>",
"time": "<timestamp>",
"region": "<region>",
"resources": [],
"detail": {
"log": "<log>",
"key": {
"pk": "<pk>",
"sk": 0
},
"type": "<signup>",
"payload": {
"id": "<id>"
}
}
},
"responseContext": {
"statusCode": 200,
"executedVersion": "$LATEST",
"functionError": "Unhandled"
},
"responsePayload": {
"errorType": "Error",
"errorMessage": "<error message>",
"trace": []
}
}
replay using EventBridge archive
When run offline events are persisted to sqlite3 file ./db
.
Events can be replayed to EventBridge or DynamoDB, you can also replay events from AWS to local which will deplay an extra stack using a websocket lambda and a new event target that writes to the connected websockets.
./cli.sh
./run-archive.js