Amazon EventBridge + HTTP API + Custom Domain… complexity in simple words.

Alexander Smirnoff
limehome-engineering
7 min readMay 12, 2021
Photo by Modestas Urbonas on Unsplash

Here at limehome, we are using a domain-driven architecture. Each service or domain is either an EC2 instance or a Lambda function that acts as a subdomain. Services are developed using the Typescript, Python and C# programming languages.

Until a few months ago, all these services communicated with each other by only using the REST API. Which allowed them to receive updated data and updates data on a different domain. Thus, plenty of cronjobs were created to perform such an operation. However, the relevance of the data was being driven down.

Until… The event-driven architecture (EDA) era has arrived. ;)

Event Driven Architecture (EDA) has existed for a long time. Cloud, microservices, serverless programming paradigms and sophisticated development frameworks are increasing the applicability of EDA in solving mission-critical business problems in real-time. Technologies and Platforms such as Kafka, IBM Cloud Pak for Integration, and Lightbend, and development frameworks such as Spring Cloud Stream, Quarkus, and Camel all provide first-class support to EDA development.

Today, I’m going to talk about Amazon EventBridge and its integration with HttpApi to have the ability to publish events via HTTP requests.

Amazon EventBridge is a serverless event bus service that makes it easy to connect your applications with data from a variety of sources. EventBridge delivers a stream of real-time data from your own applications, i.e. Software-as-a-Service (SaaS) applications or AWS services. They then route these data to targets like AWS services (AWS Lambda or Kinesis) to an HTTP invocation endpoint using an API destination or to the event bus in another account.

HTTP API for Amazon API Gateway is a new flavour of API Gateway. It focuses on delivering enhanced features, improved performance, and an easier developer experience for customers building with API Gateway.

Before the start…

I‘m going to use the SAM framework. So, I’d assume that you are already familiar with the basics of working with it, as well as with the structure of the template.

If you are not familiar with SAM yet, I’d suggest you start from the AWS Serverless Application Model page. Also, there is a quite helpful playlist on YouTube called Sessions with SAM by Eric Johnson.

Ok, let’s initiate our project by creating a new folder and template.yml file for it.

$ mkdir http-to-eb-demo
$ cd http-to-eb-demo
$ touch template.yml

So, we are ready to start and now we need to specify the initial configuration of our template which is pretty much straight forward:

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: HTTP API to EventBridge with Custom Domain

The first line identifies the capabilities of the template and the second one is a macro hosted by CloudFormation which takes an entire template written in the AWS Serverless Application Model (AWS SAM) syntax and transforms and expands it into a compliant CloudFormation template.

In the Resource section we will define the following resources:

  1. EventBus
  2. HttpApi with Custom domain
  3. Log group for the EventBus

EventBus

Custom EventBus definition is pretty straight forward and includes only the name property:

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: HTTP API to EventBridge with Custom Domain
Resources:
CustomBus:
Type: AWS::Events::EventBus
Properties:
Name: DemoBus

HttpApi

Now, we need to create an Amazon API Gateway HTTP API. HTTP APIs are designed for low-latency, cost-effective integrations with AWS services, including AWS Lambda and HTTP endpoints. HTTP APIs support OIDC and OAuth 2.0 authorization and come with built-in support for CORS and automatic deployments.

So, in the Resource section I add the AWS::Serverless::HttpApiresource:

  HttpApi:
Type: AWS::Serverless::HttpApi
Properties:
DefinitionBody:
"Fn::Transform":
Name: "AWS::Include"
Parameters:
Location: "./api.yml"

As you can see as a definition body of our HttpApi I added the OpenAPI specification file which includes all necessary attributes and parameters:

There is an important thing in the definition file is x-amazon-apigateway-integration parameter. It specifies details of the backend integration used for this method. This extension is an extended property of the OpenAPI Operation object.

Let’s break down this object.

  1. integrationSubtype— Specifies the integration subtype for an AWS service integration. Supported only for HTTP APIs. For supported integration subtypes, see Integration subtype reference.
  2. credentials — For AWS IAM role-based credentials, specify the ARN of an appropriate IAM role. If unspecified, credentials default to resource-based permissions that must be added manually to allow the API to access the resource. For more information, see Granting Permissions Using a Resource Policy.
  3. requestParameters — For REST APIs, specifies mappings from method request parameters to integration request parameters. Supported request parameters are querystring, path, header, and body. To learn more, see Working with AWS service integrations for HTTP APIs.
  4. payloadFormatVersion — Specifies the format of the payload sent to an integration. Required for HTTP APIs. For HTTP APIs, supported values for Lambda proxy integrations are 1.0 and 2.0. For all other integrations, 1.0 is the only supported value. To learn more, see Working with AWS Lambda proxy integrations for HTTP APIs and Integration subtype reference.
  5. type — The type of integration with the specified backend. For more information about the integration types, see integration:type.
  6. connectionType — The integration connection type. The valid value is “VPC_LINK” for private integration or “INTERNET”, otherwise.
  7. timeoutInMillis — Integration timeouts between 50 ms and 29,000 ms.

In order to make our payload valid against the EventBridge schema we have to provide the mandatory attributes of the payload with the requestParameters parameter:

  1. EventBusName — The event bus name or ARN to associate with this rule. If you omit this, the default event bus is used.
  2. Detail — A JSON object, whose content is at the discretion of the service originating the event. The detail content in the example above is very simple, just two fields. AWS API call events have detail objects with around 50 fields nested several levels deep.
  3. DetailType — Identifies, in combination with the source field, the fields and values that appear in the detail field.
  4. Source — Identifies the service that sourced the event. All events sourced from within AWS begin with “aws.” Customer-generated events can have any value here, as long as it doesn’t begin with “aws.” We recommend the use of Java package-name style reverse domain-name strings.
    To find the correct value for source for an AWS service, see the table in AWS Service Namespaces. For example, the source value for Amazon CloudFront is aws.cloudfront.

The attribute x-amazon-apigateway-importexport-version specifies the version of the API Gateway import and export algorithm for HTTP APIs. Currently, the only supported value is 1.0.

HttpApi mapping

The next resource will be AWS::ApiGatewayV2::ApiMapping. An API mapping relates a path of your custom domain name to a stage of your API. A custom domain name can have multiple API mappings, but the paths can’t overlap.

  HttpApiMapping:
Type: AWS::ApiGatewayV2::ApiMapping
Properties:
ApiId: !Ref HttpApi
DomainName: !Ref DomainName
Stage: !Ref HttpApiApiGatewayDefaultStage
ApiMappingKey: !Sub "${AWS::StackName}"

In the Properties attribute we have the following parameters:

  1. ApiId — The identifier of the API. Here we reference the HttpApi resource.
  2. DomainName — The domain name. The reference to the specified domain name like a example.com or api.example.com. We going to add a new section to our template in a bit to specify the domain name.
  3. Stage — The API stage. Here we simply refer to the default ApiGateway stage.
  4. ApiMappingKey — The API mapping key. This key specifies the path parameter your HttpApi endpoint will be accessible. In our case, it will be like https://api.example.com/<<stackName>>.

Template parameters

To have the ability to specify a domain name for each isolated deployment without touching the code we will add the DomainName parameter to the template as a dynamic parameter:

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: HTTP API to EventBridge with Custom Domain
Parameters:
DomainName:
Type: String
Description: Domain name for api

Parameters enable you to input custom values to your template each time you create or update a stack. DomainName parameter lets you specify the domain name for the stack.

HttpApiRole

The next resource would be AWS::IAM::Role. With this resource, we will specify the role that will be assigned to the ApiGateway to give the ability to put events to the specific EventBus.

HttpApiRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "apigateway.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
- PolicyName: ApiDirectWriteEventBridge
PolicyDocument:
Version: "2012-10-17"
Statement:
Action:
- events:PutEvents
Effect: Allow
Resource: !GetAtt CustomBus.Arn

So, we are ready to deploy our stack.

$ sam deploy --guided

— guided option lets us specify the dynamic SAM’s template parameters before the stack begins to deploy.

$ sam deploy --guidedConfiguring SAM deploy
======================
Looking for samconfig.toml : Found
Reading default arguments : Success
Setting default arguments for 'sam deploy'
=========================================
Stack Name []: my-demo-stack
AWS Region [us-west-1]: eu-central-1
Parameter DomainName []: my.domain.com
Confirm changes before deploy [y/N]: N
Allow SAM CLI IAM role creation [Y/n]: Y
Save arguments to samconfig.toml [Y/n]: Y
...Deploying with following values
===============================
Stack name : my-demo-stack
Region : eu-central-1
Confirm changeset : False
Deployment s3 bucket : <<random_bucket_name>>
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {'DomainName': 'my.domain.com'}

After the stack gets deployed you can try to send events to the EventBus via HTTP request:

$ curl --location --request POST \
'https://my.domain.com/my-demo-stack/mysource/Detail%20Type%20of%20My%20Source' \
--header 'Content-Type: application/json' \
--data-raw '{ "attr": "my-value" }'

For the EventBus your event will look like

{
"version": "0",
"id": "b5cd1064-b0f7-393e-6b41-762290c7dabc",
"detail-type": "Detail Type of My Source",
"source": "com.mycompany.mysource",
"account": "3xxxxxxxxxx4",
"time": "2021-05-08T06:54:23Z",
"region": "eu-central-1",
"resources": [],
"detail": {
"attr": "my-value"
}
}

The response would be like this:

{
"Entries": [
{
"EventId": "b5cd1064-b0f7-393e-6b41-762290c7d330"
}
],
"FailedEntryCount": 0
}

This means you successfully sent your first event via HTTP API. Congratulations! 🎉

You might extend this endpoint by adding authorization or white/black list of IP addresses. That would be your home work ;)

You can find the full template.yml here:

Happy coding! ✋

--

--