Post

.NET Architecture at Scale: Visual Guide to Modern Design Patterns

A comprehensive visual guide exploring 16 essential architectural patterns for .Net applications, featuring workflow diagrams, decision trees, and implementation examples.

.NET Architecture at Scale: Visual Guide to Modern Design Patterns

.NET Architecture at Scale: Visual Guide to Modern Design Patterns

⚡ Performance Note: This article contains 16 comprehensive patterns with visual diagrams. For faster loading, consider bookmarking specific sections or using the “Find in Page” (Ctrl+F) feature to jump to relevant patterns.

The landscape of .NET architecture has evolved dramatically with .Net and Azure’s latest services, creating unprecedented opportunities for building scalable, resilient applications. Modern enterprises face complex challenges: distributed systems that must handle millions of transactions, microservices that need seamless communication, and legacy systems requiring careful modernization.

This comprehensive visual guide explores 16 essential architectural patterns through workflow diagrams, decision trees, and interaction flows that help architects make informed decisions about system design using the latest .NET ecosystem.

Table of Contents

  1. Foundational Architecture Patterns
  2. Advanced Communication Patterns
  3. Service Integration Patterns
  4. Resilience and Data Patterns
  5. Modern .NET and Azure Integration
  6. Deployment and Infrastructure Patterns
  7. Comprehensive Pattern Selection Guide
  8. Best Practices for Implementation

Comprehensive Pattern Selection Guide

This section provides both interactive decision flows and detailed comparison matrices to help you choose optimal architectural patterns for your .NET applications.

Interactive Decision Flow

Start your architectural journey with this decision tree to identify the most suitable patterns for your specific requirements:

flowchart TD
    Start([Architecture Design Decision]) --> A{Application Type?}
    
    A -->|Simple CRUD| B[Layered Architecture]
    A -->|Complex Business Logic| C{Domain Complexity?}
    A -->|Microservices| D{Communication Style?}
    A -->|Legacy Modernization| E[Strangler Fig Pattern]
    A -->|Event-Driven/Stateless| F1[Serverless Pattern]
    
    C -->|High| F[Onion Architecture]
    C -->|Medium| G[Hexagonal Architecture]
    
    D -->|Synchronous| H{Load Distribution?}
    D -->|Asynchronous| I[Event-Driven Architecture]
    
    H -->|High| J[API Gateway + BFF]
    H -->|Medium| K[API Gateway]
    
    I --> L{Transaction Consistency?}
    L -->|Required| M[Saga Pattern]
    L -->|Eventual| N[CQRS + Event Sourcing]
    L -->|Message Reliability| O1[Outbox Pattern]
    
    B --> O{Scaling Needs?}
    F --> P{Read/Write Patterns?}
    G --> P
    
    O -->|High| Q[Add Resilience Patterns]
    P -->|Different| R[CQRS]
    P -->|Same| S[Repository Pattern]
    
    Q --> T[Circuit Breaker + Bulkhead]
    
    %% Infrastructure Patterns
    J --> U{Service Management?}
    K --> U
    U -->|Cross-cutting| V[Sidecar Pattern]
    U -->|External Communication| W[Ambassador Pattern]
    
    style Start fill:#ffeb3b
    style B fill:#c8e6c9
    style F fill:#ffcdd2
    style G fill:#f8bbd9
    style I fill:#e1bee7
    style J fill:#b3e5fc
    style F1 fill:#e8f5e8
    style O1 fill:#fff3e0
    style V fill:#f3e5f5
    style W fill:#e3f2fd

Complexity vs. Benefit Analysis

graph LR
    subgraph "Low Complexity"
        L1[Layered Architecture<br/>⭐⭐⭐⭐⭐]
        L2[API Gateway<br/>⭐⭐⭐⭐]
        L3[Circuit Breaker<br/>⭐⭐⭐⭐]
    end
    
    subgraph "Medium Complexity"
        M1[Hexagonal Architecture<br/>⭐⭐⭐]
        M2[CQRS<br/>⭐⭐⭐]
        M3[BFF<br/>⭐⭐⭐]
        M4[Serverless<br/>⭐⭐⭐]
    end
    
    subgraph "High Complexity"
        H1[Onion Architecture<br/>⭐⭐]
        H2[Event Sourcing<br/>⭐⭐]
        H3[Saga Pattern<br/>⭐⭐]
        H4[Strangler Fig<br/>⭐⭐]
    end
    
    style L1 fill:#c8e6c9
    style L2 fill:#c8e6c9
    style L3 fill:#c8e6c9
    style M1 fill:#fff3e0
    style M2 fill:#fff3e0
    style M3 fill:#fff3e0
    style M4 fill:#fff3e0
    style H1 fill:#ffcdd2
    style H2 fill:#ffcdd2
    style H3 fill:#ffcdd2
    style H4 fill:#ffcdd2

Comprehensive Pattern Comparison Matrix

PatternTeam Skill LevelSetup TimeMaintenanceScalabilityTestabilityUse Case Fit
LayeredBeginnerFastLowMediumMediumCRUD Apps
HexagonalIntermediateMediumMediumHighVery HighClean Architecture
OnionAdvancedSlowHighVery HighVery HighEnterprise DDD
CQRSIntermediateMediumMediumVery HighHighRead/Write Split
Event-DrivenAdvancedSlowHighVery HighMediumReal-time Systems
SagaExpertVery SlowVery HighHighMediumDistributed Transactions
API GatewayBeginnerFastLowVery HighMediumMicroservices
BFFIntermediateMediumMediumHighHighMulti-client Apps
ServerlessIntermediateFastLowAutoMediumEvent Processing
Strangler FigAdvancedVariableHighHighMediumLegacy Migration
Circuit BreakerBeginnerFastLowMediumHighFault Tolerance
OutboxIntermediateMediumMediumHighMediumMessage Reliability
SidecarIntermediateMediumMediumHighMediumCross-cutting Concerns
AmbassadorAdvancedMediumHighHighMediumService Proxy
BulkheadAdvancedSlowHighVery HighMediumResource Isolation
Event SourcingExpertVery SlowVery HighHighMediumAudit Trail

Quick Selection Guide

flowchart TD
    A[What's your primary need?] --> B[Simple CRUD Application]
    A --> C[High Performance & Scale]
    A --> D[Clean Testable Code]
    A --> E[Legacy Modernization]
    A --> F[Multi-client Support]
    A --> G[Fault Tolerance]
    
    B --> B1[✅ Layered Architecture]
    C --> C1[✅ CQRS + Event-Driven]
    D --> D1[✅ Hexagonal/Onion]
    E --> E1[✅ Strangler Fig]
    F --> F1[✅ API Gateway + BFF]
    G --> G1[✅ Circuit Breaker + Bulkhead]
    
    style B1 fill:#c8e6c9
    style C1 fill:#ffeb3b
    style D1 fill:#e3f2fd
    style E1 fill:#fff3e0
    style F1 fill:#f3e5f5
    style G1 fill:#ffcdd2

Architectural Pattern Categories

Understanding pattern relationships helps in making informed architectural decisions:

mindmap
  root((Design Patterns))
    Foundational
      Layered
      Hexagonal
      Onion
    Communication
      CQRS
      Event-Driven
      Saga
    Integration
      API Gateway
      BFF
      Serverless
      Strangler Fig
    Resilience
      Circuit Breaker
      Bulkhead
      Outbox
    Infrastructure
      Sidecar
      Ambassador
    Data
      Event Sourcing

Foundational Architecture Patterns

Layered Architecture

Layered Architecture remains the most familiar pattern for .NET developers, organizing applications into horizontal layers with clear separation of concerns. While simpler than modern alternatives, it provides an excellent foundation for understanding architectural principles.

Architecture Flow

graph TB
    A[Presentation Layer<br/>Controllers, Views, APIs] --> B[Business Logic Layer<br/>Services, Domain Logic]
    B --> C[Data Access Layer<br/>Repositories, ORM, DAL]
    C --> D[Database Layer<br/>SQL Server, Entity Framework]
    
    style A fill:#e1f5fe
    style B fill:#f3e5f5
    style C fill:#e8f5e8
    style D fill:#fff3e0

Request Processing Flow

sequenceDiagram
    participant C as Client
    participant P as Presentation Layer
    participant B as Business Layer
    participant D as Data Layer
    participant DB as Database
    
    C->>P: HTTP Request
    P->>P: Validate Input
    P->>B: Call Business Logic
    B->>B: Apply Business Rules
    B->>D: Repository Call
    D->>DB: SQL Query
    DB-->>D: Data
    D-->>B: Domain Objects
    B-->>P: Processing Result
    P-->>C: HTTP Response

When to Use Decision Tree

flowchart TD
    A[Consider Layered Architecture] --> B{Team Experience?}
    B -->|Junior/Mixed| C{Project Complexity?}
    B -->|Senior| D[Consider Advanced Patterns]
    
    C -->|Low-Medium| E[✅ Use Layered]
    C -->|High| F[Consider Hexagonal/Onion]
    
    E --> G{Future Scalability?}
    G -->|Limited| H[✅ Layered is Perfect]
    G -->|High| I[Plan Migration Path]
    
    style E fill:#c8e6c9
    style H fill:#c8e6c9
    style F fill:#ffcdd2
    style D fill:#ffcdd2

Best for: CRUD applications, rapid prototyping, small teams, learning projects.

Benefits: Familiarity, simplicity, quick development cycles.

Challenges: Tight coupling between layers, difficulty in testing, scalability limitations.

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Business Logic Layer - Service
public class ProductService : IProductService
{
    private readonly IProductRepository _productRepository;
    
    public ProductService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
    
    public async Task<IEnumerable<Product>> GetAllProductsAsync()
    {
        return await _productRepository.GetAllAsync();
    }
    
    public async Task<Product> CreateProductAsync(CreateProductRequest request)
    {
        var product = new Product
        {
            Name = request.Name,
            Price = request.Price,
            CreatedAt = DateTime.UtcNow
        };
        
        return await _productRepository.CreateAsync(product);
    }
}

// Data Access Layer - Repository
public class ProductRepository : IProductRepository
{
    private readonly AppDbContext _context;
    
    public ProductRepository(AppDbContext context)
    {
        _context = context;
    }
    
    public async Task<IEnumerable<Product>> GetAllAsync()
    {
        return await _context.Products.ToListAsync();
    }
    
    public async Task<Product> CreateAsync(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync();
        return product;
    }
}

When to use: Simple to medium-complexity applications, traditional enterprise systems, rapid prototyping, or when working with junior developers.

Advantages include: Simplicity, familiarity, and quick development cycles.

Disadvantages include: Tight coupling, testing challenges, and database-centric design that can hinder flexibility.

Hexagonal Architecture

Hexagonal Architecture, also known as Ports and Adapters, isolates core business logic from external concerns through well-defined interfaces. This pattern excels when technology independence and testability are paramount.

Architecture Structure

graph TB
    subgraph "Primary Adapters"
        A1[Web API]
        A2[UI]
        A3[Console]
    end
    
    subgraph "Ports"
        P1[Inbound Interfaces]
    end
    
    subgraph "Application Core"
        AC[Domain<br/>Business Logic]
    end
    
    subgraph "Ports"
        P2[Outbound Interfaces]
    end
    
    subgraph "Secondary Adapters"
        S1[Database]
        S2[Email Service]
        S3[External APIs]
    end
    
    A1 --> P1
    A2 --> P1
    A3 --> P1
    P1 --> AC
    AC --> P2
    P2 --> S1
    P2 --> S2
    P2 --> S3
    
    style AC fill:#ffeb3b
    style P1 fill:#e3f2fd
    style P2 fill:#e3f2fd

Dependency Flow

flowchart LR
    subgraph "Outside World"
        UI[User Interface]
        API[API Clients]
        DB[(Database)]
        EXT[External Services]
    end
    
    subgraph "Adapters"
        PA[Primary Adapters]
        SA[Secondary Adapters]
    end
    
    subgraph "Ports"
        IP[Input Ports]
        OP[Output Ports]
    end
    
    subgraph "Core"
        BL[Business Logic]
    end
    
    UI --> PA
    API --> PA
    PA --> IP
    IP --> BL
    BL --> OP
    OP --> SA
    SA --> DB
    SA --> EXT
    
    style BL fill:#ffeb3b
    style IP fill:#c8e6c9
    style OP fill:#c8e6c9

Testing Strategy

graph TD
    A[Testing Strategy] --> B[Unit Tests]
    A --> C[Integration Tests]
    A --> D[Acceptance Tests]
    
    B --> B1[Domain Logic<br/>✓ Fast ✓ Isolated]
    B --> B2[Use Cases<br/>✓ Mock Adapters]
    
    C --> C1[Adapter Tests<br/>✓ Real Infrastructure]
    C --> C2[Port Tests<br/>✓ Contract Validation]
    
    D --> D1[End-to-End<br/>✓ Full System]
    
    style B1 fill:#c8e6c9
    style B2 fill:#c8e6c9
    style C1 fill:#fff3e0
    style C2 fill:#fff3e0
    style D1 fill:#ffcdd2

Implementation Guidelines

1
2
3
4
5
6
7
8
9
10
11
// Core Contracts - Keep Minimal
public interface IProductRepository
{
    Task<Product> GetByIdAsync(int id);
    Task<Product> SaveAsync(Product product);
}

public interface IProductService
{
    Task<ProductDto> CreateProductAsync(CreateProductRequest request);
}

Best for: Applications requiring high testability, technology independence, or complex business logic.

Benefits: Technology agnostic, highly testable, clean dependencies.

Challenges: Initial complexity, learning curve, potential over-engineering for simple applications.

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Port - Interface defined by business needs
public interface IProductRepository
{
    Task<Product> GetByIdAsync(int id);
    Task<IEnumerable<Product>> GetAllAsync();
    Task<Product> SaveAsync(Product product);
}

// Application Service - Orchestrates business logic
public class ProductService
{
    private readonly IProductRepository _productRepository;
    private readonly INotificationService _notificationService;
    
    public ProductService(IProductRepository productRepository, 
                         INotificationService notificationService)
    {
        _productRepository = productRepository;
        _notificationService = notificationService;
    }
    
    public async Task<Product> CreateProductAsync(string name, decimal price)
    {
        var product = new Product(name, price);
        var savedProduct = await _productRepository.SaveAsync(product);
        
        await _notificationService.SendProductCreatedNotificationAsync(savedProduct);
        
        return savedProduct;
    }
}

When to use: Complex business logic applications, systems requiring high testability, applications with multiple interfaces, or systems with frequently changing external dependencies.

Advantages include: Technology independence, high testability, and maintainability.

Disadvantages include: Increased complexity and potential over-engineering for simple applications.

Onion Architecture

Onion Architecture builds upon Hexagonal principles with explicit concentric layers, placing domain logic at the center and ensuring dependencies flow inward. This pattern excels for enterprise applications with complex business rules.

Concentric Layer Structure

graph TB
    subgraph "Infrastructure Layer"
        I1[Data Access]
        I2[External Services]
        I3[File System]
    end
    
    subgraph "Presentation Layer"
        P1[Web API]
        P2[MVC]
        P3[Blazor]
    end
    
    subgraph "Application Layer"
        A1[Application Services]
        A2[Use Cases]
        A3[DTOs]
    end
    
    subgraph "Domain Layer"
        D1[Entities]
        D2[Domain Services]
        D3[Domain Events]
    end
    
    P1 -.-> A1
    P2 -.-> A1
    P3 -.-> A1
    A1 --> D1
    A2 --> D1
    I1 -.-> A1
    I2 -.-> A1
    I3 -.-> A1
    
    style D1 fill:#ffeb3b
    style D2 fill:#ffeb3b
    style D3 fill:#ffeb3b
    style A1 fill:#e3f2fd
    style A2 fill:#e3f2fd
    style A3 fill:#e3f2fd

Dependency Flow Rules

flowchart TD
    A[Onion Architecture Rules] --> B[Domain Layer]
    A --> C[Application Layer]
    A --> D[Infrastructure Layer]
    A --> E[Presentation Layer]
    
    B --> B1[✓ No Dependencies<br/>✓ Pure Business Logic<br/>✓ Domain Events]
    
    C --> C1[✓ Depends on Domain Only<br/>✓ Use Cases & Services<br/>✓ Interface Definitions]
    
    D --> D1[✓ Implements Interfaces<br/>✓ External Concerns<br/>✓ Data Access & APIs]
    
    E --> E1[✓ Depends on Application<br/>✓ User Interface<br/>✓ Controllers & Views]
    
    style B1 fill:#ffeb3b
    style C1 fill:#e3f2fd
    style D1 fill:#f3e5f5
    style E1 fill:#e8f5e8

Use Case Flow

sequenceDiagram
    participant UI as Presentation
    participant App as Application
    participant Dom as Domain
    participant Inf as Infrastructure
    
    UI->>App: Execute Use Case
    App->>Dom: Business Logic Call
    Dom->>Dom: Apply Business Rules
    Dom->>App: Domain Events
    App->>Inf: Persist Changes
    Inf-->>App: Confirmation
    App-->>UI: Response DTO
    
    Note over Dom: Core business logic<br/>remains isolated

Project Structure Guidelines

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Property.Management.Domain/
├── Entities/
├── ValueObjects/
├── DomainServices/
└── Events/

Property.Management.Application/
├── UseCases/
├── Services/
├── DTOs/
└── Interfaces/

Property.Management.Infrastructure/
├── Persistence/
├── ExternalServices/
└── Configuration/

Property.Management.Presentation/
├── Controllers/
├── Models/
└── Views/

Best for: Enterprise applications with complex business rules, Domain-Driven Design implementations, long-term maintainable systems.

Benefits: Clear separation of concerns, testable architecture, business logic isolation.

Challenges: Initial setup complexity, learning curve for teams new to DDD.

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// Domain Layer - Rich domain model
public class Order
{
    private readonly List<OrderItem> _items = new();
    
    public int Id { get; private set; }
    public string CustomerName { get; private set; }
    public DateTime OrderDate { get; private set; }
    public OrderStatus Status { get; private set; }
    public decimal TotalAmount => _items.Sum(i => i.Price * i.Quantity);
    
    public Order(string customerName)
    {
        OrderDate = DateTime.UtcNow;
        Status = OrderStatus.Draft;
    }
    
    public void AddItem(string productName, decimal price, int quantity)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Cannot modify confirmed order");
            
        _items.Add(new OrderItem(productName, price, quantity));
    }
    
    public void ConfirmOrder()
    {
        if (!_items.Any())
            throw new InvalidOperationException("Cannot confirm empty order");
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Order already confirmed");
            
        Status = OrderStatus.Confirmed;
    }
}

