AWS Compute Blog
Integrating Amazon EventBridge into your serverless applications
Event-driven architecture enables developers to create decoupled services across applications. When combined with the range of managed services available in AWS, this approach can make applications highly scalable and flexible, with minimal maintenance.
Many services in the AWS Cloud produce events, including integrated software as a service (SaaS) applications. Your custom applications can also produce and consume events. With so many events from different sources, you need a way to coordinate this traffic. Amazon EventBridge is a serverless event bus that helps manage how all these events are routed throughout your applications.
The routing logic is managed by rules that evaluate the events against event expressions. EventBridge delivers matching events to targets such as AWS Lambda, so you can process events with your custom business logic.
In this blog post, I show how you can build an event producer and consumer in AWS Lambda, and create a rule to route events. The code uses the AWS Serverless Application Model (SAM), so you can deploy the application in your own AWS Account. This walkthrough uses AWS resources that are covered by the AWS Free Tier.
To set up the example application, visit the GitHub repo and follow the instructions in the README.md file.
How the example application works
In this example, a banking application for automated teller machine (ATM) produces events about transactions. It sends the events to EventBridge, which then uses rules defined by the application to route accordingly. There are three downstream services consuming a subset of these events.
In the repo, the atmProducer subdirectory contains handler.js, which represents the ATM service producing events. This code is a Lambda handler written in Node.js, and publishes events to EventBridge via the AWS SDK using this line of code:
const result = await eventbridge.putEvents(params).promise()
This directory also contains events.js, listing several test transactions in an Entries array. A single event is defined as follows:
{
// Event envelope fields
Source: 'custom.myATMapp',
EventBusName: 'default',
DetailType: 'transaction',
Time: new Date(),
// Main event body
Detail: JSON.stringify({
action: 'withdrawal',
location: 'MA-BOS-01',
amount: 300,
result: 'approved',
transactionId: '123456',
cardPresent: true,
partnerBank: 'Example Bank',
remainingFunds: 722.34
})
}
The Detail
section of the event specifies transaction attributes. These include the location of the ATM, the amount, the partner bank, and the result of the transaction.
The handler.js file in the atmConsumer subdirectory contains three functions:
exports.case1Handler = async (event) => {
console.log('--- Approved transactions ---')
console.log(JSON.stringify(event, null, 2))
}
exports.case2Handler = async (event) => {
console.log('--- NY location transactions ---')
console.log(JSON.stringify(event, null, 2))
}
exports.case3Handler = async (event) => {
console.log('--- Unapproved transactions ---')
console.log(JSON.stringify(event, null, 2))
}
Each function receives transaction events, which are logged via the console.log
statements to Amazon CloudWatch Logs. The consumer functions operate independently of the producer and are unaware of the source of the events.
The routing logic is contained in the EventBridge rules that are deployed by the application’s SAM template. The rules evaluate the incoming stream of events, and route matching events to the target Lambda functions.
Running the ATM application
After deploying the sample application, you can generate test events by invoking the atmProducer Lambda function:
- Open the Lambda console in the same Region where you deployed the SAM application.
- There are four Lambda functions with the prefix atm-demo. Choose the atmProducerFn function, then choose Test.
- For Event name, enter Test, then choose Create. Choose Test once more to invoke the function.
This puts the sample events onto the EventBridge default event bus. Next, inspect the logs from the three consumer functions to see which events route to each function:
- Navigate to the CloudWatch console in the same Region. Select Logs, then Log groups from the menu.
- Select the log group containing atmConsumerCase1. You see two streams representing the two transactions approved by the ATM. Choose a log stream to view the output.
- Navigate back to the list of log groups, then select the log group containing atmConsumerCase2. You see streams for the two transactions matching the “New York” location filter.
- Navigate back once more to the list of log groups and select the log group containing atmConsumerCase3. Open the stream to see the denied transaction.
How EventBridge rules work
From the AWS Management Console, navigate to EventBridge from the Services dropdown. Choose Rules from the menu to see the rules created by the application deployment.
Choose one of the rules to see the configuration. Each rule is associated with a single event bus (the default bus for this application), which means it evaluates every event published to the bus. Scroll down to view the Event pattern used by the rule:
The event pattern is a JSON object with the same structure as the events they match. Each matching value must be wrapped in an array, and you can provide multiple values if necessary. If you use multiple values, this is compared using ‘or’ logic – only ones of the values needs to match the incoming event.
You can also use content-based filtering in event patterns to create more complex rules to match dynamically. The prefix operator above matches any event where the detail.location
value begins with “NY-“.
Integrating EventBridge into SAM templates
You can build and test rules manually in the EventBridge console, which can help in the development process as you refine event patterns. However, once you are ready to deploy your application, it’s easier to use a framework like SAM to launch all your serverless resources consistently.
In the example application, open the template.yaml file to view the SAM template, which defines the four Lambda functions. This shows two different ways to integrate the Lambda functions with EventBridge. The first approach uses the Events
property to configure the EventBridge rule:
atmConsumerCase3Fn:
Type: AWS::Serverless::Function
Properties:
CodeUri: atmConsumer/
Handler: handler.case3Handler
Runtime: nodejs12.x
Events:
Trigger:
Type: CloudWatchEvent
Properties:
Pattern:
source:
- custom.myATMapp
detail-type:
- transaction
detail:
result:
- "anything-but": "approved"
The syntax defines an event that invokes the Lambda function. In the YAML, you only need to define the pattern, and SAM automatically creates an IAM role with the required permissions. The pattern is the YAML equivalent of the Event Pattern shown in the console earlier.
This example automatically creates the rule on the default event bus, which exists in every AWS account. To associate the rule with a custom event bus, you can add the EventBusName to the template. If this property is missing, SAM uses the default bus.
In the second approach to defining an EventBridge configuration in SAM, you can separate the resources more clearly in the template. First, you define the Lambda function:
atmConsumerCase1Fn:
Type: AWS::Serverless::Function
Properties:
CodeUri: atmConsumer/
Handler: handler.case1Handler
Runtime: nodejs12.x
Next, the rule is defined using an AWS::Events::Rule
resource. The properties define the event pattern as before, but can also specify targets. Whereas the first method can only create a single, implied target (the parent Lambda function), you can explicitly define multiple targets using this syntax:
EventRuleCase1:
Type: AWS::Events::Rule
Properties:
Description: "Approved transactions"
EventPattern:
source:
- "custom.myATMapp"
detail-type:
- transaction
detail:
result:
- "approved"
State: "ENABLED"
Targets:
-
Arn:
Fn::GetAtt:
- "atmConsumerCase1Fn"
- "Arn"
Id: "atmConsumerTarget1"
Finally, there is an AWS::Lambda::Permission
resource that grants permission to EventBridge to invoke the target:
PermissionForEventsToInvokeLambda:
Type: AWS::Lambda::Permission
Properties:
FunctionName:
Ref: "atmConsumerCase1Fn"
Action: "lambda:InvokeFunction"
Principal: "events.amazonaws.com"
SourceArn:
Fn::GetAtt:
- "EventRuleCase1"
- "Arn"
For simple integrations where one Lambda function is invoked by one rule, the first approach is recommended. If you have complex routing logic, or you are connecting to resources outside of your SAM template, the second method is the better choice.
Conclusion
This walkthrough shows how to build a simple serverless application that produces and consumes events, using the EventBridge event bus to managing the routing. Using event patterns in rules, you can centralize the routing logic at EventBridge, helping reduce code in your downstream consuming services.
SAM templates make it simple to create EventBridge rules and define Lambda functions as targets. I show two ways to use SAM statements to define IAM permissions implicitly or explicitly. This allows you to decouple the services within your serverless applications, and take advantage of the routing offered by EventBridge with minimal configuration in your SAM templates.
To learn more, visit the Amazon EventBridge documentation.