Solution Architecture · System Design · Pickles Asia - 2025

Auction Platform Architecture
Monolith → Event-Driven Microservices

Pickles Asia's core auction platform was a NestJS monolith handling everything - live bidding, user management, vehicle catalogue, media processing, payments, notifications, and analytics - all in a single deployable. Any bug in a background batch job could take down live auctions. Any deployment meant full system risk. I designed the architecture for decomposing this into 8 independently deployable services connected by an event backbone. The engagement covered domain analysis, bounded context mapping, event schema contracts, data ownership model, API Gateway strategy, and a phased migration roadmap with defined risk gates at each phase. Delivered as 12 Architecture Decision Records and a full system design document.

monolith → microservicesDesigned
8
Services
0
Shared DB
3
Phases
12
ADRs
3-phase strangler fig ↑
The Challenge

One codebase. One deploy. Everything fails together.

Pickles Asia's auction platform had been built as a single NestJS monolith since its early days. Over 4 years it absorbed every new domain: vehicle listings, live auction streaming, real-time bidding, payment integrations, user authentication, media upload and processing, email/SMS notifications, and management reporting. By 2025, the codebase was over 180,000 lines of TypeScript with no module ownership boundaries.

The most dangerous problem was blast radius. The live auction path - the revenue-critical core of the business - shared a process and database with background batch jobs, report generation, and media processing. A slow query from a nightly report could spike response times for active bidders. A crash in any module brought everything down. Deployments were high-risk events requiring coordination across all teams simultaneously. Scaling any one part meant scaling the entire application.

  • Live auction, bidding, payments, notifications, and media all in one NestJS process - full system down if any module crashes
  • Shared MySQL database across all domains - one slow report query degraded live auction response times
  • All teams deploy together - one team's unreviewed change blocked every other team's release
  • No horizontal scaling for individual services - scaling live bidding meant scaling the reporting module too
  • No clear module ownership - refactoring any domain risked breaking unrelated functionality
  • No event history - inter-module calls were synchronous function calls, making audit trails and replay impossible

monolith.coupling_analysisHigh Risk
1
Shared database
1
Deployable unit
180k
Lines of TS
DomainIsolationDeploy risk
Live Auction EngineNoneShared
Real-time BiddingNoneShared
Payment IntegrationNoneShared
Media ProcessingNoneShared
NotificationsNoneShared
ReportingNoneShared
coupling matrix · before decomposition
The Architecture Design

Domain decomposition → Event contracts → Migration roadmap

1
Domain analysis & bounded context workshops - ran 3 structured workshops with engineering and product teams using DDD techniques: event storming to surface domain events, context mapping to identify integration patterns, and team topology mapping. Output: 8 bounded contexts: Auction Engine, Bidding Service, User & Identity, Vehicle Catalogue, Payment Gateway Adapter, Media Processing, Notification Service, Reporting & Analytics.
2
Event backbone design - chose Amazon EventBridge as the central event bus. Defined core domain event contracts: vehicle.listed, auction.started, bid.placed, bid.outbid, auction.closed, payment.initiated, payment.completed. Each event contract versioned with a schema_version field with backward-compatible evolution rules. SQS dead-letter queues assigned per consumer.
3
Data ownership model - database per service - the monolith's shared MySQL made cross-domain joins trivially easy but created tight coupling at the data layer. Designed a database-per-service model: Auction Engine on RDS MySQL; Bidding Service on ElastiCache Redis for sub-millisecond in-flight state plus RDS for audit log; Vehicle Catalogue on RDS MySQL; Notifications on DynamoDB; Reporting on a separate RDS read replica.
4
API Gateway & service mesh strategy - designed a single public API Gateway as the client-facing entry point. Internal service-to-service communication: synchronous HTTP only for request/response patterns requiring strong consistency; all other inter-service communication via EventBridge. JWT-based authentication at the API Gateway layer with service-level IAM roles for internal event bus access.
5
3-phase migration roadmap with defined risk gates - migration sequenced by blast radius risk. Phase 1: Extract Notification Service and Media Processing. Phase 2: Extract Reporting & Analytics and Vehicle Catalogue. Phase 3: Split Auction Engine and Bidding Service. Each phase gate includes: integration test coverage of event contracts, shadow deployment, data migration validation, and rollback procedure.
6
12 Architecture Decision Records (ADRs) - every major architectural choice documented with context, decision, consequences, and alternatives considered. Key ADRs: EventBridge vs SNS/SQS fan-out; database-per-service vs shared schema; synchronous vs async inter-service communication; event schema versioning strategy; API Gateway product selection; distributed tracing strategy.
Target Architecture (After Decomposition)
Clients
Web App
Mobile App
Admin Portal
↓ HTTPS
API Layer
API Gateway · JWT Auth · Rate Limiting
↓ Route per service
Core Services (Phase 3)
Auction Engine
Bidding Service
↓ Domain Events
Event Bus
Amazon EventBridge · Schema Registry · DLQ per consumer
↓ Subscribe per domain
Supporting Services
Notifications
Media
Payments
Reporting
↓ Owned data stores
Data Layer
RDS
Redis
DynamoDB
S3
Key Architectural Decisions

