Back to Blog
microservices

microservices-patterns

February 1, 202410 min min read

Essential Microservices Patterns for .NET

Microservices architecture brings scalability and flexibility, but also complexity. These essential patterns will help you build robust, maintainable microservices in .NET.

1. API Gateway Pattern

Problem

Clients need to communicate with multiple microservices, leading to:
  • Multiple network calls
  • Different protocols
  • Authentication complexity
  • Version management

Solution

Create a single entry point that routes requests to appropriate services.

public class ApiGateway
{
    private readonly IHttpClientFactory <em>httpClientFactory;
    private readonly IAuthService </em>authService;
    
    public async Task<IActionResult> RouteRequest(HttpRequest request)
    {
        // Authenticate
        var user = await <em>authService.ValidateToken(request.Headers["Authorization"]);
        
        // Route based on path
        var targetService = DetermineTargetService(request.Path);
        var client = </em>httpClientFactory.CreateClient(targetService);
        
        // Add common headers
        client.DefaultRequestHeaders.Add("X-User-Id", user.Id);
        client.DefaultRequestHeaders.Add("X-Request-Id", Guid.NewGuid().ToString());
        
        // Forward request
        var response = await client.SendAsync(CreateProxyRequest(request));
        
        return new ProxyResult(response);
    }
}

Using Ocelot

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/products/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        { "Host": "product-service", "Port": 80 }
      ],
      "UpstreamPathTemplate": "/products/{everything}",
      "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer"
      }
    }
  ]
}

2. Circuit Breaker Pattern

Problem

When a service fails, cascading failures can bring down the entire system.

Solution

Monitor failures and "open the circuit" to prevent calls to failing services.

using Polly;
using Polly.CircuitBreaker;</p><p>public class ResilientHttpClient
{
    private readonly AsyncCircuitBreakerPolicy <em>circuitBreakerPolicy;
    
    public ResilientHttpClient()
    {
        </em>circuitBreakerPolicy = Policy
            .Handle<HttpRequestException>()
            .CircuitBreakerAsync(
                handledEventsAllowedBeforeBreaking: 3,
                durationOfBreak: TimeSpan.FromSeconds(30),
                onBreak: (exception, duration) =>
                {
                    Console.WriteLine($"Circuit broken for {duration.TotalSeconds}s");
                },
                onReset: () =>
                {
                    Console.WriteLine("Circuit reset");
                }
            );
    }
    
    public async Task<T> GetAsync<T>(string url)
    {
        return await <em>circuitBreakerPolicy.ExecuteAsync(async () =>
        {
            var client = new HttpClient();
            var response = await client.GetAsync(url);
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsAsync<T>();
        });
    }
}

Advanced: Fallback Strategy

var fallbackPolicy = Policy<ProductDto>
    .Handle<BrokenCircuitException>()
    .FallbackAsync(
        fallbackValue: GetCachedProduct(),
        onFallbackAsync: async (result) =>
        {
            Console.WriteLine("Using cached data due to circuit break");
        }
    );</p><p>var product = await fallbackPolicy.ExecuteAsync(
    () => </em>client.GetAsync<ProductDto>($"/api/products/{id}")
);

3. Event-Driven Pattern

Problem

Services need to communicate without tight coupling.

Solution

Use events and message brokers for asynchronous communication.

// Domain Event
public class OrderPlacedEvent
{
    public Guid OrderId { get; set; }
    public Guid CustomerId { get; set; }
    public decimal TotalAmount { get; set; }
    public DateTime PlacedAt { get; set; }
}</p><p>// Publisher
public class OrderService
{
    private readonly IMessageBus <em>messageBus;
    
    public async Task PlaceOrderAsync(Order order)
    {
        // Save order
        await </em>repository.AddAsync(order);
        
        // Publish event
        await <em>messageBus.PublishAsync(new OrderPlacedEvent
        {
            OrderId = order.Id,
            CustomerId = order.CustomerId,
            TotalAmount = order.TotalAmount,
            PlacedAt = DateTime.UtcNow
        });
    }
}</p><p>// Subscriber - Inventory Service
public class InventoryEventHandler : IEventHandler<OrderPlacedEvent>
{
    public async Task HandleAsync(OrderPlacedEvent @event)
    {
        // Reserve inventory
        await </em>inventoryService.ReserveItems(@event.OrderId);
    }
}</p><p>// Subscriber - Notification Service
public class NotificationEventHandler : IEventHandler<OrderPlacedEvent>
{
    public async Task HandleAsync(OrderPlacedEvent @event)
    {
        // Send confirmation email
        await <em>emailService.SendOrderConfirmation(@event.OrderId);
    }
}

Using MassTransit with RabbitMQ

