DynamoDb stream lambda trigger
Tags: aws, dynamodbstream, lambda, cdk
Published: 2021-04-14 08:55:54

### Assignment
Let's take a look on a typical scenario, once it is required to subscribe to [DynamoDb stream](https://aws.amazon.com/blogs/database/dynamodb-streams-use-cases-and-design-patterns) events and trigger [AWS lambda function](https://aws.amazon.com/lambda/). In theory and in [tutorials](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-readme.html) the task looks quite simple:
 - setup a [lambda](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-readme.html)
 - add [event source](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-readme.html#event-sources) with the table attached.
Sample:
``` typescript
private subscribeToDynamoDbStream(
    lambdaRole: Role,
    layer: LayerVersion,
    applicationName: string,
    targetTable: ITable) {
    const lambda = new Function(this, `${applicationName}-lambda`, 
    {
          code: Code.fromAsset('functions/custom-trigger/src'),
          functionName: `${applicationName}-lambda`,
          handler: handler,
          layers: [layer],
          role: lambdaRole,
          runtime: Runtime.NODEJS_12_X,
     });

     lambda.addEventSource(new DynamoEventSource(targetTable, {
     startingPosition: StartingPosition.TRIM_HORIZON, }));
}
```
This scenario works well in case of the [single repository](https://aws.amazon.com/blogs/devops/best-practices-for-developing-cloud-applications-with-aws-cdk/), once lambda, dynamodb and dynamodb stream are provisioned in the same stack. 

###Issue
In more complex layout the dynamodb itself is created and corresponding dynamodb stream is provisioned in a different stack. The dynamodb arn is saved to the [SSM parameter store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html), or the name is known.
So, first import table:

``` typescript
private importTargetTable(environment: string, tableArn: string) {
    const table = Table.fromTableArn(this, 'ImportedTable', tableArn);
    return table;
  }
```
Then call `subscribeToDynamoDbStream` function:
``` typescript
const importedTable = importTargetTable(this.environment, this.tableArn);
this.subscribeToDynamoDbStream(lambdaRole, layer, applicationName, importedTable);
```
The result is unexpected: `DynamoDB Streams must be enabled on the table [tablename]`. The AWS console clearly shows that the Dynamodb stream is enabled. Quick googling recommends the most obvious thing: enable already enabled feature. CDK [source code](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts#L25) inspection reveals that during `fromTableArn` invocation the stream arn most probably has not been loaded. 

###Solution
First of all there should be DynamoDb stream arn exported to SSM during DynamoDB provisioning:

``` typescript
const targetTable = new Table(this, `targeTableName`, {
   partitionKey: { ... },
   sortKey: { ... },
   ...
   stream: StreamViewType.NEW_AND_OLD_IMAGES,
   tableName: `targetTableName`
    });
   // Export target table stream arn to SSM
   new StringParameter(this.ctx, `targetTable-stream-arn`, {
      description: "Exported stream arn",
      parameterName: `/targeTable-stream-arn`,
      stringValue: targetTable.tableStreamArn!
    });  
```
In a stack where a lambda is created and subscription to the dynamodb stream is setup, `tableStreamArn` is read from SSM.
####Option 1
Change the way how `ITable` is constructed:
``` typescript
private importTargetTable(environment: string, tableArn: string, tableStreamArn: string) {
    const table = Table.fromTableAttributes(this, 'ImportedTable', {
        tableArn: tableArn,
        tableStreamArn: tableStreamArn
    return table;
  }
```
####Option 2
Technically speaking AWS lambda with DynamoDb stream trigger does not have to be aware of DynamoDb table (and it's arn). Option 2 could be considered as an improvement of the code above: ['Invent and Simplify'](https://www.amazon.jobs/en/principles).
Once the lambda is created, EventSourceMapping object could be used to enable DynamoDb stream trigger for the lambda usin `tableStreamArn` only:
``` typescript
const lambda = new Function(this, ...);
new EventSourceMapping(this, `lambda-trigger-mapping`, {
    eventSourceArn: tableStreamArn,
    startingPosition: StartingPosition.TRIM_HORIZON,
    // any necessary options of a choice, e.g. batchSize, retryAttempt, etc.
    target: lambda
});
```

I hope that this post would help you in your CDK learning journey.
Best wishes..