// Application Layer - Use cases
public class OrderService : IOrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly IEmailService _emailService;
    
    public OrderService(IOrderRepository orderRepository, IEmailService emailService)
    {
        _orderRepository = orderRepository;
        _emailService = emailService;
    }
    
    public async Task<OrderDto> CreateOrderAsync(CreateOrderRequest request)
    {
        var order = new Order(request.CustomerName);
        var savedOrder = await _orderRepository.SaveAsync(order);
        
        return new OrderDto
        {
            Id = savedOrder.Id,
            CustomerName = savedOrder.CustomerName,
            OrderDate = savedOrder.OrderDate,
            Status = savedOrder.Status.ToString(),
            TotalAmount = savedOrder.TotalAmount
        };
    }
    
    public async Task<OrderDto> ConfirmOrderAsync(int orderId)
    {
        var order = await _orderRepository.GetByIdAsync(orderId);
        if (order == null)
            throw new ArgumentException("Order not found");
            
        order.ConfirmOrder();
        var confirmedOrder = await _orderRepository.SaveAsync(order);
        
        await _emailService.SendOrderConfirmationAsync(confirmedOrder);
        
        return MapToDto(confirmedOrder);
    }
}

When to use: Enterprise applications with complex business rules, Domain-Driven Design implementations, or long-term software projects requiring high maintainability.

Advantages include: Domain-centric design, high testability, and clear structure.

Disadvantages include: Complexity and potential over-engineering for simple applications.

Advanced Communication Patterns

CQRS (Command Query Responsibility Segregation)

Command Query Responsibility Segregation (CQRS) separates read and write operations, allowing independent optimization of each concern. This pattern enables different models for reading and writing data.

Architecture Overview

graph LR
    subgraph "Command Side"
        C1[Commands] --> CH[Command Handlers]
        CH --> WDB[(Write Database)]
    end
    
    subgraph "Query Side"
        Q1[Queries] --> QH[Query Handlers]
        QH --> RDB[(Read Database)]
    end
    
    subgraph "Event Bus"
        EB[Domain Events]
    end
    
    CH --> EB
    EB --> RS[Read Store Updater]
    RS --> RDB
    
    style C1 fill:#ffcdd2
    style Q1 fill:#c8e6c9
    style EB fill:#fff3e0

Decision Flow for CQRS

flowchart TD
    A[Consider CQRS] --> B{Read/Write Patterns Different?}
    B -->|No| C[Use Simple Repository]
    B -->|Yes| D{Scalability Requirements?}
    
    D -->|Low| E[Simple CQRS<br/>Same Database]
    D -->|High| F{Consistency Requirements?}
    
    F -->|Eventual OK| G[CQRS + Event Sourcing]
    F -->|Strong Required| H[CQRS + Saga Pattern]
    
    E --> I[MediatR Implementation]
    G --> J[Separate Read/Write Stores]
    H --> K[Distributed Transactions]
    
    style C fill:#ffcdd2
    style I fill:#c8e6c9
    style J fill:#c8e6c9
    style K fill:#fff3e0

Message Flow Patterns

sequenceDiagram
    participant C as Client
    participant API as API Gateway
    participant CH as Command Handler
    participant WS as Write Store
    participant EB as Event Bus
    participant QH as Query Handler
    participant RS as Read Store
    
    Note over C,RS: Command Flow
    C->>API: Create Order Command
    API->>CH: Process Command
    CH->>WS: Store Write Model
    CH->>EB: Publish Domain Event
    EB->>QH: Update Read Model
    QH->>RS: Store Read Model
    
    Note over C,RS: Query Flow
    C->>API: Get Order Query
    API->>QH: Process Query
    QH->>RS: Retrieve Read Model
    RS-->>QH: Read Model Data
    QH-->>API: Query Result
    API-->>C: Response

Implementation Strategy

graph TD
    A[CQRS Implementation] --> B[Phase 1: Separate Handlers]
    A --> C[Phase 2: Separate Models]
    A --> D[Phase 3: Separate Stores]
    
    B --> B1[✓ MediatR Setup<br/>✓ Command/Query Handlers<br/>✓ Same Database]
    
    C --> C1[✓ Write Models<br/>✓ Read Models<br/>✓ Event Publishing]
    
    D --> D1[✓ Write Database<br/>✓ Read Database<br/>✓ Event Sourcing]
    
    style B1 fill:#c8e6c9
    style C1 fill:#fff3e0
    style D1 fill:#ffcdd2

Core Contracts

1
2
3
4
5
6
7
8
9
// Keep contracts minimal and focused
public interface ICommand<TResult> : IRequest<TResult> { }
public interface IQuery<TResult> : IRequest<TResult> { }

// Example command
public record CreateOrderCommand(string CustomerId, List<OrderItem> Items) : ICommand<int>;

// Example query  
public record GetOrderQuery(int OrderId) : IQuery<OrderDto>;

Best for: Applications with different read/write patterns, high-scale systems, complex reporting requirements.

Benefits: Independent scaling, optimized data models, clear separation.

Challenges: Increased complexity, eventual consistency, debugging across models.

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Controllers using MediatR
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IMediator _mediator;
    
    public ProductsController(IMediator mediator)
    {
        _mediator = mediator;
    }
    
    [HttpPost]
    public async Task<IActionResult> CreateProduct([FromBody] CreateProductCommand command)
    {
        var productId = await _mediator.Send(command);
        return CreatedAtAction(nameof(GetProduct), new { id = productId }, productId);
    }
    
    [HttpGet("{id}")]
    public async Task<IActionResult> GetProduct(int id)
    {
        var product = await _mediator.Send(new GetProductByIdQuery(id));
        return Ok(product);
    }
}

CQRS Benefits: Independent scaling of read and write operations, optimized data models for specific use cases, and reduced contention.

Challenges include: Increased complexity, eventual consistency, and additional infrastructure requirements.

Event-Driven Architecture

Event-Driven Architecture enables loose coupling between components through asynchronous message passing, allowing systems to scale independently and react to business events in real-time.

Event Flow Architecture

graph TB
    subgraph "Event Producers"
        EP1[Order Service]
        EP2[Payment Service]
        EP3[Inventory Service]
    end
    
    subgraph "Event Infrastructure"
        EB[Event Bus<br/>Azure Service Bus / RabbitMQ]
    end
    
    subgraph "Event Consumers"
        EC1[Email Service]
        EC2[Analytics Service]
        EC3[Audit Service]
    end
    
    EP1 --> EB
    EP2 --> EB
    EP3 --> EB
    
    EB --> EC1
    EB --> EC2
    EB --> EC3
    
    style EB fill:#ffeb3b
    style EP1 fill:#ffcdd2
    style EP2 fill:#ffcdd2
    style EP3 fill:#ffcdd2
    style EC1 fill:#c8e6c9
    style EC2 fill:#c8e6c9
    style EC3 fill:#c8e6c9

