System Overview
The Dash360 export system is a serverless, multi-tenant, high-performance architecture that replaces the monolithic ImportExportController with a scalable Lambda-based solution.87% Code Reduction
Consolidated base classes eliminate duplication across all export functions
Sub-second Performance
Fire-and-forget progress updates keep data processing at full speed
Multi-Tenant Ready
Domain-based tenant resolution with AWS Parameter Store
Infinitely Scalable
Data-driven Terraform modules — add a new export in 7 lines of HCL
Architecture Diagram
Router Pattern
Why Router Pattern?
Solution: A single Export Router Lambda receives ALL export messages and routes them to specific Lambda functions using asyncLambda.Invoke.
- 100% message reliability — no lost messages
- Scales to 45+ export types — just add an entry to the router mapping
- No LocalStack timing issues — single Event Source Mapping
- Easy to monitor — all routing logic in one place
Consolidated Lambda Functions
Before: Massive Code Duplication
After: Consolidated Pattern
Implementation Pattern
Each export Lambda implements only 5 abstract methods:What You Get for Free
All export Lambdas automatically inherit:- SQS event handling (
HandleSqsAsync) - Progress tracking (fire-and-forget DynamoDB updates)
- S3 file storage (tenant-organized folder structure)
- EventBridge notifications (real-time progress)
- Error handling (comprehensive exception management)
- Parameter Store integration (tenant connection strings)
Multi-Tenant Architecture
Domain-Based Tenant Resolution
The system automatically detects the tenant from the request domain:| Domain | Tenant |
|---|---|
localhost | testcustomer (development) |
lccf.dash360.com | lccf |
frib.dash360.com | frib |
dash360.skao.int | skao |
Parameter Store Hierarchy
Performance Optimizations
Fire-and-Forget Progress Updates
Frontend Progress Protection
Batch Processing
- Progress updates every 100 records (not every 5)
- 95% fewer DynamoDB calls for large datasets
- Parallel progress updates don’t block data processing
Adding a New Export Type
Total effort: ~2–3 hours for a complete new export type.
Infrastructure Patterns
Data-Driven Module Approach
Instead of 45+ individual Terraform module calls, a single module creates all export Lambdas:Architecture Decisions
Router Pattern vs SQS Filters
Router Pattern vs SQS Filters
Decision: Use Router Lambda instead of SQS message attribute filters.Reasoning: SQS filters with multiple Event Source Mappings caused message competition and delivery failures in testing. AWS documentation confirms this as an anti-pattern for this use case.
Consolidated Functions vs Separate Functions
Consolidated Functions vs Separate Functions
Decision: Use
BaseImportExportLambdaFunction with inheritance.Reasoning: 95% code duplication across export functions. A single base class reduces maintenance burden and ensures consistent error handling and progress tracking across all exports.Parameter Store vs Secrets Manager
Parameter Store vs Secrets Manager
Decision: Use Parameter Store for tenant connection strings.Reasoning: Connection strings are configuration, not secrets in the security sense. Parameter Store is cost-effective, supports hierarchical naming, and fits the multi-tenant pattern well.
Fire-and-Forget vs Awaited Progress
Fire-and-Forget vs Awaited Progress
Decision: Non-blocking progress updates with frontend protection against out-of-order delivery.Reasoning: Progress updates shouldn’t block data processing. 95% performance improvement for large datasets. Frontend already protects against the only side effect (bars going backwards).
Security
| Layer | Isolation Mechanism |
|---|---|
| Database | Separate connection string per tenant via Parameter Store |
| S3 Storage | Tenant-specific folder structure ({tenant}/exports/) |
| Notifications | Export events scoped to individual user IDs |
| IAM | Least-privilege Lambda execution roles |
Monitoring and Observability
| Signal | Purpose |
|---|---|
| CloudWatch Logs (Router) | Message routing decisions and failures |
| CloudWatch Logs (Export) | Processing details, record counts, timing |
DynamoDB (dash360-processing-jobs-{env}) | Export progress, completion status, download URLs |
EventBridge (dash360.lambda) | Real-time progress events to web app via SignalR |
Performance Benchmarks (LocalStack)
| Export Type | Records | Duration |
|---|---|---|
| Risk Categories | 50 | < 1 second |
| Risk Types | 45 | < 1 second |
| Premium Pay | 1,201 | ~2 seconds |
Troubleshooting
Export Stuck at 0%
Export Stuck at 0%
- Check Router Lambda logs — is the message being routed correctly?
- Check target Lambda logs — is the target Lambda starting?
- Check for build errors —
LambdaSerializerattribute issues prevent cold start
Progress Updates Out of Order
Progress Updates Out of Order
This is expected behavior with fire-and-forget updates. The frontend only moves progress bars forward. Final completion is always awaited, so the export still completes correctly.
Parameter Store Connection Failures
Parameter Store Connection Failures
- LocalStack: Verify SSM endpoint is configured in the Terraform provider
- Permissions: Confirm Lambda execution role has
GetParameterpermission - Fallback: System logs the error and continues with fallback connection strings
Terraform Module Errors
Terraform Module Errors
- Run
terraform initafter adding new modules - Verify relative module paths are correct
- Run
terraform validatebefore apply