Every decision documented with context, trade-offs, and rejected alternatives

ADR-01
EventBridge over SNS/SQS fan-out

Decision: EventBridge as the event bus. Why: Built-in schema registry enforces contract validation at publish time. Content-based filtering lets consumers subscribe to event subsets without custom routing logic. Native CloudWatch integration provides event bus observability with zero additional instrumentation. Trade-off accepted: Higher cost per event vs raw SQS; acceptable at Pickles Asia's event volume (<500k events/day).

ADR-04
Database per service - no cross-service joins

Decision: Each service owns its data store; no other service may query it directly. Why: Shared database is coupling at the deepest layer - schema changes break multiple services simultaneously. Database-per-service enables independent scaling, technology choice, and deployment. Trade-off accepted: Cross-domain queries require read model projections maintained via event consumers. Eventual consistency accepted for non-financial data.

ADR-07
Strangler Fig pattern for incremental migration

Decision: Strangler Fig over Big Bang rewrite. Why: Big Bang migration of a live production system with real-time auction bidding is too high-risk to execute without prolonged parallel running. Strangler Fig allows the monolith to continue operating at full capacity while new services gradually absorb domains. Feature flags gate traffic between old and new implementations at each phase boundary. Trade-off accepted: Temporary complexity from dual code paths during migration phases.

ADR-11
Bidding Service: Redis for in-flight state, RDS for audit

Decision: ElastiCache Redis as the primary store for active auction bid state; all bids written to RDS asynchronously for the audit log. Why: Live auction bidding requires sub-10ms bid acceptance latency. RDS at peak load (>200 concurrent bidders) would not meet this SLA. Redis Sorted Sets provide O(log N) bid ordering. Trade-off accepted: Redis is non-durable by default - mitigated with AOF persistence and replica configuration. RDS audit log provides full recovery source.

Migration Roadmap

3 phases. Sequenced by blast radius risk. Each phase gated independently.

P1
Phase 1 - Low-risk extraction: Notification Service + Media Processing
Neither service is on the synchronous hot path of live auction bidding. Notifications are fire-and-forget; media processing is background. Extracting these first validates the EventBridge pipeline, the deployment pattern, the per-service CI/CD approach, and the runbook structure before touching core auction logic. Risk gate: notification delivery success rate ≥99.9%, media processing latency within 10% of baseline.
P2
Phase 2 - Read domain extraction: Reporting & Analytics + Vehicle Catalogue
Both services are predominantly read-heavy with no real-time write path dependency from the live auction. Reporting is extracted to its own RDS read model populated by EventBridge consumers - no more direct joins on the monolith's primary database. Vehicle Catalogue is extracted with a dual-write pattern during cutover. Risk gate: report data consistency audit (±0 discrepancy vs monolith output), catalogue API response parity test suite passing at 100%.
P3
Phase 3 - Core extraction: Auction Engine + Bidding Service (highest risk)
The most complex phase. Auction Engine and Bidding Service share deeply intertwined state transitions. Requires Redis cluster deployment, RDS schema migration, feature flag infrastructure, and a prolonged parallel-run period where both monolith and services process bids simultaneously with output comparison. Canary rollout: 5% → 25% → 50% → 100% with manual approval gates between stages. Full rollback procedure documented with <2-minute RTO. Only proceeds after Phase 1 and Phase 2 are stable in production for ≥30 days.
Engineering Decisions