Event Processing Patterns

flowchart TD
    A[Event Processing Strategy] --> B[Choreography]
    A --> C[Orchestration]
    
    B --> B1[✓ Distributed Logic<br/>✓ Services React to Events<br/>✓ No Central Coordinator]
    
    C --> C1[✓ Central Coordinator<br/>✓ Workflow Management<br/>✓ Explicit Process Control]
    
    B1 --> B2[Pro: Loose Coupling<br/>Con: Hard to Debug]
    C1 --> C2[Pro: Clear Flow<br/>Con: Central Point of Failure]
    
    style B1 fill:#c8e6c9
    style C1 fill:#fff3e0

Event Consistency Patterns

stateDiagram-v2
    [*] --> EventPublished
    EventPublished --> Processing
    Processing --> Success: All Handlers Complete
    Processing --> PartialFailure: Some Handlers Fail
    Processing --> TotalFailure: Critical Handler Fails
    
    PartialFailure --> Retry: Retry Failed Handlers
    TotalFailure --> Compensate: Execute Compensation
    
    Retry --> Success: Retry Successful
    Retry --> Dead Letter: Max Retries Exceeded
    
    Success --> [*]
    Compensate --> [*]
    Dead Letter --> [*]

Implementation Approach

1
2
3
4
5
6
7
8
9
10
11
12
// Minimal event contract
public record OrderCreatedEvent(
    int OrderId,
    string CustomerId,
    decimal Amount,
    DateTime CreatedAt);

// Event handler interface
public interface IEventHandler<in TEvent>
{
    Task HandleAsync(TEvent eventData);
}

Best for: Microservices communication, real-time systems, scalable architectures.

Benefits: Loose coupling, scalability, resilience.

Challenges: Debugging complexity, eventual consistency, monitoring requirements.

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Command Handler using MediatR
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, int>
{
    private readonly IOrderRepository _orderRepository;
    private readonly IMediator _mediator;
    
    public async Task<int> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
    {
        var order = new Order(request.CustomerId, request.Items);
        await _orderRepository.SaveAsync(order);
        
        // Publish domain event
        await _mediator.Publish(new OrderCreatedEvent
        {
            OrderId = order.Id,
            CustomerId = request.CustomerId,
            Items = request.Items,
            TotalAmount = order.TotalAmount
        }, cancellationToken);
        
        return order.Id;
    }
}
    }
}

// Event Handlers
public class InventoryEventHandler : INotificationHandler<OrderCreatedEvent>
{
    private readonly IInventoryService _inventoryService;
    
    public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
    {
        foreach (var item in notification.Items)
        {
            await _inventoryService.ReserveInventoryAsync(item.ProductId, item.Quantity);
        }
    }
}

public class EmailNotificationHandler : INotificationHandler<OrderCreatedEvent>
{
    private readonly IEmailService _emailService;
    
    public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
    {
        await _emailService.SendOrderConfirmationAsync(
            notification.CustomerId,
            notification.OrderId);
    }
}

When to use: E-commerce platforms with order processing workflows, IoT applications processing sensor data, real-time analytics systems, or microservices communication.

Advantages include: Loose coupling, fault tolerance, and horizontal scalability.

Disadvantages include: Debugging complexity, message ordering, and monitoring requirements.

Saga Pattern

The Saga Pattern manages distributed transactions across multiple microservices by coordinating a sequence of local transactions with compensating actions for rollback scenarios.

stateDiagram-v2
    [*] --> Started
    Started --> PaymentProcessed: Process Payment
    PaymentProcessed --> InventoryReserved: Reserve Inventory
    InventoryReserved --> OrderConfirmed: Confirm Order
    OrderConfirmed --> [*]
    
    PaymentProcessed --> Compensating: Payment Failed
    InventoryReserved --> Compensating: Inventory Failed
    Compensating --> Failed: Rollback Complete
    Failed --> [*]
    
    Compensating: Execute compensating transactions in reverse order

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// Saga State Machine
public class OrderProcessingSaga
{
    public string OrderId { get; set; }
    public string CustomerId { get; set; }
    public decimal Amount { get; set; }
    public SagaState State { get; set; }
    public List<string> CompletedSteps { get; set; } = new();
    
    public enum SagaState
    {
        Started,
        PaymentProcessed,
        InventoryReserved,
        OrderConfirmed,
        Failed,
        Compensating
    }
}

// Saga Orchestrator
public class OrderSagaOrchestrator
{
    private readonly IMediator _mediator;
    private readonly ISagaRepository _sagaRepository;
    
    public async Task HandleOrderCreatedAsync(OrderCreatedEvent orderCreated)
    {
        var saga = new OrderProcessingSaga
        {
            OrderId = orderCreated.OrderId.ToString(),
            CustomerId = orderCreated.CustomerId,
            Amount = orderCreated.TotalAmount,
            State = OrderProcessingSaga.SagaState.Started
        };
        
        await _sagaRepository.SaveAsync(saga);
        
        // Start the saga by processing payment
        await _mediator.Send(new ProcessPaymentCommand(
            saga.OrderId,
            saga.CustomerId,
            saga.Amount));
    }
    
    public async Task HandlePaymentProcessedAsync(PaymentProcessedEvent paymentProcessed)
    {
        var saga = await _sagaRepository.GetByOrderIdAsync(paymentProcessed.OrderId);
        saga.State = OrderProcessingSaga.SagaState.PaymentProcessed;
        saga.CompletedSteps.Add("Payment");
        
        await _sagaRepository.UpdateAsync(saga);
        
        // Next step: Reserve inventory
        await _mediator.Send(new ReserveInventoryCommand(
            saga.OrderId,
            paymentProcessed.Items));
    }
    
    public async Task HandleStepFailedAsync(SagaStepFailedEvent stepFailed)
    {
        var saga = await _sagaRepository.GetByOrderIdAsync(stepFailed.OrderId);
        saga.State = OrderProcessingSaga.SagaState.Compensating;
        
        await _sagaRepository.UpdateAsync(saga);
        
        // Execute compensating transactions in reverse order
        await ExecuteCompensatingTransactionsAsync(saga);
    }
    
    private async Task ExecuteCompensatingTransactionsAsync(OrderProcessingSaga saga)
    {
        var completedSteps = saga.CompletedSteps.AsEnumerable().Reverse();
        
        foreach (var step in completedSteps)
        {
            switch (step)
            {
                case "Inventory":
                    await _mediator.Send(new ReleaseInventoryCommand(saga.OrderId));
                    break;
                case "Payment":
                    await _mediator.Send(new RefundPaymentCommand(saga.OrderId, saga.Amount));
                    break;
            }
        }
        
        saga.State = OrderProcessingSaga.SagaState.Failed;
        await _sagaRepository.UpdateAsync(saga);
    }
}

Saga Pattern Benefits: Data consistency without distributed transactions, better error handling, and improved system resilience.

Challenges include: Increased complexity, debugging difficulties, and compensation logic complexity.

Service Integration Patterns

API Gateway

The API Gateway Pattern provides a single entry point for clients to access multiple backend services, commonly implemented using Azure API Management or Azure Application Gateway.

graph TB
    C1[Mobile Client] --> GW[API Gateway]
    C2[Web Client] --> GW
    C3[Third Party] --> GW
    
    GW --> S1[Auth Service]
    GW --> S2[Product Service]
    GW --> S3[Order Service]
    GW --> S4[Payment Service]
    
    subgraph "Cross-cutting Concerns"
        CC1[Rate Limiting]
        CC2[Authentication]
        CC3[Caching]
        CC4[Monitoring]
    end
    
    GW -.-> CC1
    GW -.-> CC2
    GW -.-> CC3
    GW -.-> CC4
    
    style GW fill:#ffeb3b
    style C1 fill:#e3f2fd
    style C2 fill:#e3f2fd
    style C3 fill:#e3f2fd

Azure API Management Configuration

1
2
3
4
5
6
7
8
9
10
11
// Rate limiting policy
@{
    return context.Request.IpAddress == "192.168.1.1" ? 1000 : 100;
}

// Route based on User-Agent
@{
    return context.Request.Headers.GetValueOrDefault("User-Agent", "").Contains("Mobile") 
        ? "mobile-backend" 
        : "web-backend";
}

API Gateway Features: Layer 7 routing, authentication and authorization, rate limiting, request/response transformation, caching, and monitoring integration. Azure API Management offers different tiers including Consumption (serverless), Developer, Basic, Standard, and Premium, each with varying features and pricing models.

When to use: E-commerce platforms routing requests to catalog, inventory, and payment services; multi-tenant applications requiring tenant-based routing; or API versioning scenarios.

Advantages include: Reduced latency through caching, cost efficiency, and centralized infrastructure management.

Disadvantages include: Potential single point of failure and performance bottlenecks.

Backend for Frontend (BFF)

The Backend for Frontend (BFF) Pattern creates dedicated backend services for specific frontend applications or interfaces, optimizing each backend for particular client needs rather than using a single general-purpose API.

graph TB
    subgraph "Clients"
        M[Mobile App]
        W[Web App]
        T[Third Party API]
    end
    
    subgraph "BFF Layer"
        MBFF[Mobile BFF]
        WBFF[Web BFF]
        TBFF[Partner API BFF]
    end
    
    subgraph "Backend Services"
        PS[Product Service]
        OS[Order Service]
        US[User Service]
        NS[Notification Service]
    end
    
    M --> MBFF
    W --> WBFF
    T --> TBFF
    
    MBFF --> PS
    MBFF --> OS
    MBFF --> NS
    
    WBFF --> PS
    WBFF --> OS
    WBFF --> US
    
    TBFF --> PS
    TBFF --> OS
    
    style MBFF fill:#ffcdd2
    style WBFF fill:#c8e6c9
    style TBFF fill:#fff3e0

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// Mobile BFF Service
[ApiController]
[Route("api/mobile/[controller]")]
public class MobileProductController : ControllerBase
{
    private readonly IProductService _productService;
    private readonly IImageService _imageService;
    
    [HttpGet("{id}")]
    public async Task<MobileProductDto> GetProduct(int id)
    {
        var product = await _productService.GetByIdAsync(id);
        
        // Mobile-specific optimization: smaller images, essential data only
        return new MobileProductDto
        {
            Id = product.Id,
            Name = product.Name,
            Price = product.Price,
            ThumbnailUrl = await _imageService.GetThumbnailAsync(product.ImageUrl, "mobile"),
            Rating = product.AverageRating,
            InStock = product.StockLevel > 0
        };
    }
}

// Web BFF Service
[ApiController]
[Route("api/web/[controller]")]
public class WebProductController : ControllerBase
{
    private readonly IProductService _productService;
    private readonly IReviewService _reviewService;
    
    [HttpGet("{id}")]
    public async Task<WebProductDto> GetProduct(int id)
    {
        var product = await _productService.GetByIdAsync(id);
        var reviews = await _reviewService.GetRecentReviewsAsync(id, 10);
        
        // Web-specific: full data with reviews, recommendations
        return new WebProductDto
        {
            Id = product.Id,
            Name = product.Name,
            Description = product.Description,
            Price = product.Price,
            ImageUrls = product.ImageUrls,
            Specifications = product.Specifications,
            Reviews = reviews,
            StockLevel = product.StockLevel,
            EstimatedDelivery = CalculateDeliveryDate(product)
        };
    }
}

When to use: Multi-platform applications (web, mobile, IoT), different client requirements, or API versioning scenarios.

Advantages include: Optimized data transfer, client-specific optimizations, and independent evolution.

Disadvantages include: Code duplication, increased infrastructure complexity, and additional maintenance overhead.

Serverless Pattern

The Serverless Pattern enables event-driven, scalable applications using Azure Functions with .NET, supporting various hosting models and orchestration patterns.

