Into Amazon EventBridge? I'm writing a book to help! →
twitter

serverlesseventbridgeschedulespatternpoc

Published on

How to raise EventBridge events in the future with EventBridge Scheduler

A pattern that allows you to trigger EventBridge events in the future, scaling to thousands of events.


  • avatar
    David Boyne
    Published on 7 min read
Blog cover

Amazon EventBridge is a serverless event bus that provides ways to create decoupled event-driven applications. We can define custom events and use EventBridge to create rules, targets, filters for our consumers.

When designing our event-driven applications we may have use-cases where we want to trigger events in the future. An example of this could be “send an email to a customer 24 hours after they create an account” or “notify the customer a week after they left the website with a basket full of items”….

What if we could create an event-driven pattern that can react to domain events and setup a solution to raise events in the future, per customer and scale to thousands of events?

Using Amazon EventBridge Scheduler with templated targets we can setup schedules in the future which will raise events directly onto our event bus. This allows us to create downstream consumers and react to future events.

In this blog post, we will explore reasons why you might want to raise future events and an example of how to raise events directly into EventBridge using the EventBridge Scheduler.

•••

Why raise events in the future?

When building event-driven architectures we often want to react to events in near real-time. EventBridge is a great solution for this as it provides a serverless event bus, handling scaling and infrastructure so we can focus on managing our producers and consumers and creating a scalable decoupled event-driven architecture.

Business requirements may require us to react to events today, and process a scenario in the future. An example of this could be an automated email to a customer 6 hours after they drop-off at a store checkout.

So how can we raise one-time schedules to fire future events with Amazon EventBridge? This is where we can use the EventBridge Scheduler.

•••

Using EventBridge Scheduler to raise EventBridge events in the future

First let’s assume we have a requirement to send customers an email 24 hours after then have signed up.

When a user signs up on our “web application” a UserCreated event is triggered.

We will listen to this event and create a one-time schedule in the future (24 hours later) to send them an email.

Let’s take a look and how we can use EventBridge Scheduler to react to EventBridge events to create future triggers.

Example of using DynamoDB Streams with EventBridgePattern that listens to EventBridge events to then trigger future events with EventBridge Scheduler
  1. User signs up on our web application and a UserCreated event is sent. The UserCreated event contains the userId, firstName and lastName of our user.
  2. An EventBridge rule is setup to listen to the UserCreated event. (We use the source myapp.users to encapsulate all user events to this domain).
// EventBridge rule to create a schedule
new Rule(this, 'UserCreatedRule', {
  description: 'Listen to UseCreated events',
  eventPattern: {
    source: ['myapp.users'],
    detailType: ['UserCreated'],
  },
  eventBus,
}).addTarget(new LambdaFunction(processUserCreatedFunction))
  1. A Lambda function is used to create a new EventBridge schedule. We give Lambda access to create the schedule and we use templated target to directly put an event onto our event bus. A one-time schedule is created for every user that is created in our application.
// create role
const launchRole = new Role(this, 'scheduler-role', {
  assumedBy: new ServicePrincipal('scheduler.amazonaws.com'),
})

// create policy for our schedule, so that it can PutEvents directly onto Eventbus
new Policy(this, 'schedule-policy', {
  policyName: 'ScheduleToPutEvents',
  roles: [launchRole],
  statements: [
    new PolicyStatement({
      effect: Effect.ALLOW,
      actions: ['events:PutEvents'],
      resources: [eventBus.eventBusArn],
    }),
  ],
})

The Lambda function 👇

import { EventBridgeEvent } from 'aws-lambda'
import {
  SchedulerClient,
  CreateScheduleCommand,
  FlexibleTimeWindowMode,
  CreateScheduleGroupCommand,
} from '@aws-sdk/client-scheduler'
import addMinutes from 'date-fns/addMinutes'

const client = new SchedulerClient({})
const minutesInTheFuture = parseInt(process.env.TRIGGER_IN_FUTURE_IN_MINUTES || '2')

interface UserCreated {
  id: string;
  firstName: string;
  lastName: string;
}

