Section 20 · Capstone

End-to-end system design playbook

Brings every section together: the journey of one request through a modern system, a repeatable interview framework, a trade-off cheat sheet and a worked example.

20.1 End-to-end: the journey of one request Added

A customer taps "Pay" in the mobile app. Here's every hop, and the concept that applies at each one.

Reference .NET 10 stack, hop by hop
HopLibrary / APIWhat it does here
1. Client sign-inMicrosoft.Identity.Client (MSAL.NET) in the .NET MAUI appOAuth 2.0 / OIDC with PKCE; gets an access token for the payments API
2. EdgeAzure Front Door + WAF (config); built-in MapStaticAssetsTLS, WAF rules, CDN caching of static files
3. GatewayAzure API Management, or Yarp.ReverseProxy in .NET; built-in Microsoft.AspNetCore.RateLimitingRouting, JWT check, quotas and rate limits
4. Servicebuilt-in ASP.NET Core minimal APIs, Microsoft.Identity.Web, AddValidation(), AddProblemDetails()Stateless API: validate token and input, return RFC 9457 errors
5. Outbound callsMicrosoft.Extensions.Http.ResilienceTimeouts, retries, circuit breakers to fraud and core systems
6. CacheMicrosoft.Extensions.Caching.Hybrid + Microsoft.Extensions.Caching.StackExchangeRedisL1 + Redis L2 for reference data and balances
7. DatabaseMicrosoft.EntityFrameworkCore.SqlServer (EF Core 10)ACID transaction; unique index on the idempotency key; outbox row in the same transaction
8. MessagingAzure.Messaging.ServiceBus; outbox via WolverineFx (free) or MassTransit v9 commercial licencePublish PaymentCompleted to notifications, ledger, analytics
9. AIMicrosoft.Extensions.AI, Microsoft.Extensions.VectorData.Abstractions, Microsoft.Agents.AIFraud explanation, RAG help, agent actions with approvals
10. ObservabilityOpenTelemetry.Extensions.Hosting, Azure.Monitor.OpenTelemetry.AspNetCore; Aspire ServiceDefaults + dashboardOne trace ID from tap to ledger; metrics for SLOs
app.MapPost("/payments", async (PaymentRequest req, [FromHeader(Name = "Idempotency-Key")] Guid key,
                                PaymentsDb db, CancellationToken ct) =>
{
    if (await db.Payments.AnyAsync(p => p.IdempotencyKey == key, ct))
        return Results.Ok();                                    // retry: same result, no double charge

    var payment = Payment.Create(req, key);
    db.Payments.Add(payment);
    db.Outbox.Add(OutboxMessage.From(new PaymentCompleted(payment.Id)));  // same transaction
    await db.SaveChangesAsync(ct);                              // unique index backs up the check
    return Results.Created($"/payments/{payment.Id}", payment.Id);
})
.RequireAuthorization("payments.write")
.RequireRateLimiting("per-user");

What happens when you tap "Pay" in a banking app? 💳 (a system design tour)

DNS → WAF/CDN → API gateway (JWT, rate limit) → load balancer → stateless service → cache + DB (ACID + idempotency key) → outbox → event bus → notifications, ledger, analytics.

Every arrow is a design decision: sync or async, consistency or availability, retry or fail fast.

Save this for your next system design interview. #SystemDesign #Architecture #Banking

20.2 System design interview framework Added

A repeatable 7-step structure for a 45–60 minute design discussion. Structure beats brilliance.

1. Clarify~5 min
→
2. Estimate~3 min
→
3. API~5 min
→
4. Data model~5 min
→
5. High-level design~10 min
→
6. Deep dives~15 min
→
7. Wrap-up~5 min

1. Clarify requirements

  • Functional: core use cases (pick 2–3)
  • Non-functional: scale, latency, availability, consistency, security, compliance
  • Constraints: cloud, existing systems, budget, timeline
  • Out of scope

2. Back-of-the-envelope

  • Users → requests/s (average and peak)
  • Read:write ratio
  • Storage per year, bandwidth
  • This drives the design choices