graph TB
    subgraph "Event Sources"
        HTTP[HTTP Triggers]
        TIMER[Timer Triggers]
        QUEUE[Queue Triggers]
        BLOB[Blob Triggers]
    end
    
    subgraph "Azure Functions"
        F1[Order Processing]
        F2[Image Resizing]
        F3[Email Sending]
        F4[Data Cleanup]
    end
    
    subgraph "Outputs"
        DB[(Database)]
        STORAGE[Blob Storage]
        SB[Service Bus]
        COSMOS[(Cosmos DB)]
    end
    
    HTTP --> F1
    TIMER --> F4
    QUEUE --> F2
    BLOB --> F2
    
    F1 --> DB
    F1 --> SB
    F2 --> STORAGE
    F3 --> SB
    F4 --> DB
    F4 --> COSMOS
    
    style F1 fill:#ffeb3b
    style F2 fill:#ffeb3b
    style F3 fill:#ffeb3b
    style F4 fill:#ffeb3b

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// .NET Isolated Worker Model
[Function("ProcessOrder")]
public async Task<IActionResult> ProcessOrder(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
    [CosmosDBOutput("OrdersDB", "Orders", Connection = "CosmosDB")] IAsyncCollector<Order> orders)
{
    var order = await req.ReadFromJsonAsync<Order>();
    await orders.AddAsync(order);
    
    return new OkObjectResult(new { OrderId = order.Id, Status = "Processing" });
}

// Durable Functions Orchestration
[Function("OrderProcessingOrchestrator")]
public async Task<string> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var order = context.GetInput<Order>();
    
    // Function chaining pattern
    await context.CallActivityAsync("ValidateOrder", order);
    await context.CallActivityAsync("ProcessPayment", order);
    await context.CallActivityAsync("UpdateInventory", order);
    await context.CallActivityAsync("ShipOrder", order);
    
    return "Order processed successfully";
}

Serverless Benefits: Automatic scaling, pay-per-execution pricing, and reduced operational overhead.

Challenges include: Cold starts, execution time limits, and vendor lock-in considerations.

Strangler Fig Pattern

The Strangler Fig Pattern enables incremental modernization of legacy systems by gradually replacing functionality while maintaining operational continuity.

graph TB
    subgraph "Phase 1: Initial State"
        C1[Client] --> LS1[Legacy System]
    end
    
    subgraph "Phase 2: Facade Introduction"
        C2[Client] --> F[Facade/Router]
        F --> LS2[Legacy System]
        F -.-> MS1[Modern Service A]
    end
    
    subgraph "Phase 3: Gradual Migration"
        C3[Client] --> F2[Facade/Router]
        F2 --> LS3[Legacy System]
        F2 --> MS2[Modern Service A]
        F2 --> MS3[Modern Service B]
    end
    
    subgraph "Phase 4: Complete Migration"
        C4[Client] --> MS4[Modern Service A]
        C4 --> MS5[Modern Service B]
        C4 --> MS6[Modern Service C]
    end
    
    style F fill:#ffeb3b
    style F2 fill:#ffeb3b
    style MS1 fill:#c8e6c9
    style MS2 fill:#c8e6c9
    style MS3 fill:#c8e6c9
    style MS4 fill:#c8e6c9
    style MS5 fill:#c8e6c9
    style MS6 fill:#c8e6c9

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Routing Facade Implementation
public class StranglerFacade : IHostedService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly IConfiguration _configuration;
    
    public async Task RouteRequest(HttpContext context)
    {
        var feature = DetermineFeature(context.Request.Path);
        
        if (IsModernizedFeature(feature))
        {
            await RouteToModernService(context, feature);
        }
        else
        {
            await RouteToLegacySystem(context);
        }
    }
    
    private bool IsModernizedFeature(string feature)
    {
        return _configuration.GetValue<bool>($"Features:{feature}:Modernized");
    }
    
    private async Task RouteToModernService(HttpContext context, string feature)
    {
        // Route to new microservice
        var modernServiceUrl = _configuration[$"Services:{feature}:Url"];
        // Implementation details...
    }
    
    private async Task RouteToLegacySystem(HttpContext context)
    {
        // Route to legacy system
        var legacyUrl = _configuration["LegacySystem:BaseUrl"];
        // Implementation details...
    }
}

Strangler Fig Implementation phases: Establish facade, incremental migration, data migration, and legacy retirement.

Advantages include: Reduced risk, continuous operation, and flexible timeline.

Disadvantages include: Complexity of managing two systems and maintaining data consistency.

Resilience and Data Patterns

Circuit Breaker Pattern: Fault Tolerance

The Circuit Breaker Pattern prevents cascading failures by monitoring service health and failing fast when dependencies are unhealthy, like an electrical circuit breaker.

Circuit Breaker State Flow

stateDiagram-v2
    [*] --> Closed
    Closed --> Open: Failure Threshold Exceeded
    Open --> HalfOpen: Timeout Expires
    HalfOpen --> Closed: Test Request Succeeds
    HalfOpen --> Open: Test Request Fails
    
    Closed: Normal Operation<br/>Requests Pass Through<br/>Monitor Failures
    Open: Circuit Tripped<br/>Fail Fast<br/>No Requests Allowed
    HalfOpen: Testing Recovery<br/>Limited Test Requests<br/>Evaluate Health
    
    note right of Closed
        Success Rate > Threshold
        Reset Failure Counter
    end note
    
    note right of Open
        Immediate Rejection
        Resource Protection
        Wait for Recovery
    end note
    
    note right of HalfOpen
        Single Test Request
        Quick Failure Detection
        Gradual Recovery
    end note

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Modern .Net Implementation
services.AddHttpClient<PaymentService>()
    .AddStandardResilienceHandler(options =>
    {
        options.CircuitBreaker.FailureRatio = 0.5;
        options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(10);
        options.CircuitBreaker.MinimumThroughput = 8;
        options.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds(30);
    });

// Custom Circuit Breaker with Polly v8
var circuitBreakerOptions = new CircuitBreakerStrategyOptions
{
    FailureRatio = 0.5,
    SamplingDuration = TimeSpan.FromSeconds(10),
    MinimumThroughput = 8,
    BreakDuration = TimeSpan.FromSeconds(30),
    ShouldHandle = new PredicateBuilder()
        .Handle<HttpRequestException>()
        .Handle<TimeoutRejectedException>()
        .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
};

var resiliencePipeline = new ResiliencePipelineBuilder()
    .AddCircuitBreaker(circuitBreakerOptions)
    .Build();

Circuit Breaker States: Closed (normal operation), Open (circuit tripped), and Half-Open (testing recovery).

Advantages include: Preventing cascading failures, resource conservation, and improved user experience.

Disadvantages include: Configuration complexity and potential false positives.

Outbox Pattern: Reliable Message Delivery

The Outbox Pattern ensures reliable message delivery by storing outbound messages in the same database transaction as business data, then publishing them asynchronously.

Outbox Pattern Flow

sequenceDiagram
    participant App as Application
    participant DB as Database
    participant OT as Outbox Table
    participant BP as Background Processor
    participant MB as Message Bus
    participant CS as Consuming Services
    
    Note over App,CS: Business Transaction with Reliable Messaging
    
    App->>DB: Begin Transaction
    App->>DB: Save Business Data
    App->>OT: Store Outbox Event
    App->>DB: Commit Transaction
    
    Note over BP: Polling Process (every 5s)
    
    BP->>OT: Query Unprocessed Events
    OT-->>BP: Pending Events
    
    loop For Each Event
        BP->>MB: Publish Event
        MB-->>BP: Acknowledge
        BP->>OT: Mark as Processed
    end
    
    MB->>CS: Deliver Event
    CS-->>MB: Acknowledge
    
    Note over App,CS: Guaranteed Delivery:<br/>At-least-once semantics

Outbox Table Structure

erDiagram
    OUTBOX_EVENTS {
        guid Id PK
        string Type
        string Data
        datetime CreatedAt
        boolean IsProcessed
        datetime ProcessedAt
        int RetryCount
        string LastError
    }
    
    BUSINESS_DATA {
        int Id PK
        string Content
        datetime CreatedAt
    }
    
    OUTBOX_EVENTS ||--o{ BUSINESS_DATA : references

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// Outbox Event Entity
public class OutboxEvent
{
    public Guid Id { get; set; }
    public string Type { get; set; }
    public string Data { get; set; }
    public DateTime CreatedAt { get; set; }
    public bool IsProcessed { get; set; }
    public DateTime? ProcessedAt { get; set; }
}

// Service Implementation
public class OrderService
{
    private readonly ApplicationDbContext _context;
    
    public async Task CreateOrderAsync(Order order)
    {
        using var transaction = await _context.Database.BeginTransactionAsync();
        
        try
        {
            // Save business data
            _context.Orders.Add(order);
            
            // Save outbox event
            var outboxEvent = new OutboxEvent
            {
                Id = Guid.NewGuid(),
                Type = "OrderCreated",
                Data = JsonSerializer.Serialize(new OrderCreatedEvent(order.Id)),
                CreatedAt = DateTime.UtcNow,
                IsProcessed = false
            };
            
            _context.OutboxEvents.Add(outboxEvent);
            
            await _context.SaveChangesAsync();
            await transaction.CommitAsync();
        }
        catch
        {
            await transaction.RollbackAsync();
            throw;
        }
    }
}

// Outbox Processor
public class OutboxProcessor : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await ProcessOutboxEventsAsync();
            await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
        }
    }
    
    private async Task ProcessOutboxEventsAsync()
    {
        using var scope = _serviceProvider.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
        var serviceBus = scope.ServiceProvider.GetRequiredService<IServiceBus>();
        
        var pendingEvents = await context.OutboxEvents
            .Where(e => !e.IsProcessed)
            .OrderBy(e => e.CreatedAt)
            .Take(100)
            .ToListAsync();
        
        foreach (var outboxEvent in pendingEvents)
        {
            try
            {
                await serviceBus.PublishAsync(outboxEvent.Type, outboxEvent.Data);
                
                outboxEvent.IsProcessed = true;
                outboxEvent.ProcessedAt = DateTime.UtcNow;
                
                await context.SaveChangesAsync();
            }
            catch (Exception ex)
            {
                // Log error and continue
            }
        }
    }
}

Outbox Pattern Benefits: Guaranteed delivery, data consistency, and resilience to system failures.

Challenges include: Complexity, latency from asynchronous processing, and duplicate message handling requirements.

Modern .Net and Azure Integration

Latest .Net Features

.Net introduces significant performance improvements with Dynamic Profile-Guided Optimization (PGO) enabled by default, providing up to 20% performance gains. Enhanced JIT compilation with On Stack Replacement (OSR), AVX-512 support, and Native AOT improvements deliver faster startup times and reduced memory consumption.

Key scalability features include improved parallelism support, enhanced garbage collection optimized for high-throughput scenarios, and System.Text.Json improvements with source generators to avoid reflection overhead.

Azure Container Apps and Dapr Integration

Azure Container Apps provides fully managed Dapr integration with built-in support for Dapr runtime APIs, serverless container orchestration, and automatic scaling from zero to thousands of instances.

Azure Integration Architecture

graph TB
    subgraph "Azure Cloud Services"
        subgraph "Container Apps Environment"
            CA1[Order Service<br/>Container App]
            CA2[Payment Service<br/>Container App]
            CA3[Inventory Service<br/>Container App]
        end
        
        subgraph "Dapr Runtime"
            D1[Service Discovery]
            D2[State Management]
            D3[Pub/Sub]
            D4[Secret Management]
        end
        
        subgraph "Azure Infrastructure"
            ASB[Azure Service Bus<br/>Message Broker]
            ACR[Azure Container Registry<br/>Image Storage]
            KV[Azure Key Vault<br/>Secret Storage]
            COSMOS[Cosmos DB<br/>State Store]
            AI[Application Insights<br/>Monitoring]
        end
    end
    
    subgraph "External Systems"
        EXT[External APIs]
        LEGACY[Legacy Systems]
    end
    
    CA1 --> D1
    CA2 --> D2
    CA3 --> D3
    
    D1 -.-> ASB
    D2 -.-> COSMOS
    D3 -.-> ASB
    D4 -.-> KV
    
    CA1 --> AI
    CA2 --> AI
    CA3 --> AI
    
    CA1 -.-> EXT
    CA2 -.-> LEGACY
    
    style CA1 fill:#e3f2fd
    style CA2 fill:#c8e6c9
    style CA3 fill:#fff3e0
    style ASB fill:#ffeb3b
    style COSMOS fill:#ffeb3b
    style KV fill:#ffeb3b

.NET Aspire Integration Flow

sequenceDiagram
    participant Dev as Developer
    participant Aspire as .NET Aspire
    participant Docker as Docker
    participant ACA as Azure Container Apps
    participant Dapr as Dapr Runtime
    participant Azure as Azure Services
    
    Dev->>Aspire: Define Distributed App
    Aspire->>Docker: Generate Containers
    Docker->>ACA: Deploy to Container Apps
    ACA->>Dapr: Initialize Dapr Sidecar
    Dapr->>Azure: Connect to Azure Services
    
    Note over Dev,Azure: Simplified Cloud-Native Development
    
    Azure-->>Dapr: Service Responses
    Dapr-->>ACA: Process Results
    ACA-->>Aspire: Runtime Telemetry
    Aspire-->>Dev: Monitoring Dashboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// .NET Aspire Integration