// Triggered when UserCreated is published
export async function handler(event: EventBridgeEvent<'UserCreated', UserCreated>) {
  try {
    // Create the schedule group for now, this would be done in CDK when we can
    await client.send(
      new CreateScheduleGroupCommand({
        Name: 'SchedulesForUsers24HoursAfterCreation',
      })
    )
  } catch (error) {}

  try {
    // create schedule for 24 hours in the future
    await client.send(
      new CreateScheduleCommand({
        // add user id on
        Name: `${event.detail.id}-24hr`,
        GroupName: 'SchedulesForUsers24HoursAfterCreation',
        Target: {
          RoleArn: process.env.SCHEDULE_ROLE_ARN,
          Arn: process.env.EVENTBUS_ARN,
          // Using the template targets for EventBridge
          EventBridgeParameters: {
            DetailType: 'UserCreated24HoursAgo',
            // let's define a bounded context/scope for these types of events
            Source: 'scheduler.notifications',
          },
          // This is the detail of the event, we just pass it on
          Input: JSON.stringify({ ...event.detail }),
        },
        FlexibleTimeWindow: {
          Mode: FlexibleTimeWindowMode.OFF,
        },
        Description: `24 hours after user ${event.detail.id} was created`,
        // Take off the ms in the date timestamp and create one-time schedule
        ScheduleExpression: `at(${
          addMinutes(new Date(), minutesInTheFuture).toISOString().split('.')[0]
        })`,
      })
    )
  } catch (error) {
    console.log('failed', error)
  }
}
  1. 24 hours later the schedule runs. This creates a new event and puts it onto our EventBus. We do this by specifying the Target when we create the schedule. We use scheduler.notifications source to store all our Scheduler related events, this is just preference. The event UserCreated24HoursAgo is raised. It’s important to remember to keep events in past tense, the user was created 24 hours ago so let the event name represent that fact.
Target: {
  RoleArn: process.env.SCHEDULE_ROLE_ARN,
  Arn: process.env.EVENTBUS_ARN,
  EventBridgeParameters: {
    DetailType: 'UserCreated24HoursAgo',
    // let's define a bounded context/scope for these types of events
    Source: 'scheduler.notifications',
  },
  // This is the detail of the event
  Input: JSON.stringify({ ...event.detail }),
}
  1. New EventBridge rule is setup to listen for the UserCreated24HoursAgo event. Downstream consumers can now react to users that have signed up 24 hours ago. (Email them, notify them, write reports etc, anything you want).
new Rule(this, 'UserCreated24HoursAgoRule', {
  description: 'Custom notification event when the user has been created 24 hours ago',
  eventPattern: {
    source: ['scheduler.notifications'],
    detailType: ['UserCreated24HoursAgo'],
  },
  eventBus,
}).addTarget(new LambdaFunction(emailCustomer))

That’s it. We have setup a pattern that consumes events in real time (UserCreated), and triggers the creation of a schedule. The schedule is then triggered 24 hours later to send an email to our customer (or anything you want!).

Things to consider using this pattern

  • At the moment the schedule will not be removed once triggered. You will have to manage that yourself (feedback has been given back to the team about this).
  • Consider using schedule groups (up to 500 per account) to group your schedules if you have hundreds/thousands.
  • Consider setting up a dead-letter queue for your schedules to capture any failures.
•••

Summary

Amazon EventBridge is a great service that allows us to create decoupled event-driven architectures and process event in near-real time. Sometimes you might find yourself wanting to trigger events in the future for future processing and this is where EventBridge Scheduler can help.

Using the EventBridge Scheduler we can setup schedules that target over 200 services and 6000 APIs. We can utilise templated targets (as seen in this example pattern) to help us target services like CodeBuild, EventBridge, Lambda, Amazon SNS and many more when our schedules are triggered.

EventBridge Scheduler can scale to millions of schedules which gives us more patterns we can explore in our event-driven applications. For example we can create schedules for many different use cases (e.g schedules per customer), and use groups to help us manage our schedules in our accounts.

I think Amazon EventBridge with Amazon EventBridge Scheduler is going to open the door for some new exciting patterns and I’m super excited to see what event-driven solutions people come up with!

If you are interested in using EventBridge Scheduler to trigger future events, you can read the code here and explore the pattern more on serverlessland.com.

•••

I would love to hear your thoughts on the pattern or if you have any other ideas for similar patterns, you can reach me on Twitter.

If you are interested in EventBridge patterns I have more coming out soon, and will be covering patterns like:

Extra resources

Until next time.

signature