System Architecture

Complete architecture for modernizing the legacy .NET monolith into .NET 8 microservices. Strangler Fig pattern, event-driven, per-service databases, zero downtime.

1. Current State vs Target State

Current State — Legacy Monolith

Single deployment
Deploy everything for 1 bug fix
Shared DB (200+ tables)
Changing 1 table impacts multiple modules
Can't scale independently
Must scale entire monolith when only Travel is under load
.NET Framework (EOL)
No security patches, outdated ecosystem
Not event-driven
No audit trail, cannot feed AI/ML
Hard to test
Tight coupling = integration tests must run entire system

Target State — End of 9 Months

2. Service Boundaries (Bounded Contexts)

#ServiceContextTypeData OwnedAPIsGo-Live
1Travel BookingTravelCorebookings, itineraries, suppliers, pricing rules/api/travel/*Month 3
2Event ManagementEventsCoreevents, venues, schedules, attendees/api/events/*Month 4
3Workforce + AllocationWorkforceSupportingstaff profiles, allocations, shifts, skills/api/staff/*Month 6
4CommunicationsCommsGenericnotifications, templates, delivery logs/api/comms/*Month 7
5Reporting (CQRS)ReportingSupportingreport definitions, read models, dashboards/api/reports/*Month 7
6Payment (Legacy)PaymentCore (Frozen)payments, invoices, reconciliation/api/payments/* (via ACL bridge)

Boundary Decision Rationale

DecisionChoseOverBecause
Travel & Event = separate2 services1 "Operations" serviceDifferent lifecycle, different scaling (Travel = high-freq CRUD, Event = complex scheduling)
Allocation → Workforce1 service, 2 modules2 separate servicesSame domain (people + allocation), same data. 2 services too small for 5 eng
Communications = separateCross-cutting serviceEmbed in each serviceAvoid duplicate logic (email/SMS/push). Shared across all modules
Reporting = CQRS read serviceDedicated serviceEach service serves own reportsCross-module reports need aggregated data. Read-only = scales differently
Payment = stay in legacyACL bridgeMigrate earlyConstraint: frozen Phase 1. Highest risk (PCI, financial). ACL isolates safely

3. Communication Model

Sync (REST)

Client → Gateway → Service. Service → ACL → Legacy Payment. Immediate response.

Rule: Client calls = sync via Gateway

Async (Service Bus)

BookingCreated → Comms sends email. Events → Reporting updates read model.

Rule: State changes = publish event

CDC (Data Sync)

Legacy DB → CDC stream → Reporting DB. No dual-write needed.

Rule: No cross-service DB access — ever

YARP Routing Configuration

/api/travel/** → travel-service:8080
/api/events/** → event-service:8080
/api/workforce/** → workforce-service:8080
/api/comms/** → comms-service:8080
/api/reports/** → reporting-service:8080
/api/payments/**legacy-monolith:80 ← Strangler Fig
/**legacy-monolith:80 ← Catch-all
Routes shift from legacy → new as services go live. When module fully migrated, route permanently points to new service.

Strangler Fig Migration Flow

4. Event Schema Standard

{
  "eventId": "uuid-v4",
  "eventType": "travel.booking.created",
  "source": "travel-booking-service",
  "timestamp": "2026-03-14T10:30:00Z",
  "version": "1.0",
  "correlationId": "uuid-v4",
  "data": {
    "bookingId": "BK-12345",
    "userId": "USR-67890",
    "destination": "Tokyo",
    "totalAmount": 2500.00
  },
  "metadata": {
    "tenantId": "tenant-001",
    "region": "APAC"
  }
}

Event Schema Rules

Versionedversion field, backward compatible evolution
ImmutableEvents are facts, never modified after publish
Self-containedConsumer must NOT need to call back to producer
IdempotentConsumers must handle duplicate delivery gracefully
AI-readyEvery event captured in Event Store for future ML/analytics
Topics: travel.booking.* · event.mgmt.* · workforce.assignment.* · comms.notify.* · payment.acl.*

5. Anti-Corruption Layer (ACL) — Legacy Payment

ACL Interface

ProcessPayment(bookingId, amount, currency, idempotencyKey)
GetPaymentStatus(transactionId)
RequestRefund(transactionId, amount)

ACL Responsibilities

  • Contract translation — new contracts ↔ legacy API formats
  • Error mapping — legacy error codes → standardized responses
  • Resilience — circuit breaker, retry with backoff, 5s timeout
  • Logging — every ACL call logged for audit trail
  • Testing — contract tests verify ACL ↔ legacy compat
Key: When Payment is modernized later → swap ACL target, ZERO changes to consumers.

6. Service Internal Architecture (Clean Architecture)

Project Structure

ServiceName/
├── src/
├── ServiceName.API/
Controllers · Middleware · Filters
├── ServiceName.Application/
Commands · Queries · EventHandlers · DTOs
├── ServiceName.Domain/
Entities · ValueObjects · Events · Interfaces
└── ServiceName.Infrastructure/
Persistence · Messaging · ExternalServices · Cache
├── tests/
Unit · Integration · Contract (Pact)
├── Dockerfile
└── docker-compose.yml

SharedKernel NuGet Package

Cross-cutting concerns shared across all services as internal NuGet package:

  • • Structured logging (Serilog)
  • • OpenTelemetry tracing
  • • Health check endpoints
  • • Exception handling middleware
  • • Correlation ID propagation
  • • Event publishing abstractions
  • • Base entity classes
  • • Common value objects

Version-controlled. Breaking changes = major version bump.

7. Database Architecture

Per-Service Database Topology

Data Sync Strategies

ScenarioStrategyHow
New service → new service (read)EventsSubscribe to domain events, maintain local projection
New service → legacy payment (write)ACL (sync call)REST through ACL → legacy API
Legacy DB → Reporting (read)CDC (Change Data Capture)Debezium/Azure CDC → Reporting DB read models
Data migration (one-time)ETL + CDCInitial bulk load (ETL) then continuous sync (CDC)
Cross-service queriesReporting ServiceNo cross-DB joins. Reporting for aggregated views

Database Migration Path

Phase 0–1: Services use DB views (read-only access to monolith). Phase 2–3: Data migrated via CDC, per-service DBs become authoritative. Legacy DB shrinks as modules extract.

8. Frontend Architecture

Strategy

  • App Shell: React 18, nav + auth + layout. Deployed Phase 0–1
  • Module-by-module: Each migrates when backend service goes live
  • Legacy iframe: Payment UI embedded via iframe (Phase 1–2)
  • Code splitting: Each module lazy-loaded on navigation

Shared Design System (Storybook)

ButtonsFormsTablesModalsChartsNavigationAlertsCards

Built upfront, shared across all modules. Ensures consistent UX during phased migration.

9. Infrastructure & Deployment

Compute

  • Azure Container Apps
  • Auto-scale per service
  • No K8s ops overhead

Data

  • Azure SQL (per-service)
  • Azure Service Bus (Standard)
  • Azure Key Vault (secrets)

DevOps

  • Bicep IaC (Azure-native)
  • GitHub Actions CI/CD
  • Azure Container Registry

10. CI/CD Pipeline

Branch Strategy

main ← feature/* (PR required, CodeRabbit + human review)
main → staging (auto-deploy)
staging → production (manual approval gate)

11. Security Architecture (4 Layers)

Layer 1: Edge

  • TLS termination (HTTPS only)
  • JWT validation (Azure AD)
  • Rate limiting (per-user, per-IP)
  • CORS policy enforcement
  • Request size limits

Layer 2: Service-to-Service

  • Managed identities (Azure AD)
  • mTLS between services
  • No direct DB access from other services
  • Internal network only

Layer 3: Data

  • Encryption at rest (Azure SQL TDE)
  • Encryption in transit (TLS 1.3)
  • Secrets in Azure Key Vault
  • PII data masked in logs
  • Payment data: legacy only (Phase 1)

Layer 4: Pipeline

  • SAST (CodeQL) in CI
  • Dependency scanning (Dependabot/Snyk)
  • Container image scanning
  • AI code: same security gates as human code

12. Observability (3 Pillars + AI)

Health Check Endpoints

/health → liveness (is process running?)
/ready → readiness (can accept traffic? DB connected?)
/startup → startup probe (initialization complete?)

AI-Enhanced Monitoring

  • • Anomaly detection on metrics (Azure Monitor AI)
  • • Log pattern analysis (clustering errors)
  • • Predictive alerting (trend-based, not threshold)
  • • Smart routing adjustment based on error rates

13. Architecture Decision Records (ADRs)

#DecisionOverRationale
ADR-001Strangler Fig PatternBig bang rewriteZero downtime required, 40K users
ADR-002YARP as API GatewayAzure APIM / Ocelot.NET-native, Strangler Fig routing, lightweight
ADR-003Azure Service BusKafka / RabbitMQManaged (5 eng can't ops Kafka), enterprise SLA
ADR-004Per-service Azure SQL DBsShared databaseService autonomy, independent deploy
ADR-005Payment stays in monolithEarly modernizeConstraint: frozen Phase 1. Highest risk (PCI)
ADR-006Clean Architecture per serviceMinimal/simple layersTestability, separation, consistent team patterns
ADR-007Contract testing (Pact)Full E2E onlyVerify service compat without full environment
ADR-008CDC for legacy→reporting syncDual-write / ETL onlyNon-invasive, real-time, no legacy code changes
ADR-009Shared NuGet package (SharedKernel)Copy-paste across servicesDRY logging, tracing, health. Versioned independently
ADR-010Event schema versioningUnversioned eventsBackward compatibility, independent evolution
ADR-011AI-first engineering (2× multiplier)Traditional development5 eng/9 months requires force multiplication
ADR-012AI code = same CI gates as humanRelaxed AI code review60-75% code is AI-generated → same quality bar