var builder = DistributedApplication.CreateBuilder(args);

var catalog = builder.AddProject<Projects.Catalog_API>("catalog")
    .WithDaprSidecar();

var ordering = builder.AddProject<Projects.Ordering_API>("ordering")
    .WithDaprSidecar();

var gateway = builder.AddProject<Projects.Gateway>("gateway")
    .WithReference(catalog)
    .WithReference(ordering);

builder.Build().Run();

.NET Aspire provides opinionated tooling for building observable, production-ready distributed applications with 40+ pre-built integrations and built-in telemetry capabilities.

Deployment and Infrastructure Patterns

Sidecar Pattern: Auxiliary Service Deployment

The Sidecar Pattern deploys auxiliary services alongside main applications, providing cross-cutting functionality without modifying the core application logic.

Sidecar Architecture Overview

graph TB
    subgraph "Pod/Container Group"
        subgraph "Main Application"
            MA[Order Service<br/>.Net API]
        end
        
        subgraph "Sidecar Services"
            LS[Logging Sidecar<br/>Fluentd]
            MS[Monitoring Sidecar<br/>Prometheus Exporter]
            SS[Security Sidecar<br/>Service Mesh Proxy]
            CS[Configuration Sidecar<br/>Config Sync]
        end
        
        SV[Shared Volumes<br/>Logs, Config, Temp]
    end
    
    subgraph "External Services"
        ELK[ELK Stack<br/>Log Aggregation]
        PROM[Prometheus<br/>Metrics Collection]
        VAULT[HashiCorp Vault<br/>Secret Management]
        CONSUL[Consul<br/>Service Discovery]
    end
    
    MA -.-> SV
    LS -.-> SV
    MS -.-> SV
    SS -.-> SV
    CS -.-> SV
    
    LS --> ELK
    MS --> PROM
    SS --> VAULT
    CS --> CONSUL
    
    style MA fill:#ffeb3b
    style LS fill:#c8e6c9
    style MS fill:#e3f2fd
    style SS fill:#ffcdd2
    style CS fill:#fff3e0

Sidecar Communication Patterns

sequenceDiagram
    participant C as Client
    participant P as Proxy Sidecar
    participant M as Main App
    participant L as Logging Sidecar
    participant Mon as Monitoring Sidecar
    participant E as External Services
    
    C->>P: HTTP Request
    P->>P: Apply Security Policies
    P->>M: Forward Request
    P->>Mon: Record Metrics
    
    M->>M: Process Business Logic
    M->>L: Write Application Logs
    M->>P: Return Response
    
    P->>Mon: Record Response Metrics
    P->>C: Forward Response
    
    L->>E: Ship Logs to ELK
    Mon->>E: Send Metrics to Prometheus

Sidecar vs Traditional Architecture

graph LR
    subgraph "Traditional Monolithic"
        T1[Application + Infrastructure Code<br/>Tightly Coupled]
        T1 --> T2[Cross-cutting Concerns<br/>Mixed with Business Logic]
        T2 --> T3[Technology Lock-in<br/>Hard to Change]
    end
    
    subgraph "Sidecar Pattern"
        S1[Clean Application<br/>Business Logic Only]
        S2[Infrastructure Sidecars<br/>Loosely Coupled]
        S1 -.-> S2
        S2 --> S3[Technology Independence<br/>Easy to Swap]
    end
    
    style T3 fill:#ffcdd2
    style S3 fill:#c8e6c9

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Main Application Service
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
    private readonly IOrderService _orderService;
    
    [HttpPost]
    public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
    {
        // Main business logic - sidecar handles logging, monitoring automatically
        var order = await _orderService.CreateAsync(request);
        return Ok(order);
    }
}

// Sidecar Configuration (docker-compose.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
version: '3.8'
services:
  order-service:
    image: myapp/order-service:latest
    ports:
      - "8080:80"
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
    
  # Logging Sidecar
  fluentd-sidecar:
    image: fluent/fluentd:latest
    volumes:
      - ./fluentd.conf:/fluentd/etc/fluent.conf
      - order-logs:/var/log/orders
    depends_on:
      - order-service
  
  # Monitoring Sidecar
  prometheus-exporter:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

Sidecar Implementation with .NET:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Health Check Sidecar
public class HealthCheckSidecar : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<HealthCheckSidecar> _logger;
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = _serviceProvider.CreateScope();
            var healthCheckService = scope.ServiceProvider.GetRequiredService<HealthCheckService>();
            
            var result = await healthCheckService.CheckHealthAsync(stoppingToken);
            
            // Report health status to external monitoring
            await ReportHealthStatusAsync(result);
            
            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
        }
    }
}

When to use: Cross-cutting concerns like logging and monitoring, legacy application enhancement, or microservices requiring common functionality.

Advantages include: Language-agnostic implementation, isolated functionality, and simplified main application.

Disadvantages include: Increased resource consumption and orchestration complexity.

Ambassador Pattern: Proxy-Based Service Enhancement

The Ambassador Pattern creates helper services that send network requests on behalf of consumer applications, acting as an out-of-process proxy for enhanced connectivity.

Ambassador Pattern Architecture

graph TB
    subgraph "Application Cluster"
        subgraph "Consumer Application"
            CA[Order Service<br/>Main Business Logic]
        end
        
        subgraph "Ambassador Services"
            PA[Payment Ambassador<br/>Enhanced Connectivity]
            IA[Inventory Ambassador<br/>Retry & Circuit Breaker]
            NA[Notification Ambassador<br/>Rate Limiting]
        end
    end
    
    subgraph "External Services"
        PS[Payment Service<br/>Third-party API]
        IS[Inventory Service<br/>Legacy System]
        NS[Notification Service<br/>Email Provider]
    end
    
    CA --> PA
    CA --> IA
    CA --> NA
    
    PA --> PS
    IA --> IS
    NA --> NS
    
    style CA fill:#ffeb3b
    style PA fill:#c8e6c9
    style IA fill:#e3f2fd
    style NA fill:#fff3e0

Ambassador Request Flow

sequenceDiagram
    participant App as Main Application
    participant Amb as Ambassador
    participant Auth as Auth Service
    participant Ext as External Service
    participant Cache as Cache
    participant Log as Logging
    
    App->>Amb: Process Payment Request
    
    Note over Amb: Ambassador Handles All Complexity
    
    Amb->>Cache: Check Cached Token
    Cache-->>Amb: Token Expired
    
    Amb->>Auth: Acquire Access Token
    Auth-->>Amb: New Token
    
    Amb->>Cache: Cache Token
    
    Amb->>Log: Log Request Start
    
    Amb->>Ext: Payment API Call (with retry logic)
    
    alt Success
        Ext-->>Amb: Payment Success
        Amb->>Log: Log Success
        Amb-->>App: Payment Response
    else Failure
        Ext-->>Amb: Payment Failed
        Amb->>Amb: Apply Circuit Breaker
        Amb->>Log: Log Failure
        Amb-->>App: Fallback Response
    end

Ambassador vs Direct Integration

graph LR
    subgraph "Direct Integration"
        D1[Application Code<br/>+ Auth Logic<br/>+ Retry Logic<br/>+ Monitoring<br/>+ Circuit Breaker]
        D1 --> D2[Complex Codebase<br/>Hard to Test<br/>Technology Coupling]
    end
    
    subgraph "Ambassador Pattern"
        A1[Clean Application<br/>Business Logic Only]
        A2[Ambassador Service<br/>Connectivity Logic]
        A1 --> A2
        A2 --> A3[Specialized Teams<br/>Easy Testing<br/>Technology Abstraction]
    end
    
    style D2 fill:#ffcdd2
    style A3 fill:#c8e6c9

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// Ambassador Service
public class PaymentAmbassador
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<PaymentAmbassador> _logger;
    private readonly CircuitBreakerPolicy _circuitBreaker;
    
    public PaymentAmbassador(HttpClient httpClient, ILogger<PaymentAmbassador> logger)
    {
        _httpClient = httpClient;
        _logger = logger;
        _circuitBreaker = Policy
            .Handle<HttpRequestException>()
            .CircuitBreakerAsync(
                exceptionsAllowedBeforeBreaking: 3,
                durationOfBreak: TimeSpan.FromSeconds(30));
    }
    
    public async Task<PaymentResponse> ProcessPaymentAsync(PaymentRequest request)
    {
        return await _circuitBreaker.ExecuteAsync(async () =>
        {
            _logger.LogInformation("Processing payment for Order {OrderId}", request.OrderId);
            
            // Add authentication headers
            _httpClient.DefaultRequestHeaders.Authorization = 
                new AuthenticationHeaderValue("Bearer", await GetAccessTokenAsync());
            
            // Add tracing headers
            _httpClient.DefaultRequestHeaders.Add("X-Trace-Id", Activity.Current?.Id);
            
            var response = await _httpClient.PostAsJsonAsync("/api/payments", request);
            response.EnsureSuccessStatusCode();
            
            var result = await response.Content.ReadFromJsonAsync<PaymentResponse>();
            
            _logger.LogInformation("Payment processed successfully for Order {OrderId}", request.OrderId);
            
            return result;
        });
    }
    
    private async Task<string> GetAccessTokenAsync()
    {
        // Token acquisition logic
        return await GetCachedTokenAsync();
    }
}

// Main Application using Ambassador
public class OrderService
{
    private readonly PaymentAmbassador _paymentAmbassador;
    
    public async Task<Order> ProcessOrderAsync(CreateOrderRequest request)
    {
        var order = new Order(request);
        
        // Ambassador handles all payment complexity
        var paymentResult = await _paymentAmbassador.ProcessPaymentAsync(
            new PaymentRequest(order.Id, order.TotalAmount));
        
        if (paymentResult.IsSuccessful)
        {
            order.MarkAsPaid();
        }
        
        return order;
    }
}

When to use: Legacy application enhancement, common connectivity patterns, or specialized network requirements.

Advantages include: Specialized team implementation, technology abstraction, and enhanced capabilities.

Disadvantages include: Additional network latency and complexity in failure handling.

Bulkhead Pattern: Resource Isolation for Resilience

The Bulkhead Pattern isolates resources into separate pools to prevent failure in one component from affecting the entire system, similar to watertight compartments in a ship.

Resource Isolation Architecture

graph TB
    subgraph "Application Layer"
        AC[API Controller]
    end
    
    subgraph "Bulkhead Service Pools"
        subgraph "Critical Services Pool"
            CP1[Thread Pool: 10]
            CP2[Connection Pool: 5]
            CP3[Memory Pool: 2GB]
        end
        
        subgraph "Standard Services Pool"
            SP1[Thread Pool: 5]
            SP2[Connection Pool: 3]
            SP3[Memory Pool: 1GB]
        end
        
        subgraph "Background Tasks Pool"
            BP1[Thread Pool: 3]
            BP2[Connection Pool: 2]
            BP3[Memory Pool: 512MB]
        end
    end
    
    subgraph "Service Types"
        CS[Critical Services<br/>Payment, Authentication]
        SS[Standard Services<br/>Product Catalog, Search]
        BT[Background Tasks<br/>Analytics, Cleanup]
    end
    
    AC --> CP1
    AC --> SP1
    AC --> BP1
    
    CP1 --> CS
    SP1 --> SS
    BP1 --> BT
    
    style CP1 fill:#ffcdd2
    style SP1 fill:#fff3e0
    style BP1 fill:#e8f5e8
    style CS fill:#ffcdd2
    style SS fill:#fff3e0
    style BT fill:#e8f5e8

Failure Isolation Demonstration

graph LR
    subgraph "Without Bulkhead"
        WB1[Shared Thread Pool<br/>20 Threads] --> WB2[All Services<br/>Competing for Resources]
        WB2 --> WB3[One Service Failure<br/>Affects All Services]
    end
    
    subgraph "With Bulkhead"
        B1[Critical Pool<br/>10 Threads] --> B2[Payment Service<br/>Isolated & Protected]
        B3[Standard Pool<br/>5 Threads] --> B4[Catalog Service<br/>Limited Impact]
        B5[Background Pool<br/>3 Threads] --> B6[Analytics Service<br/>Failure Contained]
    end
    
    style WB3 fill:#ffcdd2
    style B2 fill:#c8e6c9
    style B4 fill:#c8e6c9
    style B6 fill:#c8e6c9

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// Resource Pool Configuration
public class BulkheadConfiguration
{
    public int CriticalServiceThreads { get; set; } = 10;
    public int StandardServiceThreads { get; set; } = 5;
    public int BackgroundTaskThreads { get; set; } = 3;
}

