Search code examples
amazon-web-servicesaws-cdkamazon-dynamodb-streams

To subscribe to the dynamodb global table stream in regional aws lambdas?


Here is my regional lambda sdk configuration.

The problem is tableStreamArn it seems:

   const globalTableTableName = "my-org-glb-table1"

    const regionalTable = Table.fromTableAttributes(this, `Table-${Stack.of(this).region}`, {
        tableName: globalTableTableName,
        tableStreamArn: `arn:aws:dynamodb:${Stack.of(this).region}:${this.account}:table/${globalTableTableName}/stream/latest` // here !
    });

    const eventSource = new DynamoEventSource(regionalTable, {
        startingPosition: StartingPosition.TRIM_HORIZON,
        batchSize: 5,
        bisectBatchOnError: true,
        retryAttempts: 2
    });
    lambda1.addEventSource(eventSource);

I can not know what stream the table will have, so my lambda stack fail to deploy.

Having the stream/latest - would not work:

❌ Deployment failed: Error: The stack named app-my-org-lambda-stack failed creation, it may need to be manually deleted from the AWS console: ROLLBACK_COMPLETE: Resource handler returned message: "Invalid request provided: Stream not found: arn:aws:dynamodb:us-east-1:123456789012:table/app-my-org-glb-table1/stream/latest (Service: Lambda, Status Code: 400, Request ID: e165f9e7-f808-4528-b383-4992861e5aa0)" (RequestToken: d98d6f65-21d1-80b0-4ab3-7a3fc9430268, HandlerErrorCode: InvalidRequest)

Unless I am doing it wrong, I've checked this and that:

That sounds good, but how to make it work?

P.S. I used L1 construct to create global table, and tested that it is deploy-able and stream is on there (with some ARN).

if(stackRegion == PRIMARY_REGION) {

            const globalTable = new CfnGlobalTable(this, 'my-org-glb-table1', {
                tableName: "my-org-glb-table1",
                ...

Solution

  • Ok. the idea came from this answer.

    How I did this with the CKD, is like that (and btw GPT failed on that):

    const globalTableTableName = "my-org-glb-table1"

        // getting the stream arn for the table
        const streamArnProvider = this.getStreamArnProviderService(props.lambdaStreamArnProviderArn, ecrRepo);
        const streamArnResource = new CustomResource(this, 'StreamArnResource', {
            serviceToken: streamArnProvider.serviceToken,
            properties: {
                TableName: globalTableTableName // this is where we pass table name to the lambda
            }
        });
        const streamArn = streamArnResource.getAtt('StreamArn').toString();
     const regionalTable = Table.fromTableAttributes(this, `${globalTableTableName}-${Stack.of(this).region}`, {
            tableName: globalTableTableName,
            tableStreamArn: streamArn
        });
    

    where:

     private getStreamArnProviderService(streamArnProviderLambdaArn: string, ecrRepo: IRepository): Provider {
    
        const streamArnProviderLambda = lambda.Function.fromFunctionAttributes(this, 'ImportedLambda', {
            functionArn: streamArnProviderLambdaArn,
            // true, since the Lambda function is in the same environment and we want CDK to manage permissions
            sameEnvironment: true
        });
    
        return new Provider(this, 'StreamArnProviderService', {
            onEventHandler: streamArnProviderLambda,
        });
    }
    

    Basically by introducing +1 lambda that were getting the ARN from the table's ARN per region.

    And the streamArnProviderLambdaArn comes from another stack that crates that streamArnProviderLambda