Architectural choices, tradeoffs, and what nearly broke production

EventBridge + DDD, not gRPC + REST mash

Why EventBridge instead of synchronous gRPC service calls?

EventBridge enables temporal decoupling - the Payments service completes independently of the Notifications service. Synchronous calls require all downstream services to be available, creating availability blast radius. DDD bounded contexts map cleanly to microservices; EventBridge publishes domain events defined in the ubiquitous language, making contracts explicit and versioned.

Strangler Fig pattern, not big bang

Why a 3-phase gradual migration?

Big bang rewrites hide unknown unknowns until production. Strangler Fig gradually routes requests from the monolith to new microservices. Phase 1: Extract reporting (lowest risk, no revenue impact). Phase 2: Extract media & notifications. Phase 3: Extract live auction (highest risk). Each gate allows us to learn from production feedback before the next phase.

What nearly failed: Event schema versioning

What architectural decision almost caused coupling problems?

Initially, the design had unversioned event schemas. As soon as we started extracting services, we realized: if Payments publishes an OrderCompleted event and we add a new field later, all consumer services need schema validation. We added schema versioning with backward compatibility guarantees - old events continue to work, new consumers handle missing fields gracefully.

Lesson: Event contracts are as critical as API contracts. Schema versioning prevents the 'distributed monolith' anti-pattern.

Database per service boundaries - strict

How did you prevent shared databases in the microservices?

The design explicitly forbids cross-service database access. All inter-service communication goes through events or synchronous APIs. This enforces bounded contexts - Payments owns the orders table, Notifications owns the notification log. No hidden coupling via foreign keys. Enforcement happens in infrastructure code review and runbooks.

THE TRADEOFFS

Cost · Risk · Tradeoffs

Estimated values based on production metrics and monitoring data

Cost Impact
dramatically ↑
Team autonomy
Each service team owns their bounded context, deploy independently, no monolithic coordination bottleneck
enabled
Scaling flexibility
Can scale live auction independently from media processing; resource allocation no longer a zero-sum game
Risk Reduction
medium → low
Availability risk (live auction)
Decoupled services mean background jobs and reporting failures don't cascade to live bidding
Technical Tradeoff
high
Operational complexity
Trade: introduced distributed tracing, circuit breakers, event schema validation. Mitigated by 3-phase rollout with defined risk gates
Before vs After

Transformative Results: Before vs After

✕Before
  • Monolithic NestJS appSingle point of failure
  • Shared database across all domainsDomain coupling
  • All teams deploy togetherSlow releases
  • No service boundariesScaling constraints
→
✓After
  • 8 microservices designedIndependent deployability
  • Database-per-serviceDomain isolation
  • Independent deploymentsFaster releases
  • Clear bounded contextsIndividual scaling
⇪Business Impact
8 services designed
3-phase Strangler Fig roadmap
12 Architecture Decision Records
Tech Stack

What was used

MethodologyDomain-Driven Design (DDD)
Event BusAmazon EventBridge · Schema Registry
Queue / DLQAmazon SQS
Core servicesNestJS · Node.js · TypeScript
API GatewayAWS API Gateway
Real-time dataElastiCache Redis (Sorted Sets)
Relational dataAWS RDS MySQL
NotificationsDynamoDB
MediaS3 · CloudFront · Lambda
Migration patternStrangler Fig · Feature Flags
DocumentationArchitecture Decision Records (ADRs)
TracingAWS X-Ray · CloudWatch

Need architecture design for a complex migration or new system?

I design systems from bounded context mapping through to phased migration roadmaps - with the trade-offs documented so your team can own the decisions long-term.