Microservices with ASP.NET Core
Design and implement scalable microservices architecture.
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
- Separate databases: No shared data stores
- Async communication: Use events and messaging
- Independent deployment: Version APIs carefully
- Circuit breakers: Handle service failures
- 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.
Related Articles
Microservices Architecture
Introduction to microservices: building scalable, independent services.
Read More architectureDomain-Driven Design with ASP.NET Core
Design complex systems using Domain-Driven Design principles.
Read More architectureMulti-Tenancy in ASP.NET Core
Build scalable multi-tenant applications serving multiple customers.
Read More