3. API design

  • Main endpoints or events
  • Request and response shapes
  • Auth, idempotency, pagination

4. Data model

  • Entities and relationships
  • SQL or NoSQL, and why
  • Partition or shard key, indexes

5. High-level design

  • Draw the boxes: client → edge → gateway → services → data → async
  • Walk through 1–2 key flows end to end

6. Deep dives

  • Bottlenecks and scaling (cache, replicas, sharding, queues)
  • Failure modes (what if X dies?)
  • Consistency, security, observability

7. Wrap-up

  • Summarise key trade-offs
  • Risks and what you'd do next
  • How you'd evolve it (v2)
  • Think aloud. Interviewers score your reasoning, not just the final picture.
  • Say the trade-off every time: "I chose X because…, at the cost of…".
  • Start simple, then scale. Don't open with Kafka + Kubernetes + 12 microservices.

20.3 How to answer any "X vs Y" question Added

One formula covers Sync vs Async, SQL vs NoSQL, Queue vs Event, ETL vs ELT…

1. Define bothone line each
→
2. How each worksa tiny diagram
→
3. Key differences3–4 dimensions
→
4. When to pick whichdecision rule
→
5. Real examplefrom your experience
Example: "Queue vs Event? A queue hands a command to one consumer. An event broadcasts a fact to many. Queues give load levelling and reliable hand-off. Events give decoupling and fan-out. I'd use a queue for 'process this payment' and an event for 'payment completed'. At my last project we used Service Bus queues for… and topics for…"

20.4 Trade-off cheat sheet Added

Common either/or decisions, with a rule of thumb for when to choose each side.

DecisionChoose left when…Choose right when…
Sync ↔ AsyncUser needs the answer nowWork can finish later, fan-out, spikes
SQL ↔ NoSQLTransactions, joins, integrityMassive scale, flexible schema, known access patterns
Consistency ↔ AvailabilityMoney, inventory, identityFeeds, catalogues, analytics
Monolith ↔ MicroservicesSmall team, unclear domainMany teams, clear boundaries, independent scaling
Vertical ↔ HorizontalQuick fix, stateful DBHA, unlimited growth, stateless tier
Cache ↔ Always freshRead-heavy, staleness OKCorrectness critical
Normalise ↔ DenormaliseWrite integrityRead speed
Batch ↔ StreamLarge periodic volume, cost-sensitiveLow latency reactions
Queue ↔ Event streamWork distribution, commandsReplay, many consumers, high throughput
Orchestration ↔ ChoreographyComplex flow, visibility, compensationSimple flow, autonomy
Serverless ↔ ContainersSpiky, event-driven, short tasksSteady load, long-running, full control
REST ↔ gRPCPublic, partner, browserInternal, low latency, streaming
Build ↔ BuyCore differentiatorCommodity capability
RAG ↔ Fine-tuneChanging knowledge, citationsBehaviour, style, narrow task
Workflow ↔ AgentPredictable stepsOpen-ended, needs judgement

20.5 Worked example: real-time payment notification platform Added

"Design a service that notifies customers (push, SMS, email) within 5 seconds of any card or account transaction."

1. Requirements

  • F: subscribe to preferences, receive notifications, view history
  • NFR: 5M customers, p95 delivery < 5 s, 99.95% availability, at-least-once delivery with no duplicate pushes, PII protected

2. Estimates

  • 20M transactions/day ≈ 230/s average, ~1,000/s peak
  • History: 20M × 1 KB ≈ 20 GB/day → keep 90 days hot (~1.8 TB)

3. API / events

  • Consumes TransactionPosted events (from core banking via CDC)
  • PUT /customers/{id}/notification-preferences
  • GET /customers/{id}/notifications?cursor=

4. Data

  • Preferences: SQL (small, relational) + Redis cache
  • Notification history: NoSQL (Cosmos/Cassandra), partition key = customerId

5. Design

6. Deep dives

7. Trade-offs

Near-real-time events instead of synchronous calls from core banking (decoupled, but eventually consistent). At-least-once delivery + dedupe instead of costly exactly-once. NoSQL for history (scale) instead of SQL (ad-hoc queries go to the lakehouse instead).