// Bulkhead Implementation
public class BulkheadService
{
    private readonly SemaphoreSlim _criticalSemaphore;
    private readonly SemaphoreSlim _standardSemaphore;
    private readonly SemaphoreSlim _backgroundSemaphore;
    
    public BulkheadService(BulkheadConfiguration config)
    {
        _criticalSemaphore = new SemaphoreSlim(config.CriticalServiceThreads);
        _standardSemaphore = new SemaphoreSlim(config.StandardServiceThreads);
        _backgroundSemaphore = new SemaphoreSlim(config.BackgroundTaskThreads);
    }
    
    public async Task<T> ExecuteCriticalOperationAsync<T>(Func<Task<T>> operation)
    {
        await _criticalSemaphore.WaitAsync();
        try
        {
            return await operation();
        }
        finally
        {
            _criticalSemaphore.Release();
        }
    }
    
    public async Task<T> ExecuteStandardOperationAsync<T>(Func<Task<T>> operation)
    {
        await _standardSemaphore.WaitAsync();
        try
        {
            return await operation();
        }
        finally
        {
            _standardSemaphore.Release();
        }
    }
}

// Usage in Controller
[ApiController]
public class ProductController : ControllerBase
{
    private readonly BulkheadService _bulkheadService;
    private readonly IProductService _productService;
    private readonly IRecommendationService _recommendationService;
    
    [HttpGet("{id}")]
    public async Task<IActionResult> GetProduct(int id)
    {
        // Critical operation - guaranteed resources
        var product = await _bulkheadService.ExecuteCriticalOperationAsync(async () =>
            await _productService.GetByIdAsync(id));
        
        // Non-critical operation - limited resources
        var recommendations = await _bulkheadService.ExecuteStandardOperationAsync(async () =>
            await _recommendationService.GetRecommendationsAsync(id));
        
        return Ok(new { Product = product, Recommendations = recommendations });
    }
}

When to use: High-traffic applications, mixed workload priorities, or systems requiring guaranteed resource availability.

Advantages include: Failure isolation, resource optimization, and improved system resilience.

Disadvantages include: Increased complexity and potential resource underutilization.

Event Sourcing Pattern: Complete Audit Trail

Event Sourcing stores application state as a sequence of events rather than current state, providing complete audit trails and enabling temporal queries.

Event Sourcing Architecture

graph TB
    subgraph "Command Side"
        C1[Commands] --> AR[Aggregate Root]
        AR --> ES[Event Store]
    end
    
    subgraph "Event Store"
        ES --> E1[Event Stream 1<br/>Order-123]
        ES --> E2[Event Stream 2<br/>Order-456]
        ES --> E3[Event Stream 3<br/>Order-789]
    end
    
    subgraph "Event Stream Details"
        E1 --> EV1[OrderCreated<br/>v1]
        EV1 --> EV2[ItemAdded<br/>v2]
        EV2 --> EV3[PaymentProcessed<br/>v3]
        EV3 --> EV4[OrderShipped<br/>v4]
    end
    
    subgraph "Read Side (CQRS)"
        ES --> EP[Event Projections]
        EP --> RM1[Read Model 1<br/>Order Summary]
        EP --> RM2[Read Model 2<br/>Analytics]
        EP --> RM3[Read Model 3<br/>Audit Log]
    end
    
    subgraph "Temporal Queries"
        ES --> TQ[Time Travel Queries]
        TQ --> HS1[State at 2024-01-01]
        TQ --> HS2[State at 2024-06-01]
        TQ --> HS3[Current State]
    end
    
    style AR fill:#ffeb3b
    style ES fill:#e3f2fd
    style EP fill:#c8e6c9
    style TQ fill:#fff3e0

Event Sourcing vs Traditional Storage

graph LR
    subgraph "Traditional CRUD"
        T1[Current State Only<br/>Order: {Status: Shipped}] --> T2[Lost History<br/>No Audit Trail]
        T2 --> T3[Update Overwrites<br/>Previous Data]
    end
    
    subgraph "Event Sourcing"
        E1[Event Stream<br/>All Changes Recorded] --> E2[Complete History<br/>Full Audit Trail]
        E2 --> E3[Append Only<br/>Immutable Events]
        E3 --> E4[Temporal Queries<br/>State at Any Point]
    end
    
    style T3 fill:#ffcdd2
    style E4 fill:#c8e6c9

Event Stream Timeline

timeline
    title Order-123 Event Timeline
    2024-01-15 09:00 : OrderCreated
                     : Customer: "John Doe"
                     : Items: [Product-A, Product-B]
    2024-01-15 09:15 : ItemAdded
                     : Product: "Product-C"
                     : Quantity: 2
    2024-01-15 10:30 : PaymentProcessed
                     : Amount: $150.00
                     : Method: "Credit Card"
    2024-01-16 14:00 : OrderShipped
                     : Carrier: "FedEx"
                     : Tracking: "123456789"
    2024-01-18 16:00 : OrderDelivered
                     : Status: "Completed"
                     : Signature: "J.Doe"

Implementation Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Domain Events
public abstract record DomainEvent(Guid AggregateId, DateTime OccurredAt);

public record OrderCreated(Guid OrderId, string CustomerId, List<OrderItem> Items, DateTime OccurredAt) 
    : DomainEvent(OrderId, OccurredAt);

public record OrderItemAdded(Guid OrderId, string ProductId, int Quantity, decimal Price, DateTime OccurredAt)
    : DomainEvent(OrderId, OccurredAt);

public record OrderShipped(Guid OrderId, string TrackingNumber, DateTime OccurredAt)
    : DomainEvent(OrderId, OccurredAt);

// Event Store
public interface IEventStore
{
    Task SaveEventsAsync(Guid aggregateId, IEnumerable<DomainEvent> events, long expectedVersion);
    Task<IEnumerable<DomainEvent>> GetEventsAsync(Guid aggregateId);
    Task<IEnumerable<DomainEvent>> GetEventsAsync(Guid aggregateId, DateTime fromDate);
}

// Aggregate Root with Event Sourcing
public class Order
{
    private readonly List<DomainEvent> _uncommittedEvents = new();
    
    public Guid Id { get; private set; }
    public string CustomerId { get; private set; }
    public List<OrderItem> Items { get; private set; } = new();
    public OrderStatus Status { get; private set; }
    public long Version { get; private set; }
    
    // Constructor for new orders
    public Order(string customerId, List<OrderItem> items)
    {
        var orderCreated = new OrderCreated(Guid.NewGuid(), customerId, items, DateTime.UtcNow);
        Apply(orderCreated);
        _uncommittedEvents.Add(orderCreated);
    }
    
    // Constructor for rebuilding from events
    public Order(IEnumerable<DomainEvent> events)
    {
        foreach (var @event in events)
        {
            Apply(@event);
            Version++;
        }
    }
    
    public void AddItem(string productId, int quantity, decimal price)
    {
        var itemAdded = new OrderItemAdded(Id, productId, quantity, price, DateTime.UtcNow);
        Apply(itemAdded);
        _uncommittedEvents.Add(itemAdded);
    }
    
    public void Ship(string trackingNumber)
    {
        var orderShipped = new OrderShipped(Id, trackingNumber, DateTime.UtcNow);
        Apply(orderShipped);
        _uncommittedEvents.Add(orderShipped);
    }
    
    private void Apply(DomainEvent @event)
    {
        switch (@event)
        {
            case OrderCreated created:
                Id = created.OrderId;
                CustomerId = created.CustomerId;
                Items = created.Items.ToList();
                Status = OrderStatus.Created;
                break;
            case OrderItemAdded itemAdded:
                Items.Add(new OrderItem(itemAdded.ProductId, itemAdded.Quantity, itemAdded.Price));
                break;
            case OrderShipped shipped:
                Status = OrderStatus.Shipped;
                break;
        }
    }
    
    public IEnumerable<DomainEvent> GetUncommittedEvents() => _uncommittedEvents.AsReadOnly();
    
    public void MarkEventsAsCommitted() => _uncommittedEvents.Clear();
}

// Repository Implementation
public class OrderRepository
{
    private readonly IEventStore _eventStore;
    
    public async Task SaveAsync(Order order)
    {
        var events = order.GetUncommittedEvents();
        if (events.Any())
        {
            await _eventStore.SaveEventsAsync(order.Id, events, order.Version);
            order.MarkEventsAsCommitted();
        }
    }
    
    public async Task<Order> GetByIdAsync(Guid orderId)
    {
        var events = await _eventStore.GetEventsAsync(orderId);
        return events.Any() ? new Order(events) : null;
    }
    
    public async Task<Order> GetOrderStateAtDateAsync(Guid orderId, DateTime asOfDate)
    {
        var events = await _eventStore.GetEventsAsync(orderId, asOfDate);
        return events.Any() ? new Order(events) : null;
    }
}

Event Sourcing Benefits: Complete audit trail, temporal queries, debugging capabilities, and natural fit with event-driven architectures.

Challenges include: Complexity, eventual consistency, and query difficulty requiring CQRS implementation.

Pattern Combination Strategies

Successful architectures often combine multiple patterns. Here are proven combinations:

graph LR
    subgraph "Enterprise Combo"
        E1[Onion Architecture] --> E2[CQRS]
        E2 --> E3[Event Sourcing]
        E3 --> E4[Saga Pattern]
    end
    
    subgraph "Microservices Combo"
        M1[API Gateway] --> M2[BFF]
        M2 --> M3[Circuit Breaker]
        M3 --> M4[Bulkhead]
    end
    
    subgraph "Legacy Migration Combo"
        L1[Strangler Fig] --> L2[Event-Driven]
        L2 --> L3[Outbox Pattern]
    end
    
    style E1 fill:#ffeb3b
    style M1 fill:#c8e6c9
    style L1 fill:#fff3e0

Common Successful Combinations:

Enterprise-Grade Pattern Combinations

  • Onion + CQRS + Event Sourcing: Complex enterprise applications with rich business logic
    • Use case: Financial systems, healthcare platforms, enterprise resource planning
    • Benefits: Domain-driven design, complete audit trail, high performance
    • Complexity: High, requires experienced team
  • Hexagonal + Event-Driven + Bulkhead: Technology-agnostic business logic with asynchronous communication and resource isolation
    • Use case: Microservices architectures, distributed systems
    • Benefits: Technology independence, fault isolation, scalability
    • Complexity: Medium to High

Client-Facing Pattern Combinations

  • API Gateway + BFF + Circuit Breaker: Client-facing applications requiring resilience and optimization
    • Use case: Multi-platform applications (web, mobile, IoT)
    • Benefits: Client optimization, centralized routing, fault tolerance
    • Complexity: Medium
  • BFF + Serverless + Ambassador: Modern client-optimized backends
    • Use case: Event-driven applications with multiple clients
    • Benefits: Auto-scaling, cost efficiency, enhanced connectivity
    • Complexity: Medium

Legacy Modernization Combinations

  • Strangler Fig + BFF + Serverless: Modernizing legacy systems using client-specific backends and event-driven Functions
    • Use case: Legacy system modernization with minimal disruption
    • Benefits: Gradual migration, reduced risk, modern architecture
    • Complexity: Medium to High
  • Strangler Fig + Event-Driven + Outbox: Reliable legacy integration with modern event systems
    • Use case: Legacy systems requiring reliable message delivery
    • Benefits: Reliable messaging, gradual modernization, data consistency
    • Complexity: High

Resilience-Focused Combinations

  • Circuit Breaker + Bulkhead + Outbox: Comprehensive resilience with failure isolation and reliable messaging
    • Use case: High-availability systems, critical business applications
    • Benefits: Fault tolerance, resource isolation, message reliability
    • Complexity: Medium
  • Sidecar + Ambassador + Circuit Breaker: Infrastructure patterns for cross-cutting concerns
    • Use case: Microservices requiring common infrastructure capabilities
    • Benefits: Technology independence, enhanced connectivity, fault tolerance
    • Complexity: Medium to High

Pattern Relationship Map

Understanding how patterns complement each other in modern architectures:

1. Pattern Categories Overview

