Building Scalable Microservices with .NET 8

4 min lecturaMatías Romero

Building Scalable Microservices with .NET 8

Microservices architecture has become the de facto standard for building scalable, maintainable enterprise applications. In this post, I'll share my experience implementing microservices with .NET 8 and the lessons learned from real-world deployments.

Architecture Overview

A well-designed microservices architecture in .NET 8 typically includes:

  • API Gateway (Azure API Management or Ocelot)
  • Service Discovery (Consul or Azure Service Fabric)
  • Message Brokers (Azure Service Bus or RabbitMQ)
  • Distributed Caching (Redis)
  • Monitoring (Application Insights, Prometheus)

Setting Up a Microservice

Here's a basic microservice setup using .NET 8 Minimal APIs:

var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<UserContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// Add health checks
builder.Services.AddHealthChecks()
    .AddDbContextCheck<UserContext>();

var app = builder.Build();

// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

// Define endpoints
app.MapGet("/api/users", async (UserContext context) =>
{
    return await context.Users.ToListAsync();
})
.WithName("GetUsers")
.WithOpenApi();

app.MapHealthChecks("/health");

app.Run();

Docker Configuration

Every microservice should be containerized. Here's a optimized Dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["UserService/UserService.csproj", "UserService/"]
RUN dotnet restore "UserService/UserService.csproj"
COPY . .
WORKDIR "/src/UserService"
RUN dotnet build "UserService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "UserService.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "UserService.dll"]

Service Communication

Implement resilient communication patterns:

public class OrderService
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<OrderService> _logger;

    public OrderService(HttpClient httpClient, ILogger<OrderService> logger)
    {
        _httpClient = httpClient;
        _logger = logger;
    }

    public async Task<User?> GetUserAsync(int userId)
    {
        try
        {
            var response = await _httpClient.GetAsync($"/api/users/{userId}");
            
            if (response.IsSuccessStatusCode)
            {
                var json = await response.Content.ReadAsStringAsync();
                return JsonSerializer.Deserialize<User>(json);
            }
            
            _logger.LogWarning("User service returned {StatusCode}", response.StatusCode);
            return null;
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Error calling user service");
            return null;
        }
    }
}

Configuration with Azure Service Bus

For async communication, use message brokers:

// Program.cs
builder.Services.AddAzureServiceBus(builder.Configuration.GetConnectionString("ServiceBus"));

// Message handler
public class OrderCreatedHandler : IMessageHandler<OrderCreatedEvent>
{
    public async Task Handle(OrderCreatedEvent message)
    {
        // Process order created event
        await ProcessOrder(message.OrderId);
    }
}

Health Checks and Monitoring

Implement comprehensive health checks:

builder.Services.AddHealthChecks()
    .AddDbContextCheck<ApplicationContext>()
    .AddAzureServiceBusQueue("orders-queue")
    .AddRedis(builder.Configuration.GetConnectionString("Redis"))
    .AddHttpHealthCheck("user-service", "https://user-service/health");

Deployment with Azure Container Apps

Deploy using Azure Container Apps for serverless containers:

# azure-container-app.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: user-service-config
data:
  ASPNETCORE_ENVIRONMENT: "Production"
  ConnectionStrings__DefaultConnection: "Server=..."
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: myregistry.azurecr.io/user-service:latest
        ports:
        - containerPort: 80
        envFrom:
        - configMapRef:
            name: user-service-config

Best Practices I've Learned

  1. Start with a monolith and extract services as needed
  2. Database per service - avoid shared databases
  3. API versioning is crucial for backward compatibility
  4. Circuit breaker pattern for resilient service calls
  5. Centralized logging with correlation IDs
  6. Infrastructure as Code with Terraform or ARM templates

Performance Considerations

  • Use connection pooling for database connections
  • Implement response caching where appropriate
  • Consider CQRS pattern for read/write separation
  • Use async/await properly to avoid blocking threads

Monitoring and Observability

// Add telemetry
builder.Services.AddApplicationInsightsTelemetry();

// Custom metrics
public class OrderMetrics
{
    private static readonly Counter OrdersProcessed = 
        Meter.CreateCounter<int>("orders_processed_total");

    public void IncrementOrdersProcessed() => OrdersProcessed.Add(1);
}

Conclusion

Building microservices with .NET 8 requires careful planning and adherence to proven patterns. The key is to:

  • Design for failure and implement resilience patterns
  • Monitor everything with proper observability
  • Automate deployment and scaling
  • Keep services focused and loosely coupled

These practices have helped me successfully deploy and maintain microservices architectures serving millions of requests daily. The investment in proper architecture pays dividends in scalability and maintainability.

What challenges have you faced when implementing microservices? Share your experiences in the comments!

Compartir este post