The .NET stack for this design
ComponentLibrary / APINotes
Consume TransactionPostedAzure.Messaging.ServiceBus (ServiceBusSessionProcessor, session = accountId) or Azure.Messaging.EventHubs.Processor (partition key = accountId)Per-account ordering; workers as a BackgroundService on Container Apps, scaled by KEDA on queue depth
DedupeStackExchange.Redis (StringSetAsync with When.NotExists + 24 h expiry)Key = transactionId + channel
PreferencesEF Core 10 (Microsoft.EntityFrameworkCore.SqlServer) + HybridCacheSmall relational data, read-heavy, cached
HistoryMicrosoft.Azure.Cosmos, partition key /customerId, TTL 90 daysCursor paging with continuation tokens for GET ...?cursor=
Push / SMS / emailMicrosoft.Azure.NotificationHubs, Azure.Communication.Sms, Azure.Communication.EmailOne sender class per channel behind an interface, so a second SMS provider can be swapped in
Provider outageMicrosoft.Extensions.Http.Resilience / Polly.CoreCircuit breaker per provider, then failover; dead-letter + replay
SecurityAzure.Identity (managed identity), Microsoft.Extensions.Compliance.RedactionNo secrets in config; mask card numbers in logs
ObservabilityOpenTelemetry; a custom Histogram<double> for "posted → delivered" secondsSLO = % delivered within 5 s
processor.ProcessMessageAsync += async args =>
{
    var evt = args.Message.Body.ToObjectFromJson<TransactionPosted>()!;
    foreach (var channel in await prefs.GetChannelsAsync(evt.CustomerId, args.CancellationToken))
    {
        // Claim (transaction, channel) once for 24 h; a redelivery finds the key and skips
        bool first = await redis.StringSetAsync($"notif:{evt.TransactionId}:{channel}", 1,
                                                TimeSpan.FromHours(24), When.NotExists);
        if (first) await senders[channel].SendAsync(evt, args.CancellationToken);
    }
    await args.CompleteMessageAsync(args.Message);
};

If a send fails after the key is set, that channel is skipped on retry. Delete the key in a catch before rethrowing, or set it only after a successful send.

20.6 Design review checklist Added

Questions to run through before you present or approve a design.

Scale & performance

  • Peak load known?
  • Stateless tier?
  • Caching plan?
  • DB scaling path?

Reliability

  • SPOFs removed?
  • Timeouts, retries, circuit breakers?
  • RPO/RTO met and tested?

Security

  • AuthN/AuthZ on every call?
  • Secrets in a vault?
  • Data classified and encrypted?
  • Threat model done?

Integration

  • Right style (RT / NRT / batch)?
  • Idempotency and DLQs?
  • Contracts versioned?

Operations

  • SLOs and alerts?
  • Tracing end to end?
  • Runbooks and SOPs?
  • Safe deployment strategy?

Governance

  • ADRs for key decisions?
  • NFRs measurable?
  • Cost estimated?
  • Compliance (APRA, Privacy, PCI)?
Libraries that tick each box
AreaLibrary / API
Scale & performanceHybridCache, output caching, BenchmarkDotNet, EF Core read replicas / compiled queries
ReliabilityMicrosoft.Extensions.Http.Resilience, health checks (MapHealthChecks), Service Bus dead-letter queues
SecurityMicrosoft.Identity.Web, authorization policies, Azure.Identity + Azure.Security.KeyVault.Secrets, Data Protection, Microsoft.Extensions.Compliance.Redaction
IntegrationMicrosoft.AspNetCore.OpenApi, Asp.Versioning.Http, Azure.Messaging.ServiceBus, outbox with WolverineFx
OperationsOpenTelemetry + Azure.Monitor.OpenTelemetry.AspNetCore, Aspire dashboard, Microsoft.FeatureManagement.AspNetCore for safe rollouts
GovernanceTngTech.ArchUnitNET tests for agreed rules; ADRs as Markdown in the repo
← 19 · Banking & Payments