System Overview
The User Service follows a serverless, event-driven architecture on AWS. All components are managed services, eliminating operational overhead while providing automatic scaling. View full diagramComponent Breakdown
Client Layer
The client (web or mobile application) initiates all interactions. Authentication happens directly with Cognito, and all subsequent API calls include the JWT token.Authentication Layer
AWS Cognito
Handles user registration, login, password reset, and MFA. Issues JWT tokens for authenticated sessions.
Cognito Authorizer
Attached to API Gateway. Validates JWT signature and expiry before any Lambda invocation.
API Layer
API Gateway serves as the single entry point:- Routes requests to appropriate Lambda handlers
- Enforces authentication via Cognito Authorizer
- Provides throttling and rate limiting
- Handles CORS and request validation
Compute Layer
Lambda functions organized by domain:| Handler Group | Responsibility |
|---|---|
| User Handlers | Profile CRUD, user status management |
| Email Handlers | Email CRUD, verification, primary designation |
| Cognito Triggers | Post-confirmation hook to create DynamoDB record |
Data Layer
DynamoDB with single-table design:- One table for all User Service entities
- Global Secondary Index for email lookups
- On-demand capacity for unpredictable workloads
Event Layer
EventBridge for inter-service communication:- Publishes domain events (user.created, email.verified, etc.)
- Rules route events to interested consumers
- Dead Letter Queue captures failed deliveries
Request Flow
1
Authentication
Client authenticates with Cognito, receives JWT access token
2
API Request
Client sends request to API Gateway with JWT in Authorization header
3
Token Validation
Cognito Authorizer validates JWT signature, expiry, and audience
4
Lambda Invocation
API Gateway invokes appropriate Lambda with user claims in context
5
Data Operation
Lambda performs DynamoDB operation, extracting userId from JWT claims
6
Event Publication
On state change, Lambda publishes event to EventBridge
7
Event Distribution
EventBridge routes event to subscribed downstream services
Why Serverless?
| Aspect | Benefit |
|---|---|
| Scaling | Automatic scaling to zero and to peak demand |
| Cost | Pay-per-invocation, no idle costs |
| Operations | No servers to patch, update, or maintain |
| Focus | Engineering time on business logic, not infrastructure |
Technology Stack
| Component | Technology | Rationale |
|---|---|---|
| Runtime | Node.js 20.x (LTS) | TypeScript support, fast cold starts, AWS SDK v3 native |
| Framework | Serverless Framework | Mature, well-documented, Cognito integration |
| Database | DynamoDB | Serverless, single-digit ms latency, scales infinitely |
| Auth | Cognito | Managed auth, JWT tokens, OAuth 2.0 flows |
| Events | EventBridge | Serverless event bus, schema registry, filtering |
| IaC | Terraform + Serverless | Split responsibilities (see below) |
Design Decisions
Key architectural choices and the reasoning behind them.Why Serverless Framework over SAM or CDK?
Why Serverless Framework over SAM or CDK?
Options considered: AWS SAM, AWS CDK, Serverless Framework, Terraform-onlyChosen: Serverless FrameworkRationale:
- More mature plugin ecosystem (offline, webpack, etc.)
- Simpler YAML syntax compared to CDK’s programmatic approach
- Better local development experience than SAM
- Team familiarity reduces onboarding time
Why EventBridge over SNS+SQS?
Why EventBridge over SNS+SQS?
Options considered: SNS+SQS fan-out, EventBridge, direct Lambda invocationChosen: EventBridgeRationale:
- Native content-based filtering (no need to filter in Lambda)
- Schema Registry for event documentation
- Archive and replay for debugging/recovery
- Single event bus vs. managing multiple SNS topics
Why split Terraform and Serverless Framework?
Why split Terraform and Serverless Framework?
Options considered: All Terraform, all Serverless, split approachChosen: Split - Terraform for shared resources, Serverless for Lambda lifecycleRationale:
- Cognito and DynamoDB are long-lived; Lambda functions change frequently
- Terraform state management better suited for infrastructure
- Serverless Framework optimized for Lambda deployment patterns
- Clear separation: Terraform outputs → SSM Parameters → Serverless reads
Why DynamoDB single-table over multi-table?
Why DynamoDB single-table over multi-table?
Options considered: Separate User and Email tables, single table with entitiesChosen: Single-table designRationale:
- Atomic transactions for primary email changes (TransactWriteItems requires same table)
- Single query to fetch user + all emails
- One table to monitor, backup, and scale
- Cost efficiency (one set of on-demand capacity)
Why Cognito sub as userId?
Why Cognito sub as userId?
Options considered: Cognito
sub directly, custom UUID with mapping tableChosen: Use Cognito sub directlyRationale:- No mapping table to maintain
- Guaranteed unique across pool
- Available in every JWT token
- Simpler implementation