Skip to content

Commit

Permalink
new pattern for schdules with auto delete
Browse files Browse the repository at this point in the history
  • Loading branch information
Boyne committed Aug 9, 2023
1 parent 7ebf554 commit ecd337a
Show file tree
Hide file tree
Showing 15 changed files with 492 additions and 0 deletions.
73 changes: 73 additions & 0 deletions eventbridge-schedule-dynamic-with-auto-deletion/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Dynamic schedules with auto deletion with Amazon EventBridge Scheduler
Event-driven pattern that creates three schedules when records get added to a DynamoDB table. These schedules trigger an email service and are automatically deleted.

![alt](./architecture.png)

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/eventbridge-schedule-dynamic-with-auto-deletion.

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.


## Requirements

- [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
- [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
- [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed and configured

## Deployment Instructions

1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
```bash
git clone https://github.com/aws-samples/serverless-patterns
```
2. Change directory to the pattern directory:
```bash
cd serverless-patterns/eventbridge-schedule-dynamic-with-auto-deletion/src
```
3. Install dependencies:
```bash
npm install
```
4. From the command line, configure AWS CDK:
```bash
cdk bootstrap ACCOUNT-NUMBER/REGION # e.g.
cdk bootstrap 1111111111/us-east-1
cdk bootstrap --profile test 1111111111/us-east-1
```
5. From the command line, use AWS CDK to deploy the AWS resources for the pattern as specified in the `lib/src-stack.ts` file:
```bash
npm run build && cdk deploy
```

## How it works

- New record is inserted into DynamoDB (new user signs up)
- EventBridge Pipes is used to connect the DynamoDB stream to EventBridge.
- Every time an `INSERT` happens, a new EventBridge event is raised `NewUserCreated`
- ScheduleCreator Lambda function is then triggered, creating three schedules for the user using Amazon EventBridge Scheduler.
- All schedules are removed once they run (auto deletion).
- Each schedule triggers the `Email Service` Lambda function. (Placeholder of what an email service could be.)

## Testing

Deploy the stack and run the script:

```sh
sh src/insert-record.sh
```

This script will insert a new record into your new database `ServerlessLandUsers`. Then open the console and go to EventBridge Schedules. You will see three schedules created for each user (2 minutes from creation, 24 hours and 1 week).

## Cleanup

1. Delete the stack
```bash
cdk destroy
```

---

Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"title": "Dynamic schedules with auto deletion with Amazon EventBridge Scheduler",
"description": "Event-driven pattern that creates three schedules when records get added to a DynamoDB table. These schedules trigger an email service and are automatically deleted.",
"language": "TypeScript",
"level": "200",
"framework": "CDK",
"introBox": {
"headline": "How it works",
"text": [
"When a new user signs up a record is added into the DynamoDB table. Using EventBridge pipes, changes are captured and transformed into an EventBridge event. Downstream consumers listen and create schedules for each user that automatically delete."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/eventbridge-schedule-dynamic-with-auto-deletion",
"templateURL": "serverless-patterns/eventbridge-schedule-dynamic-with-auto-deletion",
"projectFolder": "eventbridge-schedule-dynamic-with-auto-deletion",
"templateFile": "src/src/schedule-creator/index.ts"
}
},
"resources": {
"bullets": [
{
"text": "Automatically delete schedules upon completion with Amazon EventBridge Scheduler",
"link": "https://aws.amazon.com/blogs/compute/automatically-delete-schedules-upon-completion-with-amazon-eventbridge-scheduler/"
},
{
"text": "EventBridge Visuals",
"link": "https://serverlessland.com/serverless/visuals/eventbridge"
}
]
},
"deploy": {
"text": [
"<code>cdk deploy</code>"
]
},
"testing": {
"text": [
"See the GitHub repo for detailed testing instructions."
]
},
"cleanup": {
"text": [
"Delete the stack: <code>cdk destroy</code>."
]
},
"authors": [
{
"name": "David Boyne",
"image": "../assets/images/resources/dboyne.png",
"bio": "Senior Developer Advocate at AWS focusing on EDA and Serverless."
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
15 changes: 15 additions & 0 deletions eventbridge-schedule-dynamic-with-auto-deletion/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Welcome to your CDK TypeScript project

You should explore the contents of this project. It demonstrates a CDK app with an instance of a stack (`SrcStack`)
which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk synth` emits the synthesized CloudFormation template
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { DynamicEventBridgeSchedulesStack } from '../lib/src-stack';

const app = new cdk.App();
new DynamicEventBridgeSchedulesStack(app, 'DynamicEventBridgeSchedules');
44 changes: 44 additions & 0 deletions eventbridge-schedule-dynamic-with-auto-deletion/src/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"app": "npx ts-node --prefer-ts-exts bin/src.ts",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
}
};
147 changes: 147 additions & 0 deletions eventbridge-schedule-dynamic-with-auto-deletion/src/lib/src-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as pipes from 'aws-cdk-lib/aws-pipes';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import * as path from 'path';
import { CfnScheduleGroup } from 'aws-cdk-lib/aws-scheduler';
import { LambdaTarget } from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets';

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

// The DynamoDB table for users
const usersTable = new dynamodb.Table(this, 'usersTable', {
tableName: 'ServerlessLandUsers',
partitionKey: {
name: 'userId',
type: dynamodb.AttributeType.STRING,
},
removalPolicy: cdk.RemovalPolicy.DESTROY,
stream: dynamodb.StreamViewType.NEW_IMAGE,
});

// Event Bus used for application
const eventBus = new events.EventBus(this, 'ServerlessLandEventBus', {
eventBusName: 'ServerlessLandEventBus',
});

// Schedule group for all the user schedules
const userScheduleGroup = new CfnScheduleGroup(this, 'UserScheduleGroup', {
name: 'UserScheduleGroup',
});

// Example function that would be the email service.
const emailService: NodejsFunction = new NodejsFunction(this, 'email-service', {
memorySize: 1024,
timeout: cdk.Duration.seconds(5),
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'handler',
entry: path.join(__dirname, '../src/email-service/index.ts'),
});

const scheduleRole = new iam.Role(this, 'scheduleRole', {
assumedBy: new iam.ServicePrincipal('scheduler.amazonaws.com'),
});

emailService.grantInvoke(scheduleRole);

// Example function that will create schedules for users
const scheduleCreator: NodejsFunction = new NodejsFunction(this, 'schedule-creator', {
memorySize: 1024,
timeout: cdk.Duration.seconds(5),
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'handler',
entry: "./src/schedule-creator/index.ts",
bundling: {
nodeModules: ['@aws-sdk/client-scheduler']
},
environment: {
SCHEDULE_GROUP_NAME: userScheduleGroup.name || '',
EMAIL_SERVICE_ARN: emailService.functionArn,
SCHEDULE_ROLE_ARN: scheduleRole.roleArn
},
initialPolicy: [
// Give lambda permission to create group, schedule and pass IAM role to the scheduler
new iam.PolicyStatement({
// actions: ['scheduler:CreateSchedule', 'iam:PassRole', 'scheduler:CreateScheduleGroup'],
actions: ['scheduler:CreateSchedule', 'iam:PassRole'],
resources: ['*'],
}),
],
});

// CloudWatch Log group to catch all events through this event bus, for debugging.
new events.Rule(this, 'catchAllLogRule', {
ruleName: 'catch-all',
eventBus: eventBus,
eventPattern: {
source: events.Match.prefix(''),
},
targets: [
new targets.CloudWatchLogGroup(
new logs.LogGroup(this, 'ServerlessLandEventBusAllEvents', {
logGroupName: '/aws/events/ServerlessLandEventBus/logs',
removalPolicy: cdk.RemovalPolicy.DESTROY,
})
),
],
});

// Create a rule. Listen for event and trigger lambda to create schedule
new events.Rule(this, 'create-schedules', {
ruleName: 'create-schedules-on-new-user',
eventBus: eventBus,
eventPattern: {
source: events.Match.exactString('serverlessland.users'),
detailType: events.Match.exactString('NewUserCreated'),
},
targets: [
new targets.LambdaFunction(scheduleCreator)
]
});

const pipeRole = new iam.Role(this, 'pipeRole', {
assumedBy: new iam.ServicePrincipal('pipes.amazonaws.com'),
});



usersTable.grantReadWriteData(scheduleCreator);
usersTable.grantStreamRead(pipeRole);
eventBus.grantPutEventsTo(pipeRole);

// Create EventBridge Pipe, to connect new DynamoDB items to EventBridge.
new pipes.CfnPipe(this, 'pipe', {
roleArn: pipeRole.roleArn,
source: usersTable.tableStreamArn!,
sourceParameters: {
dynamoDbStreamParameters: {
startingPosition: 'LATEST',
batchSize: 3,
},
filterCriteria: {
filters: [
{
pattern: '{"eventName" : ["INSERT"] }',
},
],
},
},
target: eventBus.eventBusArn,
targetParameters: {
eventBridgeEventBusParameters: {
detailType: 'NewUserCreated',
source: 'serverlessland.users',
},
inputTemplate: '{"userId": <$.dynamodb.NewImage.userId.S>}',
},
});
}
}
Loading

0 comments on commit ecd337a

Please sign in to comment.