Getting Started with Amazon EventBridge for .NET Developers

Getting Started with Amazon EventBridge for .NET Developers

To develop event-driven applications in AWS, we often use services like SQS to send commands and SNS to publish events. As an alternative to SNS, AWS also offers Amazon EventBridge.

EventBridge is a serverless service that uses events to connect application components together, making it easier for you to build scalable event-driven applications.

Amazon EventBridge is designed with features that extend beyond SNS, such as advanced event filtering, transformation, and routing. It handles structured events that can follow a specific schema and provides more integration options than SNS. These new features come at a slightly higher cost than SNS, and currently, it does not include a feature to ensure event ordering, which SNS already supports.

The key components to understand about Amazon EventBridge are:

  • Events: An event is a simple JSON object.

  • Sources: An event comes from AWS services, custom applications, and partners (SaaS applications).

  • Event Bus: An event bus acts as a router that receives events and delivers them to zero or more targets.

  • Rules: An event bus can have multiple rules. A rule filters and routes events to multiple targets when they match a pattern.

  • Targets: A target is the destination where events are sent.

Let's see how this works in practice by creating two Lambda functions, one to produce events and the other to consume them.

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.EventBridge
dotnet add src/MyLambda package Amazon.Lambda.APIGatewayEvents
dotnet add src/MyLambda package Amazon.Lambda.CloudWatchEvents
dotnet new sln -n EventBridge
dotnet sln add --in-root src/MyLambda

Open the Program.cs file and update the content as follows:

using Amazon.EventBridge;
using Amazon.EventBridge.Model;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.CloudWatchEvents;
using Amazon.Lambda.Core;
using System.Text.Json;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace MyLambda;

public class Function
{
    private readonly AmazonEventBridgeClient _eventBridgeClient;

    public Function()
    {
        _eventBridgeClient = new AmazonEventBridgeClient();
    }

    public record Payload(string Key);

    public async Task<APIGatewayProxyResponse> Produce(APIGatewayProxyRequest input, ILambdaContext context)
    {
        var putEventsRequest = new PutEventsRequest
        {
            Entries = new List<PutEventsRequestEntry> 
            {
                new PutEventsRequestEntry
                {
                    Source = "myapplication",
                    DetailType = "mycustomevent",
                    Detail = JsonSerializer.Serialize(new Payload(Guid.NewGuid().ToString())),
                    EventBusName = "MyEventBus"
                }
            }
        };

        var response = await _eventBridgeClient.PutEventsAsync(putEventsRequest);
        if (response.FailedEntryCount > 0)
        {
            return new APIGatewayProxyResponse
            {
                StatusCode = 500,
                Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } }
            };
        }
        else
        {
            return new APIGatewayProxyResponse
            {
                StatusCode = 200,
                Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } }
            };
        }
    }

    public async Task Consume(CloudWatchEvent<Payload> input, ILambdaContext context)
    {
        context.Logger.LogLine("Event Source: " + input.Source);
        context.Logger.LogLine("Event Detail Type: " + input.DetailType);
        context.Logger.LogLine("Event Detail: " + input.Detail.Key);
        await Task.CompletedTask;
    }
}

The Produce method uses the AmazonEventBridgeClient class from the package AWSSDK.EventBridge to publish events. Each PutEventsRequestEntry defines:

  • Source: An identifier for the event's origin.

  • DetailType: The type of event (typically used for filtering).

  • Detail: JSON-serialized data for the event.

  • EventBusName: The name of the event bus. If left empty, the default EventBridge will be used.

The Detail, DetailType, and Source are required to successfully publish an event. If we include event entries in a request that does not include each of those properties, EventBridge will fail that entry. If we submit a request in which none of the entries have each of these properties, EventBridge fails the entire request.

The Consume method is much simpler. It uses the CloudWatchEvent<T> class from the Amazon.Lambda.CloudWatchEvents package to receive events from EventBridge.

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::Events::EventBus
    Properties: 
      Name: "MyEventBus"    

  ProducerFunction:
    Type: AWS::Serverless::Function
    Properties:
      Timeout: 60
      MemorySize: 512
      Tracing: Active
      Runtime: dotnet8
      Architectures:
        - x86_64    
      Handler: MyLambda::MyLambda.Function::Produce
      CodeUri: ./src/MyLambda/
      Policies:
        - EventBridgePutEventsPolicy:
            EventBusName: !Ref MyEventBus
      Events:
        Post:
          Type: Api
          Properties:
            Path: /events
            Method: post

  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: 
        Trigger:
          Type: EventBridgeRule
          Properties:
            EventBusName: !Ref MyEventBus
            Pattern:
              source:
                - "myapplication"
              detail-type:
                - "mycustomevent"

Outputs:
  MyApiEndpoint:
    Description: "API endpoint"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/events"

The script above defines a custom event bus using the AWS::Events::EventBus resource. Then, we defined two functions with the AWS::Serverless::Function resource:

  • ProducerFunction: The producer function uses the EventBridgePutEventsPolicy policy template, allowing it to publish events to the custom event bus we created earlier.

  • ConsumerFunction: The consumer function uses the EventBridgeRule event source type, which sets the function as the target of an Amazon EventBridge rule. The Pattern property is used to match the rule with incoming events.

Instead of the EventBridgeRule event source, we can use the AWS::Events::Rule and AWS::Lambda::Permission resources to achieve the same result.

  EventBridgeRule:
    Type: AWS::Events::Rule
    Properties:
      Name: MyRule
      EventPattern:
        source:
          - "myapplication"
        detail-type:
          - "mycustomevent"
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt ConsumerFunction.Arn
          Id: "MyTarget"

  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref ConsumerFunction
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt MyEventBus.Arn

Run the following commands to deploy the resources to AWS:

sam build
sam deploy --guided

Once deployed, our producer function is ready to be invoked. You can find all the code here. Thanks, and happy coding.