graph LR
    subgraph "Pattern Categories"
        F["🏗️ FOUNDATION<br/>Core Architecture<br/>▫️ Layered<br/>▫️ Hexagonal<br/>▫️ Onion"]
        C["💬 COMMUNICATION<br/>Data & Messages<br/>▫️ CQRS<br/>▫️ Event-Driven<br/>▫️ Saga"]
        I["🔗 INTEGRATION<br/>Service Connection<br/>▫️ API Gateway<br/>▫️ BFF<br/>▫️ Serverless<br/>▫️ Strangler Fig"]
        R["🛡️ RESILIENCE<br/>Fault Tolerance<br/>▫️ Circuit Breaker<br/>▫️ Bulkhead<br/>▫️ Outbox"]
        IN["🚀 INFRASTRUCTURE<br/>Deployment Support<br/>▫️ Sidecar<br/>▫️ Ambassador"]
        D["📊 DATA & EVENTS<br/>State Management<br/>▫️ Event Sourcing"]
    end
    
    F --> C
    C --> I
    I --> R
    R --> IN
    C --> D
    
    style F fill:#e8f5e8,stroke:#4caf50,stroke-width:3px
    style C fill:#f3e5f5,stroke:#9c27b0,stroke-width:3px
    style I fill:#e1f5fe,stroke:#03a9f4,stroke-width:3px
    style R fill:#fff8e1,stroke:#ffc107,stroke-width:3px
    style IN fill:#fce4ec,stroke:#e91e63,stroke-width:3px
    style D fill:#f1f8e9,stroke:#8bc34a,stroke-width:3px

2. Foundation to Communication Flow

graph TB
    subgraph "🏗️ Foundation Patterns"
        F1["LAYERED<br/>Simple 3-tier<br/>👥 Junior Level<br/>⭐ Low Complexity"]
        F2["HEXAGONAL<br/>Ports & Adapters<br/>👨‍💻 Senior Level<br/>⭐⭐ Medium Complexity"]
        F3["ONION<br/>Domain-Centric<br/>👨‍💻 Senior Level<br/>⭐⭐ Medium Complexity"]
    end
    
    subgraph "💬 Communication Patterns"
        C1["CQRS<br/>Separate Read/Write<br/>👨‍💻 Senior Level<br/>⭐⭐ Medium Complexity"]
        C2["EVENT-DRIVEN<br/>Async Messaging<br/>👨‍💻 Senior Level<br/>⭐⭐⭐ High Complexity"]
        C3["SAGA<br/>Distributed Transactions<br/>👨‍💻 Senior Level<br/>⭐⭐⭐ High Complexity"]
    end
    
    F2 ==>|"Enables Clean Testing"| C1
    F3 ==>|"Domain-Driven Design"| C1
    C1 ==>|"Often Combined"| C2
    C2 ==>|"Orchestrates Complex Workflows"| C3
    
    style F1 fill:#e8f5e8,stroke:#4caf50,stroke-width:3px,color:#000
    style F2 fill:#e8f5e8,stroke:#4caf50,stroke-width:3px,color:#000
    style F3 fill:#e8f5e8,stroke:#4caf50,stroke-width:3px,color:#000
    style C1 fill:#f3e5f5,stroke:#9c27b0,stroke-width:3px,color:#000
    style C2 fill:#f3e5f5,stroke:#9c27b0,stroke-width:3px,color:#000
    style C3 fill:#f3e5f5,stroke:#9c27b0,stroke-width:3px,color:#000

3. Integration and Resilience Patterns

graph TB
    subgraph "� Integration Patterns"
        I1["API GATEWAY<br/>Single Entry Point<br/>� Intermediate<br/>⭐⭐ Medium Complexity"]
        I2["BFF<br/>Backend for Frontend<br/>👥 Intermediate<br/>⭐ Low Complexity"]
        I3["SERVERLESS<br/>Event-Driven Functions<br/>� Intermediate<br/>⭐ Low Complexity"]
        I4["STRANGLER FIG<br/>Legacy Modernization<br/>👨‍💻 Senior<br/>⭐⭐ Medium Complexity"]
    end
    
    subgraph "🛡️ Resilience Patterns"
        R1["CIRCUIT BREAKER<br/>Fail Fast Protection<br/>👥 Intermediate<br/>⭐ Low Complexity"]
        R2["BULKHEAD<br/>Resource Isolation<br/>👥 Intermediate<br/>⭐ Low Complexity"]
        R3["OUTBOX<br/>Reliable Messaging<br/>👨‍💻 Senior<br/>⭐⭐ Medium Complexity"]
    end
    
    I1 ==>|"Client Optimization"| I2
    I1 ==>|"Auto-Scaling"| I3
    I1 ==>|"Protection Layer"| R1
    R1 ==>|"Resource Protection"| R2
    I4 ==>|"Modern Architecture"| I1
    
    style I1 fill:#e1f5fe,stroke:#03a9f4,stroke-width:3px,color:#000
    style I2 fill:#e1f5fe,stroke:#03a9f4,stroke-width:3px,color:#000
    style I3 fill:#e1f5fe,stroke:#03a9f4,stroke-width:3px,color:#000
    style I4 fill:#fff3e0,stroke:#ff9800,stroke-width:3px,color:#000
    style R1 fill:#fff8e1,stroke:#ffc107,stroke-width:3px,color:#000
    style R2 fill:#fff8e1,stroke:#ffc107,stroke-width:3px,color:#000
    style R3 fill:#fff8e1,stroke:#ffc107,stroke-width:3px,color:#000

4. Infrastructure and Data Patterns

graph TB
    subgraph "🚀 Infrastructure Patterns"
        IN1["SIDECAR<br/>Cross-Cutting Concerns<br/>👥 Intermediate<br/>⭐⭐ Medium Complexity"]
        IN2["AMBASSADOR<br/>External Service Proxy<br/>👥 Intermediate<br/>⭐⭐ Medium Complexity"]
    end
    
    subgraph "📊 Data & Event Patterns"
        D1["EVENT SOURCING<br/>Complete Audit Trail<br/>👨‍💻 Senior<br/>⭐⭐⭐ High Complexity"]
    end
    
    subgraph "Integration Support"
        IS1["Enhanced Monitoring"]
        IS2["Service Mesh"]
        IS3["Temporal Queries"]
    end
    
    IN1 ==>|"Provides"| IS1
    IN2 ==>|"Enables"| IS2
    D1 ==>|"Supports"| IS3
    
    style IN1 fill:#fce4ec,stroke:#e91e63,stroke-width:3px,color:#000
    style IN2 fill:#fce4ec,stroke:#e91e63,stroke-width:3px,color:#000
    style D1 fill:#f1f8e9,stroke:#8bc34a,stroke-width:3px,color:#000
    style IS1 fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px,color:#000
    style IS2 fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px,color:#000
    style IS3 fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px,color:#000

5. Common Pattern Combinations

graph LR
    subgraph "🏢 Enterprise Stack"
        E1["Onion Architecture"]
        E2["CQRS"]
        E3["Event Sourcing"]
        E4["Outbox Pattern"]
        
        E1 --> E2
        E2 --> E3
        E2 --> E4
    end
    
    subgraph "🌐 Microservices Stack"
        M1["Hexagonal Architecture"]
        M2["Event-Driven"]
        M3["API Gateway"]
        M4["Circuit Breaker"]
        
        M1 --> M2
        M2 --> M3
        M3 --> M4
    end
    
    subgraph "☁️ Cloud-Native Stack"
        C1["Serverless"]
        C2["BFF"]
        C3["Ambassador"]
        C4["Sidecar"]
        
        C1 --> C2
        C2 --> C3
        C3 --> C4
    end
    
    subgraph "🔄 Legacy Modernization"
        L1["Strangler Fig"]
        L2["API Gateway"]
        L3["Event-Driven"]
        L4["Circuit Breaker"]
        
        L1 --> L2
        L2 --> L3
        L3 --> L4
    end
    
    style E1 fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
    style M1 fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style C1 fill:#e8f5e8,stroke:#4caf50,stroke-width:2px
    style L1 fill:#fff3e0,stroke:#ff9800,stroke-width:2px

Relationship Types Explained:

  • Solid arrows (—›): Strong dependencies or common combinations
  • Dotted arrows (-.→): Optional enhancements or frequent pairings
  • Line thickness: Indicates how commonly patterns are used together
  • Color coding: Groups patterns by primary purpose and complexity

Key Architectural Combinations:

  1. Modern Microservices Stack: Hexagonal + CQRS + Event-Driven + API Gateway + Circuit Breaker
  2. Enterprise Application: Onion + CQRS + Outbox + BFF + Sidecar
  3. Cloud-Native Solution: Serverless + Event-Driven + Circuit Breaker + Ambassador
  4. Legacy Modernization: Strangler Fig + Hexagonal + Event-Driven + API Gateway

Architecture Decision Framework

Comprehensive Pattern Comparison Matrix

PatternComplexityScalabilityMaintainabilityTeam SkillTime to ValuePrimary Use Cases
🏗️ Foundation Patterns      
LayeredLow ⭐Medium ⭐⭐Medium ⭐⭐Junior+ 👥Fast ⚡CRUD apps, rapid prototyping
HexagonalMedium ⭐⭐High ⭐⭐⭐High ⭐⭐⭐Senior 👨‍💻Medium 🔄Complex business logic, testability
OnionMedium ⭐⭐High ⭐⭐⭐High ⭐⭐⭐Senior 👨‍💻Medium 🔄DDD, enterprise applications
💬 Communication Patterns      
CQRSMedium ⭐⭐Very High ⭐⭐⭐⭐Medium ⭐⭐Senior 👨‍💻Medium 🔄Read/write optimization
Event-DrivenHigh ⭐⭐⭐Very High ⭐⭐⭐⭐Medium ⭐⭐Senior 👨‍💻Slow 🐌Microservices, real-time
SagaHigh ⭐⭐⭐High ⭐⭐⭐Medium ⭐⭐Senior 👨‍💻Slow 🐌Distributed transactions
🔗 Integration Patterns      
API GatewayMedium ⭐⭐High ⭐⭐⭐Medium ⭐⭐Intermediate 👥Fast ⚡Service aggregation
BFFLow ⭐Medium ⭐⭐Medium ⭐⭐Intermediate 👥Fast ⚡Client-specific APIs
ServerlessLow ⭐Very High ⭐⭐⭐⭐Medium ⭐⭐Intermediate 👥Fast ⚡Event-driven, auto-scale
Strangler FigMedium ⭐⭐Medium ⭐⭐High ⭐⭐⭐Senior 👨‍💻Slow 🐌Legacy modernization
🛡️ Resilience Patterns      
Circuit BreakerLow ⭐High ⭐⭐⭐High ⭐⭐⭐Intermediate 👥Fast ⚡Fault tolerance
BulkheadLow ⭐High ⭐⭐⭐High ⭐⭐⭐Intermediate 👥Fast ⚡Resource isolation
OutboxMedium ⭐⭐Medium ⭐⭐High ⭐⭐⭐Senior 👨‍💻Medium 🔄Reliable messaging
🚀 Infrastructure Patterns      
SidecarMedium ⭐⭐High ⭐⭐⭐High ⭐⭐⭐Intermediate 👥Medium 🔄Cross-cutting concerns
AmbassadorMedium ⭐⭐High ⭐⭐⭐High ⭐⭐⭐Intermediate 👥Medium 🔄External service proxy
📊 Data & Event Patterns      
Event SourcingHigh ⭐⭐⭐High ⭐⭐⭐Medium ⭐⭐Senior 👨‍💻Slow 🐌Audit trail, temporal queries

Team Size and Project Type Recommendations

graph LR
    subgraph TeamSize["👥 Team Size Guide"]
        T1["Small Team<br/>(1-3 devs)<br/>📊 Focus: Simplicity"]
        T2["Medium Team<br/>(4-8 devs)<br/>📊 Focus: Structure"]
        T3["Large Team<br/>(9+ devs)<br/>📊 Focus: Boundaries"]
    end
    
    subgraph ProjectType["🎯 Project Type"]
        P1["MVP/Prototype<br/>⚡ Speed First"]
        P2["Enterprise App<br/>🏢 Quality First"]  
        P3["Microservices<br/>🌐 Scale First"]
        P4["Legacy Migration<br/>🔄 Safety First"]
    end
    
    T1 --> P1
    T1 --> |"with guidance"| P2
    T2 --> P2
    T2 --> P3
    T3 --> P2
    T3 --> P3
    T3 --> P4
    
    P1 -.-> R1["✅ Layered + Serverless<br/>🚀 API Gateway + Circuit Breaker"]
    P2 -.-> R2["✅ Hexagonal/Onion + CQRS<br/>🚀 BFF + Outbox + Sidecar"]
    P3 -.-> R3["✅ Event-Driven + API Gateway<br/>🚀 Circuit Breaker + Ambassador + Saga"]
    P4 -.-> R4["✅ Strangler Fig + Hexagonal<br/>🚀 Event-Driven + API Gateway"]
    
    style T1 fill:#e8f5e8
    style T2 fill:#fff3e0
    style T3 fill:#ffebee
    style P1 fill:#e3f2fd
    style P2 fill:#f3e5f5
    style P3 fill:#e8f5e8
    style P4 fill:#fff8e1

