Isaac.

architecture

Microservices with ASP.NET Core

Design and implement scalable microservices architecture.

By Emem IsaacJanuary 9, 20243 min read
#microservices#aspnet core#distributed systems#architecture
Share:

A Simple Analogy

Microservices is like franchising a business. Each location (service) operates independently but coordinates with others. If one fails, others keep running.


Why Microservices?

  • Scalability: Scale individual services independently
  • Flexibility: Use different tech stacks per service
  • Resilience: One service failure doesn't kill system
  • Deployment: Deploy services independently
  • Team autonomy: Teams own their services

Service Structure

// OrderService/Controllers/OrdersController.cs
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IOrderService _orderService;
    private readonly IPublishEndpoint _publishEndpoint;
    
    [HttpPost]
    public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
    {
        var order = await _orderService.CreateAsync(request);
        
        // Publish event for other services
        await _publishEndpoint.Publish(new OrderCreated
        {
            OrderId = order.Id,
            CustomerId = order.CustomerId
        });
        
        return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
    }
    
    [HttpGet("{id}")]
    public async Task<IActionResult> GetOrder(int id)
    {
        var order = await _orderService.GetAsync(id);
        if (order == null) return NotFound();
        
        return Ok(order);
    }
}

// OrderService/Services/OrderService.cs
public class OrderService : IOrderService
{
    private readonly IOrderRepository _repository;
    private readonly IPaymentClient _paymentClient;
    
    public async Task<OrderDto> CreateAsync(CreateOrderRequest request)
    {
        // Validate
        if (!request.Items.Any()) throw new ArgumentException("No items");
        
        // Call payment service
        var paymentResponse = await _paymentClient.AuthorizeAsync(request.PaymentInfo);
        if (!paymentResponse.Success) throw new InvalidOperationException("Payment failed");
        
        // Create order
        var order = new Order
        {
            CustomerId = request.CustomerId,
            Items = MapItems(request.Items),
            Status = OrderStatus.Confirmed
        };
        
        await _repository.SaveAsync(order);
        return MapToDto(order);
    }
}

Service Discovery

// HttpClientFactory with Polly
builder.Services.AddHttpClient<IPaymentClient, PaymentClient>()
    .ConfigureHttpClient(client =>
    {
        client.BaseAddress = new Uri("https://payment-service:5001");
    })
    .AddTransientHttpErrorPolicy()
    .WaitAndRetryAsync(3, retryAttempt =>
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

// Or with service discovery
builder.Services.AddHttpClient<IPaymentClient, PaymentClient>()
    .ConfigureHttpClient((sp, client) =>
    {
        var serviceRegistry = sp.GetRequiredService<IServiceRegistry>();
        var paymentService = serviceRegistry.GetService("payment-service");
        client.BaseAddress = new Uri($"https://{paymentService.Host}:{paymentService.Port}");
    });

API Gateway Pattern

// Gateway/Program.cs
var app = builder.Build();

// Route to OrderService
app.MapGet("/api/orders/{id}", async (int id, HttpClient http) =>
{
    var response = await http.GetAsync($"https://order-service/api/orders/{id}");
    return response;
});

// Route to PaymentService
app.MapPost("/api/payments", async (PaymentRequest request, HttpClient http) =>
{
    var json = JsonSerializer.Serialize(request);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    var response = await http.PostAsync("https://payment-service/api/payments", content);
    return response;
});

// Or use Ocelot NuGet
var configuration = new ConfigurationBuilder()
    .AddJsonFile("ocelot.json")
    .Build();

builder.Services.AddOcelot(configuration);

Saga Pattern for Transactions

// Distributed transaction across services
public partial class OrderFulfillmentSaga : StateMachine<OrderFulfillmentState>
{
    public Event<OrderSubmitted> Submit { get; private set; }
    
    public OrderFulfillmentSaga()
    {
        Initially(
            When(Submit)
                .Then(context =>
                {
                    context.Instance.OrderId = context.Data.OrderId;
                })
                .TransitionTo(PaymentProcessing)
                .Send(context => new Uri("rabbitmq://payment-service"),
                    new ProcessPayment { OrderId = context.Data.OrderId })
        );
    }
}

Monitoring Microservices

// Distributed tracing
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter());

// Health checks for dependencies
builder.Services.AddHealthChecks()
    .AddCheck<PaymentServiceHealthCheck>("payment-service")
    .AddCheck<OrderDatabaseHealthCheck>("order-db");

Best Practices

  1. Separate databases: No shared data stores
  2. Async communication: Use events and messaging
  3. Independent deployment: Version APIs carefully
  4. Circuit breakers: Handle service failures
  5. Distributed tracing: Track requests across services

Related Concepts

  • API Gateway pattern
  • Service mesh (Istio, Linkerd)
  • Container orchestration (Kubernetes)
  • Event-driven architecture
  • CQRS for scalable queries

Summary

Microservices enable scalable, resilient systems by decomposing monoliths. Use service discovery, API gateways, and async communication to coordinate independent services.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours