Asynchronous communication with AWS CDK EventBridge

November 14, 2021

Hi CDK folks

For a startup I am building a security platform. The rough process for using the platform is something like this. The customer answers a few questions and then gets a security evaluation. For the prototype I decided to use AWS EventBridge. With this I achieve a decoupled and asynchronous communication between the services.

This was the first time I used EventBridge and I am very excited about it. Since there isn't too much AWS CDK EventBridge material on the web yet, I wanted to share my experience with you.

Below I describe the Security Platform in a little more detail and what my architecture looks like.

Security Platform

The user can answer some questions using desktop app or mobile app. The answers are stored in the user service and sent asynchronously to the security service. Once the processing is done in the security service, the user gets the result presented as a report.

Architecture

The prototype has three modules. These three modules are each implemented with their own CDK stack. These are the frontend, the user service and the security service.

Frontend

The frontend is a ReactTS app hosted in S3 via static web hosting. The app uses Amplify libraries for user management like sign up, recover password, login, logout. Also I use Amplify's GraphQl libraries as typed access to the AppSync in the user service. Also feel free to check out my other posts about Appsync.

After the user loges in he will answer som security questions. The answers then are send and stored to the user service via GraphQL queries. The frontend also has a list of security reports created by the security service.

User service

Via AppSync the answers are stored in a DynamoDB table. From there, a DynamoDB Stream Lambda listens for new or changed answer records. The Lambda then writes the answer record as a AnswerEvent to the EventBridge EventBus. The EventBus forwards the AnswerEvent to the Security.

After the security service has processed the AnswerEvent, it sends a ReportEvent containing a report back to the user service via the EventBus. A Report Lambda in the user service then receives the ReportEvent and writes the report to a Report DynamoDB table via GraphQL mutation.

The frontend has a GraphQl subscription for newly appearing reports and thus the list of reports is automatically updated if a new report arrives.

Security Service

The security service receives an AnswerEvent from the user service. A Report Lambda then receives the AnswerEvent unwrappes the answer and processes it into a report. The report is then packed into a ReportEvent and sent back to the user service.

At the moment it is sufficient to use a Lambda to create the report. However, this could change in the future as the report will become more complex and, for example, will also use third party APIs. Then a more complex workflow could be required. With the use of this event architecture this should not be a problem.

EventBridge

To send events between the user service and the security service, I use the default EventBus. The AnswerEvent and ReportEvent look like this:

import { Answer, Report } from '../frontend/src/lib/api';

export interface AnswerEvent {
  answer: Answer;
  state: State;
}

export interface ReportEvent {
  report: Report;
}

Interestingly here is that for the definition of the events I simply reuse the GraphQL wrapper classes generated by AppSync in the frontend under ../frontend/src/lib/api.

Now I need to define the lambdas which pack the respective event into the default EventBus. Here for example you see the lambda which packs the AnswerEvent into the EventBus.

import * as AWS from 'aws-sdk';
const EventBridge = new AWS.EventBridge();

export async function handler(event: lambda.DynamoDBStreamEvent) {

  let answer: Answer | null = null;

  // unmarshall DynamoDBStreamEvent
  ...

  const answerEvent: AnswerEvent = {
    answer,
    state: State.new,
  };

  const params: AWS.EventBridge.Types.PutEventsRequest = {
    Entries: [
      {
        Detail: JSON.stringify(answerEvent),
        DetailType: DetailType.AnswerEvent,
        EventBusName: 'default',
        Source: Source.userServiceDdbStream,
        Time: new Date,
      },
    ],
  };
  const result = await EventBridge.putEvents(params).promise();
}

In the EventStack, rules and targets have to be defined.

const answerRule = new events.Rule(this, 'answerRule', {
  eventPattern: {
    source: [Source.userServiceDdbStream],
  },
});
answerRule.addTarget(new targets.LambdaFunction(props.ratingLambda));

const reportRule = new events.Rule(this, 'reportRule', {
  eventPattern: {
    source: [Source.ratingServiceLambda],
  },
});
reportRule.addTarget(new targets.LambdaFunction(props.getReportEventLambda));

Summary

Having services communicate with each other asynchronously is awesome and exciting. With AWS EventBridge and AWS CDK, it's also a wonderful developer experience. I am very excited to see where the security platform is going :). Let me know your thoughts!

Thanks to the DeepL translater (free version) for helping with translating to english and saving me tons of time :).

To the wonderful readers of this article I'm saying that feedback of any kind is welcome. In the future I will try to include a discussion and comment feature here. In the meantime, please feel free to send me feedback via my social media accounts such as Twitter or FaceBook. Thank you very much :).

I love to work on Content Management Open Source projects. A lot from my stuff you can already use on https://github.com/mmuller88 . If you like my work there and my blog posts, please consider supporting me on Patreon:

Become a Patreon!

Share