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