.NET and Amazon EventBridge

As briefly mentioned in an earlier post, Amazon EventBridge is a serverless event bus service designed to deliver data from applications and services to a variety of targets. It uses a different methodology than does SNS to distribute events.

The event producers submit their events to the service bus. From there, a set of rules determines what messages get sent to which recipients. This flow is shown in Figure 1.

Figure 1. Message flow through Amazon EventBridge.
Figure 1. Message flow through Amazon EventBridge.

The key difference between SNS and EventBridge is that in SNS you send your message to a topic, so the sender makes some decisions about where the message is going. These topics can be very broadly defined and domain-focused so that any application interested in order-related messages subscribes to the order topic, but this still obligates the sender to have some knowledge about the messing system.

In EventBridge you simply toss messages into the bus and the rules sort them to the appropriate destination. Thus, unlike SNS where the messages themselves don’t really matter as much as the topic; in EventBridge you can’t define rules without an understanding of the message on which you want to apply rules. With that in mind, we’ll go in a bit of a different order now and go into using EventBridge within a .NET application, that way we’ll have a definition of the message on which we want to apply rules.

.NET and Amazon EventBridge

The first step to interacting with EventBridge from within your .NET application is to install the appropriate NuGet package, AWSSDK.EventBridge. This will also install AWSSDK.Core. Once you have the NuGet package, you can access the appropriate APIs by adding several using statements:

using Amazon.EventBridge;
using Amazon.EventBridge.Model;

You will also need to ensure that you have added:

using System.Collections.Generic;
using System.Text.Json;

These namespaces provide access to the AmazonEventBridgeClient class that manages the interaction with the EventBridge service as well as the models that are represented in the client methods. As with SNS, you can manage all aspects of creating the various EventBridge parts such as service buses, rules, endpoints, etc. You can also use the client to push events to the bus, which is what we do now. Let’s first look at the complete code and then we will walk through the various sections.

