The serverless kingslayer? - Migration from serverless to CDK

Thumbnail

The serverless kingslayer? - Migration from serverless to CDK

Last day I have been at a customer and suggested using the CDK for Infrastructure as code. He responded with a huge yes. He has worked his way through the CDK Intro Workshop aws-cdk-examples/typescript at master · aws-samples/aws-cdk-examples · GitHub and wanted to use it.

Then we discussed a lambda function. The customer was using the serverless framework - like most of the people. But we decided to use CDK instead. Here is why.

How Dare you? - Why choose a new framework over an existing one?

Pros:

  • Serverless defines state, CDK is a program
  • If you have many AWS infrastructure ressources, you have CDK constructs to define it more easily
  • If you want to access ressources generated by Serverless its sometimes tricky, with CDK you just reference them
  • Modularisation of ressources in serverless means CloudFormation include files, in CDK its real modularisation

Cons:

  • Learning a new framework
  • Serverless most of the time just works
  • Serverless has huge library of plugins
  • Serverless can serve other clouds too

How Could you? - Migrate a Serverless lambda function

Let`s have a look at a simple lambda function:

Hello kingslayer world

First we will create the function with the serverless framework, which only takes minutes.

Diagram: Lambda with APi Gateway

Diagram: Lambda with APi Gateway

Generate lambda with the serverless framework

serverless create --template hello-world
6 serverless create --template hello-world
6
Serverless: Generating boilerplate...
 _______                             __
| _   .-----.----.--.--.-----.---- | .-----.-----.-----.     |                                      |       |       |     |     |     |       |       |       |       |
| -------------------------------- | ----------------------- | ------------------------------------ | ----- | ----- | --- | --- | --- | ----- | ----- | ----- | ----- |
|                                  | ___                     | -__                                  | _     |       |     | -__ | _   |       | -__   | __ -- | __ -- |
| ____                             | _____                   | __                                   | \___/ | _____ | __  |     | __  | _____ | _____ | _____ |       |
|                                  |                         | The Serverless Application Framework |       |       |     |     |     |       |       |       |       |
|                                  | serverless.com, v1.46.0 |                                      |       |       |     |     |     |       |       |       |       |
 -------'

Serverless: Successfully generated boilerplate for template: "hello-world"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name

Now we have:

.
├── handler.js
└── serverless.yml
File Purpose
handler.js Lambda function
serverless.yml Configuration

Generate CDK App

With the CDK we need some more minutes, but if you define a template for that its also very fast.

cdk init app --language=typescript
Applying project template app for typescript
Initializing a new git repository...
Executing npm install...```

Now we have some more:

├── README.md
├── bin
├── cdk.json
├── lib
├── node_modules
├── package-lock.json
├── package.json
└── tsconfig.json
File Purpose
README.md You should have one
bin directory with the main app
cdk.json CDK main configuration
lib directory with the stack modules
node_modules node libraries
package* Node modules configuration
tsconfig.json typescript configuration

So with greater power comes … more files.

Add Lambda resource

We edit the main stack:

vi lib/cdk-stack.ts

to:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import cdk = require('@aws-cdk/core');
import lambda = require('@aws-cdk/aws-lambda');
import path = require('path');


export class CdkStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here
     new lambda.Function(this, 'HelloHandler', {
      code: lambda.Code.asset(path.join(__dirname,  '../lambda')),
      handler: 'hello.handler',
      runtime: lambda.Runtime.NODEJS_8_10,
      memorySize: 1024
    });
  }
}

So we have to add 6 more lines that the serverless example. Note that we choose the name of the directory lambda for ourselves. So it’s easy to have more lambda functions from the start.

Now we create the lambda function directory:

mkdir lambda

Every file in this directory will be zipped to lambda.

Lets create a sample lambda:

vi lambda/hello.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
'use strict';

module.exports.helloWorld = (event, context, callback) => {
  const response = {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': '*', // Required for CORS support to work
    },
    body: JSON.stringify({
      message: 'CDK 1.x ! Your function executed successfully!',
      input: event,
    }),
  };

  callback(null, response);
};

With CDK the template starts without an API Gateway:

Diagram: just Lambda

Diagram: just Lambda

Compare deploy

Deploy serverless

Deploying with serverless is straightforward:

serverless deploy

