Getting Started with Amazon EventBridge Scheduler for .NET Developers
In our previous article, Getting Started with Amazon EventBridge for .NET Developers, we delved into the capabilities of Amazon EventBridge for routing events through applications. But what if we need to trigger an event based on time? To address this, Amazon EventBridge provides schedule rules. However, the currently recommended approach is to use the Amazon EventBridge Scheduler.
Amazon EventBridge Scheduler is a serverless scheduler that allows you to create, run, and manage tasks from one central managed service.
Amazon EventBridge Scheduler supports several types of scheduling patterns:
Rate-based: This type allows tasks to run at regular intervals. The rate expression is
rate(value unit)
, whereunit
can be minutes, hours, or days.Cron-based: This type allows tasks to run based on a cron expression, allowing for more detailed schedules. The cron expression is
cron(minutes hours day-of-month month day-of-week year)
. Rate-based and cron-based schedules can be triggered in a specific timeframe using a start date and an end date.One-time: A one-time schedule triggers a target only once at the specific date and time we set. The one-time expression is
at(yyyy-mm-ddThh:mm:ss)
.
All schedule types on EventBridge Scheduler invoke their targets with 60 second precision. This means that if you set your schedule to run at
1:00
, it will invoke the target API between1:00:00
and1:00:59
, assuming that a flexible time window is not set.
In Amazon EventBridge Scheduler, we can use numerous targets that are divided into two groups:
Templated targets: These are a predefined set of API operations against a group of AWS services. To use them, we need to provide the
Arn
of the service, theRoleArn
that Amazon EventBridge Scheduler will assume to trigger the target, and optionally aninput
(the event payload).Universal targets: These types of targets allow you to invoke a wider set of API operations. The parameters are similar, but the content differs slightly:
Arn
: The complete service ARN, including the API operation we want to target.arn:aws:scheduler:::aws-sdk:service:apiAction
Input
: In this case, it is not just the event payload; it is the entire payload for the target API.
Other features worth mentioning are:
Flexible Time Window: this setting allows events to be triggered within a specified time range instead of at an exact, fixed time.
Error-handling: Amazon EventBridge Scheduler lets us set the number of retries for our schedule. If we reach the maximum number of retries, it supports Dead-Letter Queues.
Time Zones: Amazon EventBridge Scheduler offers timezone support to ensure events are triggered at the correct local times.
Let's put everything into practice by developing a one-time schedule and a rate-based schedule, both targeting a Lambda function.
Pre-requisites
Have an IAM User with programmatic access.
Install the Amazon Lambda Templates (
dotnet new -i Amazon.Lambda.Templates
)Install the Amazon Lambda Tools (
dotnet tool install -g Amazon.Lambda.Tools
)Install AWS SAM CLI.
The Backend Services
Run the following commands to set up our Lambda functions:
dotnet new lambda.EmptyFunction -n MyLambda -o .
dotnet add src/MyLambda package AWSSDK.Scheduler
dotnet add src/MyLambda package Amazon.Lambda.APIGatewayEvents
dotnet new sln -n EventBridgeScheduler
dotnet sln add --in-root src/MyLambda
Open the Program.cs
file and update the content as follows:
using Amazon.Scheduler;
using Amazon.Scheduler.Model;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using System.Text.Json;
using System.Globalization;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace MyLambda;
public class Function
{
private readonly AmazonSchedulerClient _schedulerClient;
private readonly string _targetArn;
private readonly string _roleArn;
public Function()
{
_schedulerClient = new AmazonSchedulerClient();
_targetArn = Environment.GetEnvironmentVariable("TARGET_ARN")!;
_roleArn = Environment.GetEnvironmentVariable("ROLE_ARN")!;
}
public record Payload(string Key);
public async Task<APIGatewayProxyResponse> Produce(APIGatewayProxyRequest input, ILambdaContext context)
{
var request = new CreateScheduleRequest
{
Name = $"myschedule-{input.RequestContext.RequestId}",
ScheduleExpression = $"at({DateTime.UtcNow.AddMinutes(5).ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture)})",
GroupName = "myapp",
State = ScheduleState.ENABLED,
Target = new Target { Arn = _targetArn, RoleArn = _roleArn, Input = JsonSerializer.Serialize(new Payload(Guid.NewGuid().ToString())) },
ActionAfterCompletion = ActionAfterCompletion.DELETE,
FlexibleTimeWindow = new FlexibleTimeWindow
{
Mode = FlexibleTimeWindowMode.OFF,
}
};
await _schedulerClient.CreateScheduleAsync(request);
return new APIGatewayProxyResponse
{
StatusCode = 200,
Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } }
};
}
public async Task Consume(Payload input, ILambdaContext context)
{
context.Logger.LogLine("Key: " + input.Key);
await Task.CompletedTask;
}
}
The Produce
method uses the AmazonSchedulerClient
class from the package AWSSDK.Scheduler
to create a one-time schedule. On the other hand, the Consume
method accepts an object of the Payload
class as a parameter, which is the same class used to serialize the input in the previous method.
AWS SAM template
At the solution level, create a template.yml
file with the following content:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
SAM
Resources:
MyEventBus:
Type: AWS::Scheduler::ScheduleGroup
Properties:
Name: "myapp"
ConsumerFunction:
Type: AWS::Serverless::Function
Properties:
Timeout: 60
MemorySize: 512
Tracing: Active
Runtime: dotnet8
Architectures:
- x86_64
Handler: MyLambda::MyLambda.Function::Consume
CodeUri: ./src/MyLambda/
ConsumerRole:
Type: AWS::IAM::Role
Properties:
RoleName: ConsumerRole
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: "scheduler.amazonaws.com"
Action: "sts:AssumeRole"
Policies:
- PolicyName: AllowInvokeLambda
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "lambda:InvokeFunction"
Resource:
- !GetAtt ConsumerFunction.Arn
ProducerFunction:
Type: AWS::Serverless::Function
Properties:
Timeout: 60
Environment:
Variables:
TARGET_ARN: !GetAtt ConsumerFunction.Arn
ROLE_ARN: !GetAtt ConsumerRole.Arn
MemorySize: 512
Tracing: Active
Runtime: dotnet8
Architectures:
- x86_64
Handler: MyLambda::MyLambda.Function::Produce
CodeUri: ./src/MyLambda/
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- scheduler:CreateSchedule
Resource: '*'
- Effect: Allow
Action:
- iam:PassRole
Resource: !GetAtt ConsumerRole.Arn
Events:
Post:
Type: Api
Properties:
Path: /events
Method: post
RateSchedule:
Type: AWS::Scheduler::Schedule
Properties:
GroupName: myapp
Name: "myrateschedule"
State: ENABLED
FlexibleTimeWindow:
Mode: 'OFF'
ScheduleExpression: "rate(5 minutes)"
Target:
Arn: !GetAtt ConsumerFunction.Arn
RoleArn: !GetAtt ConsumerRole.Arn
Input: '{"Key": "123"}'
RetryPolicy:
MaximumRetryAttempts: 10
DailyCronSchedule:
Type: AWS::Scheduler::Schedule
Properties:
GroupName: myapp
Name: "mydailycronschedule"
State: ENABLED
FlexibleTimeWindow:
Mode: 'FLEXIBLE'
MaximumWindowInMinutes: 10
ScheduleExpression: "cron(15 10 * * ? *)"
Target:
Arn: !GetAtt ConsumerFunction.Arn
RoleArn: !GetAtt ConsumerRole.Arn
Input: '{"Key": "abc"}'
Outputs:
MyApiEndpoint:
Description: "API endpoint"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/events"
The script above defines a new group with the AWS::Scheduler::ScheduleGroup
resource. To trigger the ConsumerFunction
from Amazon EventBridge Scheduler, we define a resource of type AWS::IAM::Role
with the appropriate permissions. The ProducerFunction
requires two permissions:
scheduler:CreateSchedule
to create the schedule.iam:PassRole
to pass the role to the service.
In addition to the resources needed to create the one-time schedule, we included the creation of the RateSchedule
and DailyCronSchedule
using the AWS::Scheduler::Schedule
resource. The most common properties under this resource are:
GroupName
: The name of the schedule group associated with this schedule.Name
: The name of the schedule.State
: Specifies whether the schedule is enabled or disabled.ScheduledExpression
: This expression defines when the schedule runs.FlexibleTimeWindow
: This allows you to set up a time window during which Amazon EventBridge Scheduler will trigger the schedule.Target
: The details of the schedule's target.
Run the following commands to deploy the resources to AWS:
sam build
sam deploy --guided
Optionally, we can define rate-based and cron-based schedules using the ScheduleV2
event source within the AWS::Serverless::Function
resource itself:
ConsumerFunction:
Type: AWS::Serverless::Function
Properties:
Timeout: 60
MemorySize: 512
Tracing: Active
Runtime: dotnet8
Architectures:
- x86_64
Handler: MyLambda::MyLambda.Function::Consume
CodeUri: ./src/MyLambda/
Events:
ScheduleEvent:
Type: ScheduleV2
Properties:
ScheduleExpression: "rate(10 minute)"
Name: "myrateschedulev2"
GroupName: myapp
State: ENABLED
Input: '{"Key": "xyz"}'
RetryPolicy:
MaximumRetryAttempts: 5
DeadLetterConfig:
Type: SQS
You can find all the code here. Thanks, and happy coding.
Subscribe to my newsletter
Read articles from Raul Naupari directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Raul Naupari
Raul Naupari
Somebody who likes to code