| BFF | Medium | High | Medium | Intermediate | Multi-platform apps, API optimization | | Sidecar | Low | Medium | High | Intermediate | Cross-cutting concerns, auxiliary services | | Ambassador | Medium | Medium | High | Intermediate | Legacy enhancement, proxy patterns | | Bulkhead | Medium | High | High | Senior | Resource isolation, fault tolerance | | Event Sourcing | High | Very High | Medium | Senior | Audit trails, temporal queries |

Pattern Combination Strategies

Successful architectures often combine multiple patterns. Common combinations include:

Performance Impact Comparison

graph TB
    subgraph "Performance Metrics by Pattern"
        subgraph "Low Latency (< 50ms)"
            LL1[Layered Architecture]
            LL2[Repository Pattern]
            LL3[Circuit Breaker]
        end
        
        subgraph "Medium Latency (50-200ms)"
            ML1[Hexagonal Architecture]
            ML2[API Gateway]
            ML3[CQRS]
            ML4[BFF]
        end
        
        subgraph "Higher Latency (200ms+)"
            HL1[Event-Driven Architecture]
            HL2[Saga Pattern]
            HL3[Event Sourcing]
            HL4[Microservices Mesh]
        end
    end
    
    subgraph "Scalability Rating"
        subgraph "High Scalability (1000+ TPS)"
            HS1[CQRS + Event Sourcing]
            HS2[Serverless Functions]
            HS3[Event-Driven + Bulkhead]
        end
        
        subgraph "Medium Scalability (100-1000 TPS)"
            MS1[API Gateway + BFF]
            MS2[Hexagonal + Repository]
            MS3[Layered + Caching]
        end
        
        subgraph "Limited Scalability (< 100 TPS)"
            LS1[Simple Layered]
            LS2[Monolithic Architecture]
        end
    end
    
    style LL1 fill:#c8e6c9
    style LL2 fill:#c8e6c9
    style LL3 fill:#c8e6c9
    style HS1 fill:#ffeb3b
    style HS2 fill:#ffeb3b
    style HS3 fill:#ffeb3b
    style HL1 fill:#ffcdd2
    style HL2 fill:#ffcdd2
    style HL3 fill:#ffcdd2

Pattern Migration Paths

flowchart TD
    Start[Existing System] --> Assessment{Current Architecture?}
    
    Assessment -->|Monolith| Mono[Monolithic System]
    Assessment -->|Layered| Layer[Layered Architecture]
    Assessment -->|Legacy| Legacy[Legacy System]
    
    Mono --> Step1[Step 1: Add API Gateway]
    Layer --> Step2[Step 2: Implement CQRS]
    Legacy --> Step3[Step 3: Strangler Fig Pattern]
    
    Step1 --> Step4[Step 4: Extract Microservices]
    Step2 --> Step5[Step 5: Add Event-Driven]
    Step3 --> Step6[Step 6: Modern Service Implementation]
    
    Step4 --> Final1[Modern Microservices]
    Step5 --> Final2[Event-Driven Architecture]
    Step6 --> Final3[Modernized System]
    
    Final1 --> Optimize[Optimization Phase]
    Final2 --> Optimize
    Final3 --> Optimize
    
    Optimize --> Patterns[Add Advanced Patterns:<br/>• Circuit Breaker<br/>• Bulkhead<br/>• Event Sourcing<br/>• Saga]
    
    style Start fill:#e3f2fd
    style Optimize fill:#ffeb3b
    style Patterns fill:#c8e6c9
  • CQRS + Event-Driven + Event Sourcing: Complete event-based architecture with separated read/write models, event communication, and complete audit trails
  • BFF + API Gateway: Client-optimized backends with centralized routing and cross-cutting concerns
  • Sidecar + Ambassador: Auxiliary services with enhanced connectivity patterns
  • Hexagonal + Event-Driven + Bulkhead: Technology-agnostic business logic with asynchronous communication and resource isolation
  • Strangler Fig + BFF + Serverless: Modernizing legacy systems using client-specific backends and event-driven Functions
  • Circuit Breaker + Bulkhead + Outbox: Comprehensive resilience with failure isolation and reliable messaging

Best Practices for Implementation

Pattern Implementation Lifecycle

graph TD
    A[Assessment Phase] --> B[Design Phase]
    B --> C[Implementation Phase]
    C --> D[Testing Phase]
    D --> E[Deployment Phase]
    E --> F[Monitoring Phase]
    F --> G[Evolution Phase]
    G --> A
    
    A --> A1[Team Skills Assessment<br/>Current Architecture Review<br/>Business Requirements<br/>Performance Needs]
    
    B --> B1[Pattern Selection<br/>Architecture Design<br/>Technology Stack<br/>Migration Strategy]
    
    C --> C1[Code Implementation<br/>Infrastructure Setup<br/>Integration Development<br/>Documentation]
    
    D --> D1[Unit Testing<br/>Integration Testing<br/>Performance Testing<br/>Security Testing]
    
    E --> E1[Blue-Green Deployment<br/>Feature Flags<br/>Gradual Rollout<br/>Rollback Planning]
    
    F --> F1[Performance Monitoring<br/>Error Tracking<br/>Business Metrics<br/>Health Checks]
    
    G --> G1[Pattern Effectiveness<br/>Scaling Needs<br/>Technology Updates<br/>Optimization]
    
    style A fill:#e3f2fd
    style B fill:#fff3e0
    style C fill:#c8e6c9
    style D fill:#ffcdd2
    style E fill:#f3e5f5
    style F fill:#ffeb3b
    style G fill:#e1bee7

Team Skill Requirements Matrix

graph TB
    subgraph "Junior Level (0-2 years)"
        J1[Layered Architecture ✓]
        J2[API Gateway ✓]
        J3[Circuit Breaker ✓]
    end
    
    subgraph "Intermediate Level (2-5 years)"
        I1[Hexagonal Architecture ✓]
        I2[CQRS ✓]
        I3[BFF ✓]
        I4[Serverless ✓]
        I5[Sidecar ✓]
        I6[Outbox ✓]
    end
    
    subgraph "Senior Level (5+ years)"
        S1[Onion Architecture ✓]
        S2[Event-Driven ✓]
        S3[Saga Pattern ✓]
        S4[Strangler Fig ✓]
        S5[Ambassador ✓]
        S6[Bulkhead ✓]
    end
    
    subgraph "Expert Level (8+ years)"
        E1[Event Sourcing ✓]
        E2[Complex Pattern Combinations ✓]
        E3[Enterprise Architecture ✓]
    end
    
    style J1 fill:#c8e6c9
    style J2 fill:#c8e6c9
    style J3 fill:#c8e6c9
    style I1 fill:#fff3e0
    style I2 fill:#fff3e0
    style I3 fill:#fff3e0
    style I4 fill:#fff3e0
    style I5 fill:#fff3e0
    style I6 fill:#fff3e0
    style S1 fill:#ffcdd2
    style S2 fill:#ffcdd2
    style S3 fill:#ffcdd2
    style S4 fill:#ffcdd2
    style S5 fill:#ffcdd2
    style S6 fill:#ffcdd2
    style E1 fill:#f3e5f5
    style E2 fill:#f3e5f5
    style E3 fill:#f3e5f5

Start with architectural assessment

Begin with a thorough assessment of your current system, team capabilities, and business requirements. Simple applications benefit from Layered Architecture, while complex enterprise systems require Onion or Hexagonal approaches.

Implement monitoring and observability

Modern .Net applications should leverage OpenTelemetry for distributed tracing, Application Insights for performance monitoring, and custom metrics for business-specific monitoring.

Comprehensive Observability Architecture

graph TB
    subgraph "Application Layer"
        APP1[Order Service]
        APP2[Payment Service]
        APP3[Inventory Service]
    end
    
    subgraph "Observability Stack"
        subgraph "Metrics Collection"
            PROM[Prometheus]
            GRAFANA[Grafana Dashboards]
        end
        
        subgraph "Distributed Tracing"
            JAEGER[Jaeger Tracing]
            ZIPKIN[Zipkin Alternative]
        end
        
        subgraph "Logging"
            ELK[ELK Stack]
            FLUENT[Fluentd/Fluent Bit]
        end
        
        subgraph "Azure Monitoring"
            AI[Application Insights]
            LAW[Log Analytics Workspace]
            ALERTS[Azure Alerts]
        end
    end
    
    subgraph "OpenTelemetry"
        OTEL[OpenTelemetry Collector]
        SDK[.NET SDK Integration]
    end
    
    APP1 --> SDK
    APP2 --> SDK
    APP3 --> SDK
    
    SDK --> OTEL
    
    OTEL --> PROM
    OTEL --> JAEGER
    OTEL --> ELK
    OTEL --> AI
    
    PROM --> GRAFANA
    AI --> LAW
    LAW --> ALERTS
    
    style OTEL fill:#ffeb3b
    style SDK fill:#c8e6c9
    style AI fill:#e3f2fd
    style GRAFANA fill:#fff3e0

Telemetry Data Flow

sequenceDiagram
    participant App as .NET Application
    participant OTEL as OpenTelemetry SDK
    participant Collector as OTEL Collector
    participant Metrics as Metrics Store
    participant Traces as Trace Store
    participant Logs as Log Store
    participant Dashboard as Monitoring Dashboard
    
    App->>OTEL: Business Events
    App->>OTEL: Performance Metrics
    App->>OTEL: Distributed Traces
    App->>OTEL: Application Logs
    
    OTEL->>Collector: Batched Telemetry
    
    Collector->>Metrics: Prometheus Metrics
    Collector->>Traces: Jaeger Spans
    Collector->>Logs: Structured Logs
    
    Dashboard->>Metrics: Query Metrics
    Dashboard->>Traces: Query Traces
    Dashboard->>Logs: Query Logs
    
    Note over Dashboard: Unified Observability View
1
2
3
4
5
6
7
8
9
10
11
12
// OpenTelemetry Configuration
services.AddOpenTelemetry()
    .WithTracing(builder =>
    {
        builder.AddSource("Polly");
        builder.AddAspNetCoreInstrumentation();
    })
    .WithMetrics(builder =>
    {
        builder.AddMeter("Polly");
        builder.AddMeter("Microsoft.Extensions.Http.Resilience");
    });

Adopt gradual migration strategies

When modernizing existing systems, use the Strangler Fig Pattern to gradually replace components while maintaining operational continuity. Start with low-risk components and expand systematically.

Ensure team readiness

Advanced patterns require skilled teams. Invest in training for Domain-Driven Design, microservices principles, and cloud-native development practices before implementing complex patterns.

Conclusion

This visual guide transforms complex architectural decisions into clear, actionable workflows. The modern .NET ecosystem offers unprecedented capabilities for building scalable, resilient applications, and the visual decision trees and flow diagrams in this guide help architects navigate the complexity of pattern selection.

Key Takeaways

mindmap
  root((Architecture Success))
    Start Simple
      Layered for MVPs
      Iterative Enhancement
      Team Learning
    Think Visual
      Diagram First
      Document Flows
      Share Understanding
    Plan Evolution
      Migration Paths
      Pattern Combinations
      Scalability Roadmap
    Monitor Continuously
      Pattern Effectiveness
      Performance Impact
      Team Productivity

Decision Framework Summary

Follow these visual workflows for architectural success:

  1. Assessment Phase: Use the pattern selection decision tree to identify suitable patterns
  2. Design Phase: Apply architecture flow diagrams to structure your solution
  3. Implementation Phase: Reference minimal code contracts and interfaces
  4. Evolution Phase: Leverage pattern relationship maps for strategic enhancement

The Visual Advantage

Diagrams over code samples provide lasting value because they:

  • Focus on architectural concepts rather than implementation details
  • Remain relevant as technology evolves
  • Enable better communication across teams
  • Support faster decision-making processes
  • Facilitate pattern combination strategies

Success in modern .NET architecture depends on visual thinking, systematic pattern selection, and evolutionary design approaches. Use the decision trees and workflow diagrams in this guide to build applications that are not only functional today but adaptable for tomorrow’s requirements.

This post is licensed under CC BY 4.0 by the author.