gives:

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (404 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.................................
Serverless: Stack update finished...
Service Information
service: serverless-hello-world
stage: dev
region: us-east-1
api keys:
  None
endpoints:
  GET - https://mmuds1arad.execute-api.us-east-1.amazonaws.com/dev/hello-world
functions:
  helloWorld: serverless-hello-world-dev-helloWorld

Please note that the region us-east-1 is predefined with the serverless.yml.

And destroy:

serverless remove

You get:

Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
.............
Serverless: Stack removal finished...

If you do not specify a deploymentBucket as shown here, sls will create a new bucket each time you deploy a new function.

A feature wich is missing with CDK is that you only deploy the function code, not the CloudFormation again:

serverless deploy function -f helloWorld

Gives you:

Serverless: Packaging function: helloWorld...
Serverless: Excluding development dependencies...
Serverless: Uploading function: helloWorld (404 B)...
Serverless: Successfully deployed function: helloWorld

You have to change something in handler.js beforehand, so you have something to deploy!

With CDK in combination with SAM, you may execute your lambda localy in an docker lambda environment, which is even more easy for testing. See the CDK Documentation.

Deploy CDK

The first time you use the CDK with assets you have to create a deployment bucket with the “bootstrap” command.

Deploying with CDK has the ability to add the comparism step:

cdk diff

That shows you the changed resources:

Stack CdkStack
The CdkStack stack uses assets, which are currently not accounted for in the diff output! See https://github.com/awslabs/aws-cdk/issues/395
IAM Statement Changes
┌───┬─────────────────────────────────┬────────┬────────────────┬──────────────────────────────────┬───────────┐
│   │ Resource                        │ Effect │ Action         │ Principal                        │ Condition │
├───┼─────────────────────────────────┼────────┼────────────────┼──────────────────────────────────┼───────────┤
│ + │ ${HelloHandler/ServiceRole.Arn} │ Allow  │ sts:AssumeRole │ Service:lambda.${AWS::URLSuffix} │           │
└───┴─────────────────────────────────┴────────┴────────────────┴──────────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬─────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                    │ Managed Policy ARN                                                             │
├───┼─────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${HelloHandler/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴─────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np)

Parameters
[+] Parameter HelloHandler/Code/S3Bucket HelloHandlerCodeS3Bucket4359A483: {"Type":"String","Description":"S3 bucket for asset \"CdkStack/HelloHandler/Code\""}
[+] Parameter HelloHandler/Code/S3VersionKey HelloHandlerCodeS3VersionKey07D12610: {"Type":"String","Description":"S3 key for asset version \"CdkStack/HelloHandler/Code\""}
[+] Parameter HelloHandler/Code/ArtifactHash HelloHandlerCodeArtifactHash5DF4E4B6: {"Type":"String","Description":"Artifact hash for asset \"CdkStack/HelloHandler/Code\""}

Resources
[+] AWS::IAM::Role HelloHandler/ServiceRole HelloHandlerServiceRole11EF7C63
[+] AWS::Lambda::Function HelloHandler HelloHandler2E4FBA4D

This comes in very handy especially with production environment when you really want to know whether a resource is updated, created or delete and recreated.

Then deploy:

cdk deploy

Gives:

cdk deploy
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬─────────────────────────────────┬────────┬────────────────┬──────────────────────────────────┬───────────┐
│   │ Resource                        │ Effect │ Action         │ Principal                        │ Condition │
├───┼─────────────────────────────────┼────────┼────────────────┼──────────────────────────────────┼───────────┤
│ + │ ${HelloHandler/ServiceRole.Arn} │ Allow  │ sts:AssumeRole │ Service:lambda.${AWS::URLSuffix} │           │
└───┴─────────────────────────────────┴────────┴────────────────┴──────────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬─────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                    │ Managed Policy ARN                                                             │
├───┼─────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${HelloHandler/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴─────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np)

Do you wish to deploy these changes (y/n)?

and

CdkStack: deploying...
Updated: asset.4460f9e1f069325b4df0d02c7ccdfebb4ceb607caf3d28422cf04274907fb15f (zip)
CdkStack: creating CloudFormation changeset...
 0/4 | 8:04:50 AM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata    | CDKMetadata
 0/4 | 8:04:50 AM | CREATE_IN_PROGRESS   | AWS::IAM::Role        | HelloHandler/ServiceRole (HelloHandlerServiceRole11EF7C63)
 0/4 | 8:04:50 AM | CREATE_IN_PROGRESS   | AWS::IAM::Role        | HelloHandler/ServiceRole (HelloHandlerServiceRole11EF7C63) Resource creation Initiated
 0/4 | 8:04:51 AM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata    | CDKMetadata Resource creation Initiated
 1/4 | 8:04:51 AM | CREATE_COMPLETE      | AWS::CDK::Metadata    | CDKMetadata
 2/4 | 8:05:08 AM | CREATE_COMPLETE      | AWS::IAM::Role        | HelloHandler/ServiceRole (HelloHandlerServiceRole11EF7C63)
 2/4 | 8:05:20 AM | CREATE_IN_PROGRESS   | AWS::Lambda::Function | HelloHandler (HelloHandler2E4FBA4D)
 2/4 | 8:05:21 AM | CREATE_IN_PROGRESS   | AWS::Lambda::Function | HelloHandler (HelloHandler2E4FBA4D) Resource creation Initiated
 3/4 | 8:05:21 AM | CREATE_COMPLETE      | AWS::Lambda::Function | HelloHandler (HelloHandler2E4FBA4D)
 4/4 | 8:05:22 AM | CREATE_COMPLETE      | AWS::CloudFormation::Stack | CdkStack

 ✅  CdkStack

Stack ARN:
arn:aws:cloudformation:eu-central-1:669453403305:stack/CdkStack/06554110-a9eb-11e9-b01a-0692890e48f0

With the additional parameter cdk deploy --require-approval never you will not be asked to confirm.

And destroy:

cdk destroy

Gives you:

Are you sure you want to delete: CdkStack (y/n)? y
CdkStack: destroying...

 ✅  CdkStack: destroyed

Or

cdk destroy --force

without confirmation. Changes wich are made only to the lambda asset will be noticed and deployed.

Migrate basic configuration

Lets look at the serverless.yaml and the matching parts in CDK:

Serverless CDK
service: serverless-hello-world Stack Name/Function name
Provider: name: aws Its always AWS
runtime: nodejs10.x lib/cdk-stack.ts: runtime: lambda.Runtime.NODEJS_8_10
handler: handler.helloWorld handler: ‘hello.handler’
events we will come to that later

So its not so different.

Events: http

If you use a lambda inside aws, you call the aws api directly. From the “outside” you have to put an api gateway in front of the lambda to create a REST API. You could also attach an lambda to an Application Load Balancer. Lets use the Api Gateway here:

Migrate API Gateway Basics

Serverless API

In serverless its very short to define a API Gateway:

    events:
      - http:
          path: hello-world
          method: get
          cors: true

Gives you an deployed APi endpoint, like shown in serverless deploy:

endpoints:
  GET - https://g72uv1y4e2.execute-api.eu-central-1.amazonaws.com/dev/hello-world

Now we may access the endpoint:

wget https://g72uv1y4e2.execute-api.eu-central-1.amazonaws.com/dev/hello-world

which gives us the output, including:

"message":"Go Serverless v1.0!  Your function executed successfully!",

CDK API: choose you path

With the CDK you may use CDK constructs or AWS SAM. But first let`s have a look at the Api Gateway resource.

Defining API Gateway in pure CloudFormation (also in terraform) is not very convinient, so AWS created SAM (serverless application modell) for that.

Why is defining the API in pure CloudFormation complex? This comes from the many moving parts, which have to reference each other.

Looking at the Cloudformation documentation you see the method and the request (and many other) resource types. Refer e.g. here to see an example of the referencing .

These are the minimum of resources for creating the APIGateway:

  1. API: AWS::ApiGateway::RestApi
  2. Resource AWS::ApiGateway::Resource references API
  3. Method AWS::ApiGateway::Method references resource

and some more to deploy the api.

So you would have to have at least:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Resources:
  # 1 API
  HelloAPI:
    Type: AWS::ApiGateway::RestApi
  # 2 Resource
  HelloAPIproxy0FA343AE:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId:
        Fn::GetAtt:
          - HelloAPI
          - RootResourceId
      RestApiId:
        Ref: HelloAPI15CE0595
  # 3 Method
  HelloAPIproxyANY3911E02A:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: ANY
      ResourceId:
        Ref: HelloAPIproxy0FA343AE
      RestApiId:
        Ref: HelloAPI
  # some more
  HelloAPIDeployment:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId:
        Ref: HelloAPI
  HelloAPIDeploymentStageprod9702371B:
    Type: AWS::ApiGateway::Stage
    Properties:
      RestApiId:
        Ref: HelloAPI

Redundant references are in lines 9,14,21,23,29,34

With the AWS Serverless Application Model it has become easy:

See SAM Template Concepts:

Events:

        ThumbnailApi:
            # Define an API Gateway endpoint that responds to HTTP GET at /thumbnail
            Type: Api
            Properties:
                Path: /thumbnail
                Method: GET

Now you may use SAM with CDK. But with the CDK its also easy. So we will create the APIGateway with CDK constructs.

CDK API with basic constructs

CDK constructs are designed to abstract details. So it is no surprise its simpler as in pure CloudFormation:

You add the apigateway library:

npm i import @aws-cdk/aws-apigateway --save

and include the api gateway into cdk-agw/lib/cdk-stack.ts:

1
2
3
    new LambdaRestApi(this, 'HelloAPI', {
      handler: fn,
    });

Let`s compare this to the serverless.yml definition:

    events:
      - http:
          path: hello-world
          method: get
          cors: true

The main difference is that the static yaml definition is “stupid”. It does not know what it defines. In the CDK definition, through the cdk api it is defined that this const api really is an apigateway.

Because of that it is possible that the editor shows you information about the context apigateway, which is a huge benefit during development:

code-completion

While in yaml the editor is lost in trying to show something usefull:

code-completion-yaml

Deploy CDK apigateway construct

Now the concept of the CDK should has become clearer. As serverless tries to make things easy by not telling you whats happens under the hood, CDK tries to make things easy by telling you whats happens in detail or by giving you methodes to access the underlying resources.

So the “http event” in sls becomes an “LambdaRestApi” in CDK.

With cdk diff we get also information about the CloudFormation resources which will be added for the API Gateway:

[+] AWS::ApiGateway::RestApi HelloAPI HelloAPI15CE0595
[+] AWS::ApiGateway::Deployment HelloAPI/Deployment HelloAPIDeploymentC6DB7C2Cfc9698904ca98f9c6235ab6301368b0c
[+] AWS::ApiGateway::Stage HelloAPI/DeploymentStage.prod HelloAPIDeploymentStageprod9702371B
[+] AWS::ApiGateway::Account HelloAPI/Account HelloAPIAccount55AE3404
[+] AWS::ApiGateway::Resource HelloAPI/Default/{proxy+} HelloAPIproxy0FA343AE
[+] AWS::ApiGateway::Method HelloAPI/Default/{proxy+}/ANY HelloAPIproxyANY3911E02A
[+] AWS::ApiGateway::Method HelloAPI/Default/ANY HelloAPIANY99C05532

Evolution of your function

Now we will add an event supported by sls. After that we will do something which is not directly supported by sls.

Migrate events around the clock: Cron

Diagram: Lambda with cron

Diagram: Lambda with cron

Serverless CloudWatch events

You have a lambda which should be called at 12 minutes past midnight each day. How do we do that? With a CloudWatch events rule.

With the supported events from serverless, you just add the event to the serverless.yml:

functions:
  handler: handler.helloWorld
  events:
    - schedule: cron(0 12 * * ? *)

CDK CloudWatch events

In CDK its also simple:

23
24
25
26
27
   const rule = new Rule(this, 'Rule', {
      schedule: Schedule.expression('cron(0 12 * * ? *)')
    });

    rule.addTarget(new LambdaFunction(fn));

No we increase the difficulty!

Extending: add an SNS Topic to existing Cfn Rule

Suppose now I want to add an SNS topic to the CloudWatch rule.

Diagram: Lambda with cron and SNS

Diagram: Lambda with cron and SNS

Extend the CDK app

With CDK its simple - you add the SNS topic in line 30 and add that topic as a target for the CloudWatch event rule (line 32).

30
31
32
  const topic = new sns.Topic(this, "blogtopic");

  rule.addTarget( new SnsTopic(topic));

Were done!

Extend serverless

Serverless is great and simple of you work whithin the given boundaries. Beyond this it can get tricky..

Plugins

With Serverless you are lucky if there is an existing and working plugin. So search at plugin page for a solution.

Some of the plugins just extend Cfn. E.g. a CloudWatch Logs subscription has its own serverless plugin: Log subscription plugin This is just a construct in CDK,see Log Subscription Destination.

But in this UseCase we have no plugin for adding targets to the event rule! So we have to extend the generated CloudFormation or write our own serverless plugin. See variables in serverless documentation.

Because writing a plugin just for this simple task would be overkill, we (try) to extend the serverless generated Cfn.

Access CloudFormation Resource in serverless

So, no luck with plugins, then you may choose to access the generated CloudFormation.

From the working CloudFormation from the CDK we learn a way to add another target to the eventrule:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 Rule4C995B7F:
    Type: 'AWS::Events::Rule'
    Properties:
      ScheduleExpression: cron(0 12 * * ? *)
      State: ENABLED
      Targets:
        - Arn:
            'Fn::GetAtt':
              - HelloHandler2E4FBA4D
              - Arn
          Id: Target0
        - Arn:
            Ref: blogtopic91581ADD
          Id: Target1

We only need an sns topic with policy and the additional lines 12-14 to add the topic as an target to the CloudWatch event rule.

For details read the fine Cfn manual

Let´s do this in serverless. You can add CloudFormation resources in CloudFormation yaml at a resource section in serverless.yml.

Here is the point: From now on you only can create a more complex solution in serverless as with the CDK.

Because any Cfn resource, which has no high level construct in the CDK, has an low-level CFN resources. See the CDK API Reference for more details on this.

So if the resource is already a simple to use higher level construct, its simpler to develop in CDK. If the resource only has a low-level Cfn resource, then the effort is nearly the same.

But lets see how much effort this scenario is in sls (serverless).

First see AWS Resources in the serverless documentation.

In the documentation we find Events::Rule with Schedule: {normalizedFunctionName}EventsRuleSchedule{SequentialID} as the name or this resource.

You may have a look at the generated CloudFormation from serverless by doing:

  1. serverless package`
  2. See generated .serverless/cloudformation-template-update-stack.json`

This is the sls generated events rule so far:

"HelloWorldEventsRuleSchedule1": {
      "Type": "AWS::Events::Rule",
      "Properties": {
        "ScheduleExpression": "cron(0 12 * * ? *)",
        "State": "ENABLED",
        "Targets": [
          {
            "Arn": {
              "Fn::GetAtt": [
                "HelloWorldLambdaFunction",
                "Arn"
              ]
            },
            "Id": "helloWorldSchedule"
          }
        ]
      }

So we add a resource section to the serverless.yml. In this section you can add CloudFormation to the generated CloudFormation in serverless.

There we create the sns topic as CloudFormation. In serverless you may create an SNS topic as an event bound to the lambda function. But if you want a standalone topic, you have to fall back to CloudFormation:

Here is the configuration added to serverless.yml to get an sns topic and policy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
resources:
  Resources:
    blogtopic91581ADD:
      Type: 'AWS::SNS::Topic'
    blogtopicPolicy5228F783:
      Type: 'AWS::SNS::TopicPolicy'
      Properties:
        PolicyDocument:
          Statement:
            - Action: 'sns:Publish'
              Effect: Allow
              Principal:
                Service:
                  'Fn::Join':
                    - ''
                    - - events.
                      - Ref: 'AWS::URLSuffix'
              Resource:
                Ref: blogtopic91581ADD
              Sid: '0'
          Version: 2012-10-17
        Topics:
          - Ref: blogtopic91581ADD
    HelloWorldEventsRuleSchedule1:
      Properties:
        Targets:
          - Arn:
            - Ref: blogtopic91581ADD

And these were the lines from above which the cdk needs to create an sns topic:

1
2
  const topic = new sns.Topic(this, "blogtopic");
  rule.addTarget( new SnsTopic(topic));

With serverless you may mix attributes into the generated CloudFormation, so with the right reference to the generated resource you can alter the resource.

The normalizedFunctionName is HelloWorld so we add to the resources section the code which should add the new topic blogtopic to the rule:

    HelloWorldEventsRuleSchedule1:
      Properties:
        Targets:
          - Arn: 
              Ref: blogtopic
              Id: TopicTarget

Serverless should add another target to the events rule. Unfortunately this only works with additional properties.

So this attempt leads to this generated CloudFormation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 "HelloWorldEventsRuleSchedule1": {
      "Type": "AWS::Events::Rule",
      "Properties": {
        "ScheduleExpression": "cron(0 12 * * ? *)",
        "State": "ENABLED",
        "Targets": [
          {
            "Arn": {
              "Fn::GetAtt": [
                "HelloWorldLambdaFunction",
                "Arn"
              ],
              "Ref": "blogtopic",
              "Id": "TopicTarget"
            },
            "Id": "helloWorldSchedule"
          }
        ]
      }

This is wrong, because in line 13 the reference to the topic was addes to the first entry in the Targets array instead of beeing added in another array item. The problem is, that all ids from the items are the same: Arn.

Surrender

So its not possbile to mix a new target into the cfn in a simple way.

One solution would be creating these resources manually in the resource section of the serverless.yml:

  • AWS::SNS::Topic
  • AWS::SNS::TopicPolicy
  • AWS::Events::Rule

and reference one ARN in the Events::Rule to the generated Lambda Funktion. This would be a fallback to doing most things manually.

Now Choose your side: declarative with serverless or imparative with CDK

My conclusion - only for AWS projects - is that if you stay within the boundaries of serverless, it can still be a good fit for your project.

But if you extend your Lamdas with additional AWS Services, CDK have some serious advantages over serverless and to be frank over AWS SAM too.

What do you think?