Skip to content

Commit ecd337a

Browse files
author
Boyne
committed
new pattern for schdules with auto delete
1 parent 7ebf554 commit ecd337a

15 files changed

Lines changed: 492 additions & 0 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Dynamic schedules with auto deletion with Amazon EventBridge Scheduler
2+
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.
3+
4+
![alt](./architecture.png)
5+
6+
Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/eventbridge-schedule-dynamic-with-auto-deletion.
7+
8+
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.
9+
10+
11+
## Requirements
12+
13+
- [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.
14+
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
15+
- [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
16+
- [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed and configured
17+
18+
## Deployment Instructions
19+
20+
1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
21+
```bash
22+
git clone https://github.com/aws-samples/serverless-patterns
23+
```
24+
2. Change directory to the pattern directory:
25+
```bash
26+
cd serverless-patterns/eventbridge-schedule-dynamic-with-auto-deletion/src
27+
```
28+
3. Install dependencies:
29+
```bash
30+
npm install
31+
```
32+
4. From the command line, configure AWS CDK:
33+
```bash
34+
cdk bootstrap ACCOUNT-NUMBER/REGION # e.g.
35+
cdk bootstrap 1111111111/us-east-1
36+
cdk bootstrap --profile test 1111111111/us-east-1
37+
```
38+
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:
39+
```bash
40+
npm run build && cdk deploy
41+
```
42+
43+
## How it works
44+
45+
- New record is inserted into DynamoDB (new user signs up)
46+
- EventBridge Pipes is used to connect the DynamoDB stream to EventBridge.
47+
- Every time an `INSERT` happens, a new EventBridge event is raised `NewUserCreated`
48+
- ScheduleCreator Lambda function is then triggered, creating three schedules for the user using Amazon EventBridge Scheduler.
49+
- All schedules are removed once they run (auto deletion).
50+
- Each schedule triggers the `Email Service` Lambda function. (Placeholder of what an email service could be.)
51+
52+
## Testing
53+
54+
Deploy the stack and run the script:
55+
56+
```sh
57+
sh src/insert-record.sh
58+
```
59+
60+
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).
61+
62+
## Cleanup
63+
64+
1. Delete the stack
65+
```bash
66+
cdk destroy
67+
```
68+
69+
---
70+
71+
Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
72+
73+
SPDX-License-Identifier: MIT-0
230 KB
Loading
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"title": "Dynamic schedules with auto deletion with Amazon EventBridge Scheduler",
3+
"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.",
4+
"language": "TypeScript",
5+
"level": "200",
6+
"framework": "CDK",
7+
"introBox": {
8+
"headline": "How it works",
9+
"text": [
10+
"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."
11+
]
12+
},
13+
"gitHub": {
14+
"template": {
15+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/eventbridge-schedule-dynamic-with-auto-deletion",
16+
"templateURL": "serverless-patterns/eventbridge-schedule-dynamic-with-auto-deletion",
17+
"projectFolder": "eventbridge-schedule-dynamic-with-auto-deletion",
18+
"templateFile": "src/src/schedule-creator/index.ts"
19+
}
20+
},
21+
"resources": {
22+
"bullets": [
23+
{
24+
"text": "Automatically delete schedules upon completion with Amazon EventBridge Scheduler",
25+
"link": "https://aws.amazon.com/blogs/compute/automatically-delete-schedules-upon-completion-with-amazon-eventbridge-scheduler/"
26+
},
27+
{
28+
"text": "EventBridge Visuals",
29+
"link": "https://serverlessland.com/serverless/visuals/eventbridge"
30+
}
31+
]
32+
},
33+
"deploy": {
34+
"text": [
35+
"<code>cdk deploy</code>"
36+
]
37+
},
38+
"testing": {
39+
"text": [
40+
"See the GitHub repo for detailed testing instructions."
41+
]
42+
},
43+
"cleanup": {
44+
"text": [
45+
"Delete the stack: <code>cdk destroy</code>."
46+
]
47+
},
48+
"authors": [
49+
{
50+
"name": "David Boyne",
51+
"image": "../assets/images/resources/dboyne.png",
52+
"bio": "Senior Developer Advocate at AWS focusing on EDA and Serverless."
53+
}
54+
]
55+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.js
2+
!jest.config.js
3+
*.d.ts
4+
node_modules
5+
6+
# CDK asset staging directory
7+
.cdk.staging
8+
cdk.out
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.ts
2+
!*.d.ts
3+
4+
# CDK asset staging directory
5+
.cdk.staging
6+
cdk.out
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Welcome to your CDK TypeScript project
2+
3+
You should explore the contents of this project. It demonstrates a CDK app with an instance of a stack (`SrcStack`)
4+
which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic.
5+
6+
The `cdk.json` file tells the CDK Toolkit how to execute your app.
7+
8+
## Useful commands
9+
10+
* `npm run build` compile typescript to js
11+
* `npm run watch` watch for changes and compile
12+
* `npm run test` perform the jest unit tests
13+
* `cdk deploy` deploy this stack to your default AWS account/region
14+
* `cdk diff` compare deployed stack with current state
15+
* `cdk synth` emits the synthesized CloudFormation template
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env node
2+
import * as cdk from 'aws-cdk-lib';
3+
import { DynamicEventBridgeSchedulesStack } from '../lib/src-stack';
4+
5+
const app = new cdk.App();
6+
new DynamicEventBridgeSchedulesStack(app, 'DynamicEventBridgeSchedules');
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts bin/src.ts",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"**/*.d.ts",
11+
"**/*.js",
12+
"tsconfig.json",
13+
"package*.json",
14+
"yarn.lock",
15+
"node_modules",
16+
"test"
17+
]
18+
},
19+
"context": {
20+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21+
"@aws-cdk/core:checkSecretUsage": true,
22+
"@aws-cdk/core:target-partitions": [
23+
"aws",
24+
"aws-cn"
25+
],
26+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29+
"@aws-cdk/aws-iam:minimizePolicies": true,
30+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35+
"@aws-cdk/core:enablePartitionLiterals": true,
36+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37+
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
38+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
39+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
40+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
41+
"@aws-cdk/aws-route53-patters:useCertificate": true,
42+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false
43+
}
44+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
roots: ['<rootDir>/test'],
4+
testMatch: ['**/*.test.ts'],
5+
transform: {
6+
'^.+\\.tsx?$': 'ts-jest'
7+
}
8+
};
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import * as cdk from 'aws-cdk-lib';
2+
import { Construct } from 'constructs';
3+
import * as events from 'aws-cdk-lib/aws-events';
4+
import * as targets from 'aws-cdk-lib/aws-events-targets';
5+
import * as logs from 'aws-cdk-lib/aws-logs';
6+
import * as iam from 'aws-cdk-lib/aws-iam';
7+
import * as pipes from 'aws-cdk-lib/aws-pipes';
8+
import * as lambda from 'aws-cdk-lib/aws-lambda';
9+
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
10+
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
11+
import * as path from 'path';
12+
import { CfnScheduleGroup } from 'aws-cdk-lib/aws-scheduler';
13+
import { LambdaTarget } from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets';
14+
15+
export class DynamicEventBridgeSchedulesStack extends cdk.Stack {
16+
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
17+
super(scope, id, props);
18+
19+
// The DynamoDB table for users
20+
const usersTable = new dynamodb.Table(this, 'usersTable', {
21+
tableName: 'ServerlessLandUsers',
22+
partitionKey: {
23+
name: 'userId',
24+
type: dynamodb.AttributeType.STRING,
25+
},
26+
removalPolicy: cdk.RemovalPolicy.DESTROY,
27+
stream: dynamodb.StreamViewType.NEW_IMAGE,
28+
});
29+
30+
// Event Bus used for application
31+
const eventBus = new events.EventBus(this, 'ServerlessLandEventBus', {
32+
eventBusName: 'ServerlessLandEventBus',
33+
});
34+
35+
// Schedule group for all the user schedules
36+
const userScheduleGroup = new CfnScheduleGroup(this, 'UserScheduleGroup', {
37+
name: 'UserScheduleGroup',
38+
});
39+
40+
// Example function that would be the email service.
41+
const emailService: NodejsFunction = new NodejsFunction(this, 'email-service', {
42+
memorySize: 1024,
43+
timeout: cdk.Duration.seconds(5),
44+
runtime: lambda.Runtime.NODEJS_18_X,
45+
handler: 'handler',
46+
entry: path.join(__dirname, '../src/email-service/index.ts'),
47+
});
48+
49+
const scheduleRole = new iam.Role(this, 'scheduleRole', {
50+
assumedBy: new iam.ServicePrincipal('scheduler.amazonaws.com'),
51+
});
52+
53+
emailService.grantInvoke(scheduleRole);
54+
55+
// Example function that will create schedules for users
56+
const scheduleCreator: NodejsFunction = new NodejsFunction(this, 'schedule-creator', {
57+
memorySize: 1024,
58+
timeout: cdk.Duration.seconds(5),
59+
runtime: lambda.Runtime.NODEJS_18_X,
60+
handler: 'handler',
61+
entry: "./src/schedule-creator/index.ts",
62+
bundling: {
63+
nodeModules: ['@aws-sdk/client-scheduler']
64+
},
65+
environment: {
66+
SCHEDULE_GROUP_NAME: userScheduleGroup.name || '',
67+
EMAIL_SERVICE_ARN: emailService.functionArn,
68+
SCHEDULE_ROLE_ARN: scheduleRole.roleArn
69+
},
70+
initialPolicy: [
71+
// Give lambda permission to create group, schedule and pass IAM role to the scheduler
72+
new iam.PolicyStatement({
73+
// actions: ['scheduler:CreateSchedule', 'iam:PassRole', 'scheduler:CreateScheduleGroup'],
74+
actions: ['scheduler:CreateSchedule', 'iam:PassRole'],
75+
resources: ['*'],
76+
}),
77+
],
78+
});
79+
80+
// CloudWatch Log group to catch all events through this event bus, for debugging.
81+
new events.Rule(this, 'catchAllLogRule', {
82+
ruleName: 'catch-all',
83+
eventBus: eventBus,
84+
eventPattern: {
85+
source: events.Match.prefix(''),
86+
},
87+
targets: [
88+
new targets.CloudWatchLogGroup(
89+
new logs.LogGroup(this, 'ServerlessLandEventBusAllEvents', {
90+
logGroupName: '/aws/events/ServerlessLandEventBus/logs',
91+
removalPolicy: cdk.RemovalPolicy.DESTROY,
92+
})
93+
),
94+
],
95+
});
96+
97+
// Create a rule. Listen for event and trigger lambda to create schedule
98+
new events.Rule(this, 'create-schedules', {
99+
ruleName: 'create-schedules-on-new-user',
100+
eventBus: eventBus,
101+
eventPattern: {
102+
source: events.Match.exactString('serverlessland.users'),
103+
detailType: events.Match.exactString('NewUserCreated'),
104+
},
105+
targets: [
106+
new targets.LambdaFunction(scheduleCreator)
107+
]
108+
});
109+
110+
const pipeRole = new iam.Role(this, 'pipeRole', {
111+
assumedBy: new iam.ServicePrincipal('pipes.amazonaws.com'),
112+
});
113+
114+
115+
116+
usersTable.grantReadWriteData(scheduleCreator);
117+
usersTable.grantStreamRead(pipeRole);
118+
eventBus.grantPutEventsTo(pipeRole);
119+
120+
// Create EventBridge Pipe, to connect new DynamoDB items to EventBridge.
121+
new pipes.CfnPipe(this, 'pipe', {
122+
roleArn: pipeRole.roleArn,
123+
source: usersTable.tableStreamArn!,
124+
sourceParameters: {
125+
dynamoDbStreamParameters: {
126+
startingPosition: 'LATEST',
127+
batchSize: 3,
128+
},
129+
filterCriteria: {
130+
filters: [
131+
{
132+
pattern: '{"eventName" : ["INSERT"] }',
133+
},
134+
],
135+
},
136+
},
137+
target: eventBus.eventBusArn,
138+
targetParameters: {
139+
eventBridgeEventBusParameters: {
140+
detailType: 'NewUserCreated',
141+
source: 'serverlessland.users',
142+
},
143+
inputTemplate: '{"userId": <$.dynamodb.NewImage.userId.S>}',
144+
},
145+
});
146+
}
147+
}

0 commit comments

Comments
 (0)