// Configure MassTransit
services.AddMassTransit(x =>
{
    x.AddConsumer<OrderPlacedConsumer>();
    
    x.UsingRabbitMq((context, cfg) =>
    {
        cfg.Host("rabbitmq://localhost", h =>
        {
            h.Username("guest");
            h.Password("guest");
        });
        
        cfg.ReceiveEndpoint("order-placed-queue", e =>
        {
            e.ConfigureConsumer<OrderPlacedConsumer>(context);
        });
    });
});

4. Saga Pattern

Problem

Distributed transactions across multiple services are complex.

Solution

Use choreography or orchestration to manage long-running transactions.

Choreography Approach

public class OrderSaga
{
    // Step 1: Order created
    public async Task Handle(OrderCreatedEvent @event)
    {
        await </em>messageBus.PublishAsync(new ReserveInventoryCommand
        {
            OrderId = @event.OrderId,
            Items = @event.Items
        });
    }
    
    // Step 2: Inventory reserved
    public async Task Handle(InventoryReservedEvent @event)
    {
        await <em>messageBus.PublishAsync(new ProcessPaymentCommand
        {
            OrderId = @event.OrderId,
            Amount = @event.TotalAmount
        });
    }
    
    // Step 3: Payment processed
    public async Task Handle(PaymentProcessedEvent @event)
    {
        await </em>messageBus.PublishAsync(new ShipOrderCommand
        {
            OrderId = @event.OrderId
        });
    }
    
    // Compensation: Payment failed
    public async Task Handle(PaymentFailedEvent @event)
    {
        await <em>messageBus.PublishAsync(new ReleaseInventoryCommand
        {
            OrderId = @event.OrderId
        });
    }
}

5. Service Discovery

Problem

Services need to find each other in dynamic environments.

Solution

Use a service registry like Consul or Eureka.

public class ConsulServiceRegistry
{
    private readonly IConsulClient </em>consul;
    
    public async Task RegisterServiceAsync(ServiceRegistration registration)
    {
        await <em>consul.Agent.ServiceRegister(new AgentServiceRegistration
        {
            ID = registration.Id,
            Name = registration.Name,
            Address = registration.Address,
            Port = registration.Port,
            Check = new AgentServiceCheck
            {
                HTTP = $"http://{registration.Address}:{registration.Port}/health",
                Interval = TimeSpan.FromSeconds(10),
                Timeout = TimeSpan.FromSeconds(5)
            }
        });
    }
    
    public async Task<ServiceEndpoint> DiscoverServiceAsync(string serviceName)
    {
        var services = await </em>consul.Health.Service(serviceName, tag: null, passingOnly: true);
        
        if (!services.Response.Any())
            throw new ServiceNotFoundException(serviceName);
        
        // Load balance - pick random
        var service = services.Response[Random.Shared.Next(services.Response.Length)];
        
        return new ServiceEndpoint
        {
            Address = service.Service.Address,
            Port = service.Service.Port
        };
    }
}

6. CQRS in Microservices

Separate Read and Write Models

// Command Side (Write)
public class CreateOrderCommand : IRequest<Guid>
{
    public Guid CustomerId { get; set; }
    public List<OrderItem> Items { get; set; }
}</p><p>public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Guid>
{
    private readonly OrderDbContext <em>dbContext;
    
    public async Task<Guid> Handle(CreateOrderCommand request, CancellationToken ct)
    {
        var order = new Order(request.CustomerId, request.Items);
        
        </em>dbContext.Orders.Add(order);
        await <em>dbContext.SaveChangesAsync(ct);
        
        return order.Id;
    }
}</p><p>// Query Side (Read)
public class GetOrderQuery : IRequest<OrderDto>
{
    public Guid OrderId { get; set; }
}</p><p>public class GetOrderHandler : IRequestHandler<GetOrderQuery, OrderDto>
{
    private readonly IOrderReadRepository </em>readRepo;
    
    public async Task<OrderDto> Handle(GetOrderQuery request, CancellationToken ct)
    {
        return await _readRepo.GetByIdAsync(request.OrderId);
    }
}

Best Practices

Start with a monolith: Don't jump to microservices too early ✅ Use async communication: Prefer events over synchronous HTTP ✅ Implement proper logging: Distributed tracing is essential ✅ Design for failure: Use circuit breakers and retries ✅ Automate everything: CI/CD is crucial for microservices ✅ Monitor health: Implement health checks and metrics

Conclusion

These patterns form the foundation of successful microservices architecture in .NET. Start with the patterns that solve your immediate problems:

  • API Gateway for client communication
  • Circuit Breaker for resilience
  • Event-Driven for loose coupling
  • Saga for distributed transactions
Remember: microservices add complexity. Use these patterns to manage that complexity, not create more of it.

Share this article

Tags

Microservices Patterns .NET

Subscribe to Newsletter

Get notified about new articles