A production-ready, event-driven microservices architecture using Amazon EventBridge, demonstrating loose coupling, scalability, and fault isolation.
Client
β
API Gateway
β
OrderService (Lambda) - Producer
β emits event β
EventBridge (Default Bus)
βββ PaymentService (Lambda) - Consumer
βββ InventoryService (Lambda) - Consumer
βββ NotificationService (Lambda) - Consumer
- Loose Coupling: Services only communicate via events, never direct calls
- Scalability: Each service scales independently
- Fault Isolation: One service failure doesn't break others
- Extensibility: Add new services without touching existing code
- Schema Evolution: Versioned event contracts for backward compatibility
- AWS Lambda: Serverless compute
- Amazon EventBridge: Event routing and filtering
- API Gateway: HTTP entry point
- DynamoDB: NoSQL database (on-demand billing)
- CloudWatch: Logging and monitoring
- IAM: Security and permissions
- Trigger: HTTP POST via API Gateway
- Responsibility:
- Accept order requests
- Store order in DynamoDB
- Emit
OrderCreatedevent to EventBridge
- DynamoDB Table:
OrdersTable- PK:
orderId - Attributes:
userId,productId,quantity,status,createdAt,updatedAt
- PK:
- Trigger: EventBridge rule listening to
OrderCreatedevents - Responsibility:
- Simulate payment processing (90% success rate)
- Update order status to
PAIDorPAYMENT_FAILED - Emit
PaymentFailedevent on failure
- Trigger: EventBridge rule listening to
OrderCreatedevents - Responsibility:
- Reserve inventory for the order
- Validate stock availability
- Emit
InventoryFailedevent if insufficient stock
- DynamoDB Table:
InventoryTable- PK:
productId - Attributes:
availableQty,updatedAt
- PK:
- Trigger: Multiple EventBridge rules for:
OrderCreatedPaymentFailedInventoryFailed
- Responsibility:
- Send notifications (currently logs to CloudWatch)
- Handles all event types gracefully
OrderCreated Event
βββ PaymentService (processes payment)
βββ InventoryService (reserves stock)
βββ NotificationService (sends notification)
PaymentFailed Event
βββ NotificationService (notifies user)
InventoryFailed Event
βββ NotificationService (notifies user)
{
"source": "eventflow.orders",
"detail-type": "OrderCreated",
"detail": {
"orderId": "ord-123",
"userId": "user-42",
"productId": "prod-9",
"quantity": 2,
"timestamp": "2026-01-13T10:00:00Z",
"version": "1.0"
}
}{
"source": "eventflow.payment",
"detail-type": "PaymentFailed",
"detail": {
"orderId": "ord-123",
"userId": "user-42",
"productId": "prod-9",
"quantity": 2,
"reason": "Payment processing failed",
"timestamp": "2026-01-13T10:00:00Z",
"version": "1.0"
}
}{
"source": "eventflow.inventory",
"detail-type": "InventoryFailed",
"detail": {
"orderId": "ord-123",
"userId": "user-42",
"productId": "prod-9",
"quantity": 2,
"availableQty": 1,
"reason": "Insufficient inventory",
"timestamp": "2026-01-13T10:00:00Z",
"version": "1.0"
}
}- Node.js 20.x or higher
- AWS CLI configured with appropriate credentials
- Serverless Framework CLI installed globally
-
Install dependencies:
npm install
-
Deploy to AWS:
npm run deploy
Or deploy to a specific stage:
npm run deploy:dev
-
Get the API Gateway URL: After deployment, the API Gateway URL will be displayed in the output. It will look like:
https://xxxxx.execute-api.us-east-1.amazonaws.com/dev
-
Create an order:
curl -X POST https://YOUR_API_GATEWAY_URL/dev/orders \ -H "Content-Type: application/json" \ -d '{ "userId": "user-123", "productId": "prod-456", "quantity": 2 }'
-
Check CloudWatch Logs:
# Order Service logs npm run logs -- -f createOrder --tail # Payment Service logs npm run logs -- -f processPayment --tail # Inventory Service logs npm run logs -- -f reserveInventory --tail # Notification Service logs npm run logs -- -f sendNotification --tail
-
Query DynamoDB:
# Get order aws dynamodb get-item \ --table-name eventflow-order-system-orders-dev \ --key '{"orderId": {"S": "ord-xxxxx"}}' # Get inventory aws dynamodb get-item \ --table-name eventflow-order-system-inventory-dev \ --key '{"productId": {"S": "prod-456"}}'
The system is designed to handle failures gracefully:
| Scenario | What Happens |
|---|---|
| Payment fails | Inventory service still runs independently |
| Inventory fails | Payment service still processes (if it succeeded) |
| Notification fails | Business logic continues unaffected |
| One Lambda throttles | Other Lambdas continue processing |
Each Lambda has minimal, least-privilege permissions:
- Order Service: Can write to OrdersTable and emit events
- Payment Service: Can update OrdersTable and emit events
- Inventory Service: Can read/write InventoryTable, update OrdersTable, and emit events
- Notification Service: Read-only (just logs events)
- CloudWatch Logs: All Lambda functions log to CloudWatch
- EventBridge Metrics: Available in CloudWatch Metrics
- DynamoDB Metrics: Table metrics in CloudWatch
After working with this project, you'll understand:
- β Why event-driven systems scale better
- β EventBridge vs SNS vs SQS use cases
- β How to design loosely coupled microservices
- β How failures propagate (or don't) in event-driven systems
- β How to add new services without refactoring existing code
- β Schema evolution and versioning strategies
To add a new service (e.g., FraudDetectionService):
- Create a new Lambda function in
src/fraud-detection-service/ - Add it to
serverless.ymlwith an EventBridge trigger - Deploy - no changes needed to existing services!
Example:
detectFraud:
handler: src/fraud-detection-service/handler.detectFraud
events:
- eventBridge:
pattern:
source:
- eventflow.orders
detail-type:
- OrderCreatedTo remove all AWS resources:
npm run removeOr for a specific stage:
serverless remove --stage dev- All services use the EventBridge default bus (free tier)
- DynamoDB uses on-demand billing (free tier eligible)
- Lambda functions use 256MB memory and 30s timeout
- Event versioning (
version: "1.0") allows for schema evolution
- Events not triggering: Check EventBridge rules in AWS Console!
- Permission errors: Verify IAM roles have correct permissions
- DynamoDB errors: Ensure tables are created (check CloudFormation stack)
- API Gateway CORS: CORS is enabled by default in the configuration