static void Main(string[] args)
{
    var client = new AmazonEventBridgeClient();

    var order = new Order();

    var message = new PutEventsRequestEntry
    {
        Detail = JsonSerializer.Serialize(order),
        DetailType = "CreateOrder",
        EventBusName = "default",
        Source = "ProDotNetOnAWS"
    };

    var putRequest = new PutEventsRequest
    {
        Entries = new List<PutEventsRequestEntry> { message }
    };

    var response = client.PutEventsAsync(putRequest).Result;
    Console.WriteLine(
$"Request processed with ID of  
          #{response.ResponseMetadata.RequestId}");
    Console.ReadLine();
}

The first thing we are doing in the code is newing up our AmazonEventBridgeClient so that we can use the PutEventsAsync method, which is the method used to send the event to EventBridge. That method expects a PutEventsRequest object that has a field Entries that are a list of PutEventsRequestEntry objects. There should be a PutEventsRequestEntry object for every event that you want to be processed by EventBridge, so a single push to EventBridge can include multiple events.

Tip: One model of event-based architecture is to use multiple small messages that imply different items of interest. Processing an order, for example, may result in a message regarding the order itself as well as messages regarding each of the products included in the order so that the inventory count can be managed correctly. This means the Product domain doesn’t listen for order messages, they only pay attention to product messages. Each of these approaches has its own advantages and disadvantages.

The PutEventsRequestEntry contains the information to be sent. It has the following properties:

·         Detail – a valid JSON object that cannot be more than 100 levels deep.

·         DetailType – a string that provides information about the kind of detail contained within the event.

·         EventBusName – a string that determines the appropriate event bus to use. If absent, the event will be processed by the default bus.

·         Resources – a List<string> that contains ARNs which the event primarily concerns. May be empty.

·         Source – a string that defines the source of the event.

·         Time – a string that sets the time stamp of the event. If not provided EventBridge will use the time stamp of when the Put call was processed.

In our code, we only set the Detail, DetailType, EventBusName, and Source.

This code is set up in a console, so running the application gives results similar to that shown in Figure 2

Figure 2. Console application that sent a message through EventBridge
Figure 2. Console application that sent a message through EventBridge

We then used Progress Telerik Fiddler to view the request so we can see the message that was sent. The JSON from this message is shown below.

{
    "Entries":
    [
        {
            "Detail": "{\"Id\":0,
                        \"OrderDate\":
                                \"0001-01-01T00:00:00\",
                        \"CustomerId\":0,
                        \"OrderDetails\":[]}",
            "DetailType": "CreateOrder",
            "EventBusName": "default",
            "Source": "ProDotNetOnAWS"
        }
    ]
}

Now that we have the message that we want to process in EventBridge, the next step is to set up EventBridge. At a high level, configuring EventBridge in the AWS console is simple.

Configuring EventBridge in the Console

You can find Amazon EventBridge by searching in the console or by going into the Application Integration group. Your first step is to decide whether you wish to use your account’s default event bus or create a new one. Creating a custom event bus is simple as all you need to provide is a name, but we will use the default event bus.

Before going any further, you should translate the event that you sent to the event that EventBridge will be processing. You do this by going into Event buses and selecting the default event bus. This will bring you to the Event bus detail page. On the upper right, you will see a button Send events. Clicking this button will bring you to the Send events page where you can configure an event. Using the values from the JSON we looked at earlier, fill out the values as shown in Figure 3.

Figure 3. Getting the “translated” event for EventBridge
Figure 3. Getting the “translated” event for EventBridge

Once filled out, clicking the Review button brings up a window with a JSON object. Copy and paste this JSON as we will use it shortly. The JSON that we got is displayed below.

{
  "version": "0",
  "detail-type": "CreateOrder",
  "source": "ProDotNetOnAWS",
  "account": "992271736046",
  "time": "2022-08-21T19:48:09Z",
  "region": "us-west-2",
  "resources": [],
  "detail": "{\"Id\":0,\"OrderDate\":\"0001-01-01T00:00:00\",\"CustomerId\":0,\"OrderDetails\":[]}"
}

The next step is to create a rule that will evaluate the incoming messages and route them to the appropriate recipient. To do so, click on the Rules menu item and then the Create rule button. This will bring up Step 1 of the Create rule wizard. Here, you define the rule by giving it a name that must be unique by event bus, select the event bus on which the rule will run, and choose between Rule with an event pattern and Schedule. Selecting to create a schedule rule will create a rule that is run regularly on a specified schedule. We will choose to create a rule with an event pattern.

Step 2 of the wizard allows you to select the Event source. You have three options, AWS events or EventBridge partner events, Other, or All events. The first option references the ability to set rules that identify specific AWS or EventBridge partner services such as SalesForce, GitHub, or Stripe, while the last option allows you to set up destinations that will be forwarded every event that comes through the event bus. We typically see this when there is a requirement to log events in a database as they come in or some special business rule such as that. We will select Other so that we can handle custom events from our application(s).

You next can add in a sample event. You don’t have to take this action, but it is recommended to do this when writing and testing the event pattern or any filtering criteria. Since we have a sample message, we will select Enter my own and paste the sample event into the box as shown in Figure 4.

Figure 4. Adding a Sample Event when configuring EventBridge
Figure 4. Adding a Sample Event when configuring EventBridge

Be warned, however, if you just paste the event directly into the sample event it will not work as the matching algorithms will reject it as invalid without an id added into the JSON as highlighted by the golden arrow in Figure 4.

Once you have your sample event input, the next step is to create the Event pattern that will determine where this message should be sent. Since we are using a custom event, select the Custom patterns (JSON editor) option. This will bring a JSON editor window in which you enter your rule. There is a drop-down of helper functions that will help you put the proper syntax into the window but, of course, there is no option for simple matching – you have to know what that syntax is already. Fortunately, it is identical to the rule itself, so adding an event pattern that will select every event that has a detail-type of “Create Order” is:

{
  "detail-type": ["CreateOrder"]
}

Adding this into the JSON editor and selecting the Test pattern button will validate that the sample event matched the event pattern. Once you have successfully tested your pattern select the Next button to continue.

You should now be on the Step 3 Select Target(s) screen where you configure the targets that will receive the event. You have three different target types that you can select from, EventBridge event bus, EventBridge API Destination, or AWS Service. Clicking on each of the different target types will change the set of information that you will need to manage that detail the target. We will examine two of these in more detail, the EventBridge API destination, and the AWS Service, starting with the AWS service.

Selecting the AWS service radio button brings up a drop-down list of AWS services that can be targeted. Select the SNS target option. This will bring up a drop-down list of the available topics. Select the topic we worked with in the previous section and click the Next button. You will have the option configure Tags and then can Create rule.

Once we had this rule configured, we re-ran our code to send an event from the console. Within several seconds we received the email that was sent from our console application running on our local machine to EventBridge where the rule filtered the event to send it to SNS which then configured and sent the email containing the order information that we submitted from the console.

Now that we have verified the rule the fun way, let’s go back into it and make it more realistic. You can edit the targets for a rule by going into Rules from the Amazon EventBridge console and selecting the rule that you want to edit. This will bring up the details page. Click on the Targets tab and then click the Edit button. This will bring you back to the Step 3 Select Target(s) screen. From here you can choose to add an additional target (you can have up to 5 targets for each rule) or replace the target that pointed to the SNS service. We chose to replace our existing target.

Since we are looking at using EventBridge to communicate between various microservices in our application we will configure the target to go to a custom endpoint. To do so requires that we choose a Target type of EventBridge API destination. We will then choose to Create a new API destination which will provide all of the destination fields that we need to configure. These fields are listed below.

·         Name – the name of the API destination. Destinations can be reused in different rules, so make sure the name is clear.

·         Description – optional value describing the destination.

·         API destination endpoint – the URL to the endpoint which will receive the event.

·         HTTP Method – the HTTP method used to send the event, can be any of the HTTP methods.

·         Invocation rate limit per second – an optional value, defaulted to 300, of the number of invocations per second. Smaller values mean that events may not be delivered.

The next section to configure is the Connection. The connection contains information about authorization as every API request must have some kind of security method enabled. Connections can be reused as well, and there are three different Authorization types supported. These types are:

·         Basic (Username/Password) – where a username and password combination is entered into the connection definition.

·         OAuth Client Credentials – where you enter the OAuth configuration information such as Authorization endpoint, Client ID, and Client secret.

·         API Key – which adds up to 5 key\value pairs in the header.

Once you have configured your authorization protocol you can select the Next button to once again complete moving through the EventBridge rules creation UI.

There are two approaches that are commonly used when creating the rule to target API endpoint mapping. The first is a single endpoint per type of expected message. This means that, for example, if you were expecting “OrderCreated” and “OrderUpdated” messages then you would have created two separate endpoints, one to handle each message. The second approach is to create a generic endpoint for your service to which all inbound EventBridge messages are sent and then the code within the service evaluates each message and manages it from there.

Modern Event Infrastructure Creation

So far, we have managed all the event management through the console, creating topics and subscriptions in SNS and rules, connections, and targets in EventBridge. However, taking this approach in the real world will be extremely painful. Instead, modern applications are best served by modern methods of creating services; methods that can be run on their own without any human intervention. There are two approaches that we want to touch on now, Infrastructure-as-Code (IaC) and in-application code.

Infrastructure-as-Code

Using AWS CloudFormation or AWS Cloud Development Kit within the build and release process allows developers to manage the growth of their event infrastructure as their usage of events grows. Typically, you would see the work breakdown as being the teams building the systems sending events are responsible for creating the infrastructure required for sending, and the teams for the systems listening for events need to manage the creation of that infrastructure. Thus, if you are planning on using SNS then the sending system would have the responsibility for adding the applicable topic(s) while the receiving system would be responsible for adding the appropriate subscription(s) to the topics in which they are interested.

Using IaC to build out your event infrastructure allows you to scale your use of events easily and quickly. It also makes it easier to manage any changes that you may feel are necessary, as it is very common for the messaging approach to be adjusted several times as you determine the level of messaging that is appropriate for the interactions needed within your overall system.

In-Application Code

In-Application code is a completely different approach from IaC as the code to create the infrastructure resides within your application. This approach is commonly used in “configuration-oriented design”, where configuration is used to define the relationship(s) that each application plays. An example of a configuration that could be used when an organization is using SNS is below.

{
     “sendrules”:[{“name”:”Order”, “key”:”OrdersTopic”}],
     “receiverules”: [{“name”:” ProductUpdates”, 
                       “key”:” Products”,
                       “endpoint”:”$URL/events/product”}],
}

The code in the application would then ensure that every entry in the sendrules property has the appropriate topic created, so using the example above the name value represents the topic name and the key value represents the value that will be used within the application to map to the “Order” topic in SNS. The code in the application would then evaluate the receiverules value and create subscriptions for each entry.

This seems like a lot of extra work, but for environments that do not support IaC then this may be the easiest way to allow developers to manage the building of the event’s infrastructure. We have seen this approach built as a framework library included in every application that used events, and every application provided a configuration file that represented the messages they were sending and receiving. This framework library would evaluate the service(s) to see if there was anything that needed to be added and if so then add them.

Leave a Reply