Modern cloud applications rarely consist of a single service. A typical production system may contain APIs, background workers, databases, notification services, payment systems, analytics pipelines, and AI workloads.
When these services communicate directly with one another, the architecture can quickly become difficult to maintain.
For example:
Order API
|
+----> Email Service
|
+----> Inventory Service
|
+----> Payment Service
|
+----> Analytics Service
As more consumers are added, the number of connections increases.
An event-driven architecture provides another approach:
Order API
|
v
EventBridge Event Bus
|
+----> Inventory
+----> Email
+----> Analytics
+----> Notifications
Amazon EventBridge allows applications to publish events and route them to targets based on event rules. Custom event buses are especially useful when an application needs its own event-routing boundary instead of putting every event onto a shared default bus.
This article explains how custom EventBridge event buses work, how to design reliable event-driven applications with them, and how to integrate them with .NET applications.
What Is an Event Bus?
An event bus is a logical destination where applications publish events.
An event represents something that happened.
Examples include:
OrderCreated
PaymentCompleted
CustomerRegistered
InvoiceGenerated
DocumentProcessed
AgentCompleted
The producer does not need to know which services consume the event.
For example:
Order Service
|
| OrderCreated
v
Event Bus
|
+----> Inventory
+----> Email
+----> Analytics
This creates loose coupling between services.
The order service only needs to publish OrderCreated.
It does not need direct dependencies on inventory, email, or analytics services.
Default Event Bus vs Custom Event Bus
EventBridge provides a default event bus, but applications can also create custom event buses.
A custom event bus provides a dedicated routing boundary for a particular application, domain, team, or integration scenario.
For example:
Default Event Bus
|
+---- Application A
+---- Application B
+---- Application C
A custom design could instead use:
Order Bus
|
+---- Inventory
+---- Shipping
+---- Billing
Customer Bus
|
+---- CRM
+---- Notifications
+---- Analytics
The appropriate structure depends on organizational and application boundaries.
Why Use Custom Event Buses?
Custom event buses can provide several architectural benefits.
Domain Separation
Different business domains can have separate event-routing boundaries.
Commerce Bus
Customer Bus
Billing Bus
AI Bus
This can make ownership and event management easier to understand.
Access Control
Permissions can be scoped around event buses.
A producer may have permission to publish to one bus without automatically gaining access to unrelated event infrastructure.
Cleaner Routing Rules
A custom bus can contain rules relevant to a particular workload.
For example:
Order Bus
OrderCreated
|
+----> Inventory Rule
+----> Notification Rule
+----> Analytics Rule
This reduces unrelated rules being mixed together.
EventBridge Architecture
A simplified EventBridge architecture looks like this:
Producer
|
v
Custom Event Bus
|
+----------+----------+
| | |
v v v
Rule A Rule B Rule C
| | |
v v v
Target A Target B Target C
The important components are:
Event producer — publishes an event.
Event bus — receives events.
Event rule — determines which events should be processed.
Target — receives matching events.
Targets can include AWS services and supported endpoints.
Event Structure
A useful event should contain enough information for consumers to understand what happened.
A conceptual event might look like:
{
"source": "orders",
"detail-type": "OrderCreated",
"detail": {
"orderId": "ORD-1042",
"customerId": "CUS-204",
"total": 149.99
}
}
The event should describe the business fact rather than dictate what consumers must do.
For example:
Good:
OrderCreated
Less useful:
SendEmailToCustomer
OrderCreated describes something that happened.
SendEmailToCustomer describes an implementation action.
Keeping events focused on business facts allows more consumers to use them later.
Creating a Custom Event Bus
The infrastructure can be created through AWS tooling or infrastructure-as-code.
Conceptually:
Custom Event Bus
Name:
orders-production
The application then publishes events to that bus.
A production environment should generally manage infrastructure declaratively rather than creating resources manually for every deployment.
Infrastructure-as-code tools such as AWS CloudFormation, AWS CDK, or Terraform can help maintain consistent environments.
Publishing Events From .NET
A .NET application can use the AWS SDK to publish EventBridge events.
A simplified example:
using Amazon.EventBridge;
using Amazon.EventBridge.Model;
using System.Text.Json;
public class OrderEventPublisher
{
private readonly IAmazonEventBridge eventBridge;
public OrderEventPublisher(
IAmazonEventBridge eventBridge)
{
this.eventBridge = eventBridge;
}
public async Task PublishOrderCreatedAsync(
string orderId,
string customerId,
decimal total,
CancellationToken cancellationToken)
{
var detail = new
{
orderId,
customerId,
total
};
var request = new PutEventsRequest
{
Entries =
[
new PutEventsRequestEntry
{
EventBusName = "orders-production",
Source = "orders",
DetailType = "OrderCreated",
Detail = JsonSerializer.Serialize(detail)
}
]
};
await eventBridge.PutEventsAsync(
request,
cancellationToken);
}
}
The producer only needs to know the event contract and destination bus.
It does not need to know which services will consume the event.
Registering the AWS Client in ASP.NET Core
The AWS SDK client can be registered with dependency injection.
builder.Services.AddAWSService<IAmazonEventBridge>();
Then inject the client into your event publishing service.
This keeps AWS infrastructure concerns outside controllers and business logic.
Event Rules
Publishing an event is only the first step.
Rules determine what should happen with matching events.
Suppose the bus receives:
OrderCreated
OrderCancelled
PaymentCompleted
ShipmentCreated
A rule can match only:
OrderCreated
and route it to a target.
Conceptually:
Event Bus
|
+---- OrderCreated ------> Inventory Lambda
|
+---- PaymentCompleted --> Receipt Service
|
+---- ShipmentCreated --> Notification Service
This allows multiple consumers to react independently.
Event Pattern Matching
Rules can filter events based on event attributes.
For example, a rule might match:
{
"source": ["orders"],
"detail-type": ["OrderCreated"]
}
Another rule could filter based on the contents of the detail.
For example:
{
"source": ["orders"],
"detail-type": ["OrderCreated"],
"detail": {
"total": [
{
"numeric": [">", 1000]
}
]
}
}
This allows different workflows to react to different types of events.
One Event, Multiple Consumers
One of the strongest characteristics of event-driven architecture is fan-out.
Consider:
OrderCreated
|
v
EventBridge
/ | \
/ | \
v v v
Inventory Email Analytics
The producer publishes the event once.
Multiple consumers can independently process it.
This avoids direct dependencies such as:
Order Service
|
+----> Inventory
|
+----> Email
|
+----> Analytics
The event bus becomes the integration boundary.
Reliability Considerations
Event-driven systems are asynchronous, which changes how reliability should be designed.
An HTTP request might return an immediate success or failure.
An event publication can succeed while downstream processing happens later.
Therefore, distinguish between:
Event accepted
|
v
Event delivered
|
v
Target processed
|
v
Business operation completed
These are different states.
A producer should not assume that publishing an event means every downstream operation has already completed.
Handling Failed Consumers
Suppose an event reaches a target but processing fails.
OrderCreated
|
v
Consumer
|
X
Failure
A reliable architecture needs a strategy for failures.
Depending on the target and architecture, this can include retries, dead-letter queues, error handling, and operational alerts.

Join the conversation! Your thoughts help the community grow.