Architecture Challenges

Advanced challenges that combine multiple concepts — design complete solutions for complex real-world scenarios.

Objective

The challenges in this section combine multiple architecture concepts into complex scenarios. Unlike the previous labs (which focus on a single topic), here you’ll need to integrate patterns, resilience, security, and operations into complete solutions.

Each challenge has a difficulty level and an estimated time. Try to solve them before looking at the hints.


Challenge 1: Zero-downtime migration

Level: Intermediate | Estimated time: 30-45 minutes

Scenario

Your company has a monolith that handles authentication, catalog, and orders. You need to extract the orders service as a microservice without downtime. The monolith processes 500 orders per hour and cannot be stopped.

Requirements

  1. Zero downtime during the migration
  2. The ability to roll back at any point
  3. Historical order data must be available in the new service
  4. Existing reports must keep working

Your task

Design a step-by-step migration plan. Consider:

  • How do you migrate the data without stopping the monolith?
  • How do you gradually redirect traffic?
  • How do you verify that the new service works correctly?
  • What’s your rollback plan?

Hints

Hint 1: Migration pattern

Use the Strangler Fig pattern:

  1. Create the new orders service
  2. Redirect reads first (lower risk)
  3. Then redirect writes
  4. Finally, remove the orders code from the monolith
Hint 2: Data migration

Use Change Data Capture (CDC):

  1. Do an initial dump of the historical orders into the new service
  2. Set up CDC to replicate changes in real time from the monolith to the new service
  3. Once both are in sync, redirect the traffic
Hint 3: Verification

Use the Shadow Traffic pattern:

  1. Send a copy of each request to the new service (without affecting the user)
  2. Compare the responses from the monolith and the new service
  3. When the responses match 99.9% of the time, redirect the real traffic

Reference solution

View full solution

Phase 1 — Preparation (week 1-2):

  • Create the new orders service with the same API
  • Set up CDC from the monolith’s orders table
  • Migrate historical data with a batch script

Phase 2 — Verification (week 3):

  • Enable shadow traffic: each request to the monolith is duplicated to the new service
  • Compare responses automatically and log discrepancies
  • Fix bugs until the responses match

Phase 3 — Gradual migration (week 4):

  • Redirect 10% of read traffic to the new service
  • Monitor latency, errors, and data consistency
  • Increase gradually: 25% → 50% → 75% → 100%

Phase 4 — Writes (week 5):

  • Redirect writes to the new service
  • The new service publishes events that the monolith consumes (for legacy reports)
  • Monitor intensively for 48 hours

Phase 5 — Cleanup (week 6):

  • Remove the orders code from the monolith
  • Disable CDC
  • Update reports to use the new data source

Rollback at any phase: Revert the proxy/load balancer configuration to send traffic to the monolith.


Challenge 2: Multi-provider payment system

Level: Advanced | Estimated time: 45-60 minutes

Scenario

Design a payment service that supports multiple providers (Stripe, PayPal, bank transfer) with the following requirements:

  1. Idempotency: a payment cannot be processed twice
  2. Resilience: if one provider fails, automatically try another
  3. Auditing: every payment attempt must be logged
  4. Reconciliation: the ability to verify that charges match orders
  5. PCI compliance: don’t store card data in your system

Your task

Design:

  1. The payment service API
  2. The internal processing flow
  3. The data model
  4. The resilience strategies
  5. The reconciliation flow

Hints

Hint 1: Idempotency

Use an idempotency key generated by the client:

POST /payments
Headers: Idempotency-Key: uuid-unique-per-payment
Body: { orderId, amount, currency, provider }

Before processing, check whether a payment with that key already exists. If it does, return the previous result without processing again.

Hint 2: Data model
Payment {
  id: UUID
  idempotencyKey: string (unique)
  orderId: string
  amount: decimal
  currency: string
  status: PENDING | PROCESSING | COMPLETED | FAILED | REFUNDED
  provider: STRIPE | PAYPAL | BANK_TRANSFER
  providerTransactionId: string?
  attempts: PaymentAttempt[]
  createdAt: timestamp
  updatedAt: timestamp
}

PaymentAttempt {
  id: UUID
  paymentId: UUID
  provider: string
  status: SUCCESS | FAILED | TIMEOUT
  errorCode: string?
  errorMessage: string?
  responseTime: number
  attemptedAt: timestamp
}
Hint 3: Fallback strategy

Define a provider priority order by payment type:

  1. Credit card: Stripe → PayPal
  2. Debit card: Stripe → PayPal
  3. Transfer: bank only

If the primary provider fails with a recoverable error (timeout, 503), try the next one. If it fails with a non-recoverable error (card declined, insufficient funds), don’t retry.


Challenge 3: Design a notification system

Level: Intermediate | Estimated time: 30-45 minutes

Scenario

Design a notification service that supports:

  • Channels: email, push, SMS, in-app
  • Priorities: critical (immediate), high (< 1 min), normal (< 5 min), low (daily batch)
  • Preferences: users can choose which channels to receive by notification type
  • Rate limiting: don’t send more than 10 notifications per hour per user
  • Templates: notifications use templates with dynamic variables

Your task

  1. Define the API for sending notifications
  2. Design the internal processing flow
  3. Define how you handle user preferences
  4. Design the rate limiting strategy
  5. Define what happens when a channel fails

Considerations

  • Do you use one queue per priority or a single queue with priorities?
  • How do you handle the daily batch of low-priority notifications?
  • What happens if the email service is down but push is working?
  • How do you avoid sending the same notification twice?
View suggested architecture
[Notifications API]


[Priority queue]
    ├── CRITICAL queue (immediate processing, dedicated workers)
    ├── HIGH queue (processing < 1 min)
    ├── NORMAL queue (processing < 5 min)
    └── LOW queue (accumulation for batch)


[Notification Processor]

    ├── Check user preferences
    ├── Apply rate limiting
    ├── Resolve template with variables


[Channel Router]
    ├──► [Email Provider] (SendGrid/SES)
    ├──► [Push Provider] (Firebase/APNs)
    ├──► [SMS Provider] (Twilio)
    └──► [In-App Store] (WebSocket/DB)

Rate limiting: Use Redis with a per-user counter with a 1-hour TTL. Before sending, check the counter. If it exceeds the limit, enqueue for the next period.

Deduplication: Use a composite key (userId + notificationType + entityId) with a TTL to avoid duplicates.


Challenge 4: End-to-end observability

Level: Advanced | Estimated time: 45-60 minutes

Scenario

You have 8 microservices in production. A user reports that “checkout is sometimes slow.” You can’t reproduce the problem consistently. Design an observability strategy that lets you diagnose this type of problem.

Your task

  1. What metrics would you collect from each service?
  2. How would you implement distributed tracing?
  3. What alerts would you configure?
  4. How would you correlate logs across services?
  5. What dashboard would you create for the on-call team?

Success criteria

Your design should make it possible to:

  • Identify which service causes the latency in under 5 minutes
  • See the complete trace of a user’s request
  • Detect degradations before users report them
  • Understand the impact of a deploy on the system’s latency
View suggested strategy

Metrics (per service):

  • Latency: p50, p90, p95, p99 per endpoint
  • Error rate: 4xx and 5xx per endpoint
  • Throughput: requests per second
  • Saturation: CPU, memory, DB connections, thread pool

Distributed tracing:

  • Generate a traceId at the API Gateway
  • Propagate it via the X-Trace-Id header to all services
  • Each service records spans with start, end, and metadata
  • Use OpenTelemetry as the standard

Alerts:

  • p99 latency > 2x the baseline for more than 5 minutes
  • 5xx error rate > 1% for more than 2 minutes
  • Message queue growing for more than 10 minutes
  • CPU > 80% for more than 5 minutes

On-call dashboard:

  • Overview: health of all services (green/yellow/red)
  • Flow view: latency per checkout step
  • Traces view: the 10 slowest traces from the last hour
  • Errors view: errors grouped by type and service

Reflection

After completing the challenges:

  1. Which challenge was the hardest? Why?
  2. In which areas do you feel you need more practice?
  3. Were you able to reuse patterns from previous sections?
  4. Which challenge most resembles a real problem you face at work?

The challenges are designed to simulate the real complexity of software architecture. There are no perfect solutions — there are well-reasoned solutions.