Propósito: Atua como um ponto de entrada único para requisições de clientes em uma arquitetura de microserviços, tratando roteamento, autenticação, limitação de taxa e logging, enquanto abstrai a complexidade de múltiplos serviços de backend.
Visão Geral da Arquitetura
Componentes-chave:
Cliente: Envia requisições HTTP
API Gateway: Roteia requisições e aplica políticas
Microserviços: Tratam lógica de negócio independentemente
Fluxo de Requisição
Cliente envia requisição para o API Gateway
Gateway autentica e aplica políticas (ex: limitação de taxa)
Roteia requisição para o microserviço apropriado
Microserviço processa e retorna resposta
Gateway agrega ou transforma resposta se necessário
Cliente recebe resposta final
Implementação em C# Usando Ocelot
Instalar Ocelot
dotnet add package Ocelot
Configurar ocelot.json
{
"Routes": [
{
"DownstreamPathTemplate": "/api/products",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{ "Host": "localhost", "Port": 5001 }
],
"UpstreamPathTemplate": "/gateway/products",
"UpstreamHttpMethod": [ "GET" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000"
}
}
Configurar Program.cs (para .NET 6+)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOcelot();
var app = builder.Build();
await app.UseOcelot();
app.Run();
Propósito: Em vez de clientes fazerem múltiplas chamadas para diferentes microserviços, uma camada de composição orquestra essas chamadas e retorna um resultado consolidado, simplificando a lógica do cliente e melhorando o desempenho.
Visão Geral da Arquitetura
Cliente → Camada de Composição → [Microserviço A, B, C] → Resposta Unificada
Componentes-chave:
Cliente: Envia uma única requisição
Camada de Composição: Agrega respostas de múltiplos serviços
Microserviços: Fornecem dados específicos do domínio
Implementação em C#
DTO Composto
public class CompositeResponse {
public Product Product { get; set; }
public Inventory Inventory { get; set; }
public Pricing Pricing { get; set; }
}
Controlador de Composição
[ApiController]
[Route("api/composite")]
public class CompositeController : ControllerBase {
private readonly IHttpClientFactory _httpClientFactory;
public CompositeController(IHttpClientFactory httpClientFactory) {
_httpClientFactory = httpClientFactory;
}
[HttpGet("{productId}")]
public async Task<IActionResult> GetCompositeData(string productId) {
var client = _httpClientFactory.CreateClient();
var product = await client.GetFromJsonAsync<Product>($"http://localhost:5001/api/products/{productId}");
var inventory = await client.GetFromJsonAsync<Inventory>($"http://localhost:5002/api/inventory/{productId}");
var pricing = await client.GetFromJsonAsync<Pricing>($"http://localhost:5003/api/pricing/{productId}");
var response = new CompositeResponse {
Product = product,
Inventory = inventory,
Pricing = pricing
};
return Ok(response);
}
}
Benefícios:
Simplifica a lógica do cliente
Reduz overhead de rede
Centraliza transformação e orquestração
Melhora desempenho para aplicações intensivas em UI
Propósito: Em sistemas distribuídos, hardcoding endereços de serviço é frágil. Service Discovery introduz um registro de serviços que rastreia instâncias disponíveis, permitindo que clientes ou roteadores localizem serviços dinamicamente.
Visão Geral da Arquitetura
Cliente → Registro de Serviços ← Microserviços (Registro)
Cliente → Endereço de Serviço Resolvido → Microserviço
Componentes-chave:
Cliente: Inicia chamadas de serviço
Registro de Serviços: Armazena metadados de instâncias de serviço
Microserviços: Se registram e respondem a requisições
Implementação em C# Usando Consul
Instalar Pacote Consul
dotnet add package Consul
Registrar Serviço com Consul
var consulClient = new ConsulClient();
var registration = new AgentServiceRegistration {
ID = "order-service-1",
Name = "OrderService",
Address = "localhost",
Port = 5001,
Check = new AgentServiceCheck {
HTTP = "http://localhost:5001/health",
Interval = TimeSpan.FromSeconds(10)
}
};
await consulClient.Agent.ServiceRegister(registration);
Descobrir Serviço do Cliente
var consulClient = new ConsulClient();
var services = await consulClient.Agent.Services();
var orderService = services.Response.Values
.FirstOrDefault(s => s.Service.Equals("OrderService"));
var client = new HttpClient();
var response = await client.GetAsync($"http://{orderService.Address}:{orderService.Port}/api/orders");
Propósito: Cada microserviço gerencia seu próprio armazenamento de dados, garantindo acoplamento fraco, evolução independente do esquema e flexibilidade tecnológica.
Visão Geral da Arquitetura
Serviço A → Banco de Dados A
Serviço B → Banco de Dados B
Serviço C → Banco de Dados C
Cada serviço:
Possui seus dados
Expõe acesso apenas via APIs
Pode escolher o banco de dados mais adequado (SQL, NoSQL, Graph, etc.)
Implementação em C# com EF Core
Definir DbContexts Separados
// OrderServiceDbContext.cs
public class OrderServiceDbContext : DbContext
{
public DbSet<Order> Orders { get; set; }
}
// InventoryServiceDbContext.cs
public class InventoryServiceDbContext : DbContext
{
public DbSet<Item> Items { get; set; }
}
Configurar no Startup
services.AddDbContext<OrderServiceDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("OrderDb")));
services.AddDbContext<InventoryServiceDbContext>(options =>
options.UseMongoDb(Configuration.GetConnectionString("InventoryDb")));
Benefícios:
Independência de serviço
Escalabilidade baseada em necessidades de dados individuais
Diversidade tecnológica
Isolamento de falhas
Desafios:
Complexidade de consultas entre serviços
Requisitos de duplicação de dados
Gerenciamento de transações distribuídas
Propósito: Gerencia transações distribuídas entre microserviços dividindo o processo em uma série de transações locais. Se uma falhar, transações compensatórias são acionadas para desfazer os passos anteriores.
Visão Geral da Arquitetura
Cliente → Orquestrador Saga → [Serviço de Pedido → Serviço de Pagamento → Serviço de Inventário]
↓ (em caso de falha)
[Ações de Compensação]
Componentes-chave:
Cliente: Inicia a transação
Orquestrador Saga: Coordena o fluxo e trata falhas
Microserviços: Executam transações locais e compensações
Implementação em C# Usando Orquestração
Definir Modelos
public class Order {
public int Id { get; set; }
public string ProductId { get; set; }
public int Quantity { get; set; }
public string Status { get; set; }
}
public class Payment {
public int OrderId { get; set; }
public decimal Amount { get; set; }
public string Status { get; set; }
}
Controlador Orquestrador Saga
[ApiController]
[Route("[controller]")]
public class SagaController : ControllerBase {
private readonly IHttpClientFactory _httpClientFactory;
public SagaController(IHttpClientFactory httpClientFactory) {
_httpClientFactory = httpClientFactory;
}
[HttpPost]
public async Task<IActionResult> ProcessOrder([FromBody] Order order) {
order.Status = "Pending";
var orderResponse = await CallService("OrderService", order);
if (!orderResponse.IsSuccessStatusCode)
return BadRequest("Order creation failed.");
var paymentResponse = await CallService("PaymentService", new Payment { OrderId = order.Id, Amount = 100 });
if (!paymentResponse.IsSuccessStatusCode) {
await CallService("OrderService/cancel", order); // Compensação
return BadRequest("Payment failed.");
}
var inventoryResponse = await CallService("InventoryService", order);
if (!inventoryResponse.IsSuccessStatusCode) {
await CallService("PaymentService/refund", new { OrderId = order.Id }); // Compensação
await CallService("OrderService/cancel", order); // Compensação
return BadRequest("Inventory reservation failed.");
}
return Ok("Order processed successfully.");
}
private async Task<HttpResponseMessage> CallService(string serviceName, object payload) {
var client = _httpClientFactory.CreateClient();
return await client.PostAsJsonAsync($"http://localhost:5000/{serviceName}", payload);
}
}
Propósito: Mudanças de estado são capturadas como uma sequência de eventos em vez de armazenar o estado atual diretamente. Eventos são imutáveis e armazenados em um log de apêndice apenas, com estado do sistema reconstruído pela reprodução de eventos.
Visão Geral da Arquitetura
Cliente → Manipulador de Comando → Armazenamento de Eventos → Manipulador de Eventos → Modelo de Leitura
Componentes-chave:
Cliente: Envia comandos (ex: CreateAccount)
Manipulador de Comando: Valida e transforma comandos em eventos
Armazenamento de Eventos: Persiste eventos de forma imutável
Manipulador de Eventos: Se inscreve em eventos e atualiza projeções
Implementação em C#
Definir Interface e Evento
public interface IEvent {
DateTime Timestamp { get; }
}
public class AccountCreatedEvent : IEvent {
public Guid AccountId { get; }
public string AccountName { get; }
public DateTime Timestamp { get; }
public AccountCreatedEvent(Guid accountId, string accountName) {
AccountId = accountId;
AccountName = accountName;
Timestamp = DateTime.UtcNow;
}
}
Implementação do Armazenamento de Eventos
public interface IEventStore {
void SaveEvent(IEvent @event);
IEnumerable<IEvent> GetEvents();
}
public class InMemoryEventStore : IEventStore {
private readonly List<IEvent> _events = new();
public void SaveEvent(IEvent @event) => _events.Add(@event);
public IEnumerable<IEvent> GetEvents() => _events;
}
Reconstruindo Estado a partir de Eventos
public class Account {
public Guid Id { get; private set; }
public string Name { get; private set; }
public void Apply(AccountCreatedEvent e) {
Id = e.AccountId;
Name = e.AccountName;
}
public static Account Rehydrate(IEnumerable<IEvent> events) {
var account = new Account();
foreach (var e in events) {
if (e is AccountCreatedEvent ace)
account.Apply(ace);
}
return account;
}
}
Propósito: Command Query Responsibility Segregation separa operações de leitura e escrita em modelos distintos, melhorando escalabilidade e simplificando código para domínios complexos.
Visão Geral da Arquitetura
Cliente → Controlador → [Manipulador de Comando | Manipulador de Consulta] → Armazenamento de Dados
Componentes-chave:
Controlador: Recebe requisições HTTP
Manipulador de Comando: Executa lógica de escrita
Manipulador de Consulta: Executa lógica de leitura
Armazenamento de Dados: Pode ser dividido em bancos de dados de leitura/escrita separados
Implementação em C# Usando MediatR
Instalar MediatR
dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
Definir Comando e Manipulador
public record CreateOrderCommand(string ProductId, int Quantity) : IRequest<Guid>;
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Guid> {
public async Task<Guid> Handle(CreateOrderCommand request, CancellationToken cancellationToken) {
var orderId = Guid.NewGuid();
// Salvar no BD (omitido por brevidade)
return await Task.FromResult(orderId);
}
}
Definir Consulta e Manipulador
public record GetOrderByIdQuery(Guid OrderId) : IRequest<Order>;
public class GetOrderByIdHandler : IRequestHandler<GetOrderByIdQuery, Order> {
public async Task<Order> Handle(GetOrderByIdQuery request, CancellationToken cancellationToken) {
// Buscar do BD (omitido por brevidade)
return await Task.FromResult(new Order { Id = request.OrderId, ProductId = "P123", Quantity = 2 });
}
}
Controlador de Exemplo
[ApiController]
[Route("[controller]")]
public class OrdersController : ControllerBase {
private readonly IMediator _mediator;
public OrdersController(IMediator mediator) => _mediator = mediator;
[HttpPost]
public async Task<IActionResult> Create(CreateOrderCommand command) {
var id = await _mediator.Send(command);
return Ok(id);
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(Guid id) {
var order = await _mediator.Send(new GetOrderByIdQuery(id));
return Ok(order);
}
}
Propósito: Monitora chamadas de serviço e "abre o circuito" quando falhas excedem um limite, bloqueando temporariamente requisições para dar ao serviço que está falhando tempo para se recuperar.
Estados do Circuito
Estado
Descrição
Fechado
Requisições fluem normalmente. Falhas são rastreadas
Aberto
Requisições são bloqueadas. Após um timeout, transiciona para Meio-Aberto
Meio-Aberto
Algumas requisições de teste são permitidas. Se bem-sucedidas, o circuito fecha novamente
Implementação em C# Usando Polly
Instalar Polly
dotnet add package Polly
Definir Política de Circuit Breaker
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromSeconds(30)
);
Usar Política com HttpClient
var httpClient = new HttpClient();
await circuitBreakerPolicy.ExecuteAsync(async () =>
{
var response = await httpClient.GetAsync("https://api.example.com/data");
response.EnsureSuccessStatusCode();
});
Propósito: Em vez de falhar imediatamente, o sistema retenta a operação algumas vezes antes de desistir. Especialmente útil para tratar falhas transitórias em sistemas nativos da nuvem e distribuídos.
Implementação em C# Usando Polly
Definir Política de Retry
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)), // Backoff exponencial
onRetry: (exception, timeSpan, retryCount, context) =>
{
Console.WriteLine($"Retry {retryCount} after {timeSpan.TotalSeconds}s due to: {exception.Message}");
});
Usar Política com HttpClient
await retryPolicy.ExecuteAsync(async () =>
{
var response = await httpClient.GetAsync("https://api.example.com/data");
response.EnsureSuccessStatusCode();
});
Benefícios:
Trata falhas transitórias como timeouts ou limitação de taxa
Melhora confiabilidade sem sobrecarregar serviços
Estratégia de retry, delay e backoff customizável
Considerações:
Evite retenta em erros não-transitórios (ex: 404 Não Encontrado)
Use backoff exponencial para prevenir tempestades de retry
Combine com Circuit Breaker para resiliência aprimorada
Propósito: Como compartimentos no casco de um navio, este padrão isola componentes ou cargas de trabalho para conter falhas, prevenindo esgotamento de recursos ou falhas em cascata que afetem toda a aplicação.
Implementação em C# Usando SemaphoreSlim
public class ServiceCaller
{
private static SemaphoreSlim semaphore = new SemaphoreSlim(10); // Max 10 chamadas simultâneas
public async Task<string> MakeServiceCallAsync()
{
await semaphore.WaitAsync();
try
{
// Simular chamada de serviço externo
var result = await ExternalService.MakeRequestAsync();
return $"Service call successful: {result}";
}
catch (Exception ex)
{
return $"Service call failed: {ex.Message}";
}
finally
{
semaphore.Release();
}
}
}
Benefícios:
Isolamento de Falhas: Limita o raio de impacto de falhas
Estabilidade Melhorada: Previne esgotamento de recursos
Resiliência: Mantém componentes não afetados funcionando
Propósito: Executa um processo ou container secundário (o "sidecar") ao lado de um microserviço para tratar preocupações transversais como logging, monitoramento, configuração ou segurança sem tocar no código do serviço principal.
Implementação em C# Usando Dapr
Instalar Dapr CLI
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash
dapr init
Executar Microserviço com Sidecar Dapr
dapr run --app-id orderservice --app-port 5000 dotnet run
Usar SDK Dapr em .NET
var daprClient = new DaprClientBuilder().Build();
await daprClient.PublishEventAsync("pubsub", "order-topic", newOrder);
Benefícios:
Modularidade: Separação clara de interesses
Reusabilidade: Sidecars podem ser reutilizados entre serviços
Escalabilidade: Sidecars escalam independentemente
Observabilidade: Mais fácil monitorar e rastrear
Propósito: Inspirado na árvore ficus estranguladora, este padrão permite substituição gradual de partes de sistemas legados com novas funcionalidades sem interromper usuários, usando uma camada de fachada ou proxy para rotear requisições.
Visão Geral da Arquitetura
Cliente → Fachada → [Sistema Legado | Sistema Novo]
Componentes-chave:
Cliente: Continua usando a mesma interface
Fachada: Roteia requisições para serviços legados ou novos
Sistema Legado: Trata funcionalidade não migrada
Sistema Novo: Hospeda componentes modernizados
Implementação em C#
Controlador Fachada
[ApiController]
[Route("{*url}")]
public class FacadeController : ControllerBase {
private readonly IHttpClientFactory _httpClientFactory;
public FacadeController(IHttpClientFactory httpClientFactory) {
_httpClientFactory = httpClientFactory;
}
[HttpGet]
public async Task<IActionResult> RouteRequest(string url) {
var targetUrl = url.StartsWith("new/")
? $"http://localhost:6000/{url}" // Sistema novo
: $"http://localhost:5000/{url}"; // Sistema legado
var client = _httpClientFactory.CreateClient();
var response = await client.GetAsync(targetUrl);
return StatusCode((int)response.StatusCode, await response.Content.ReadAsStringAsync());
}
}
Configurar Serviços
builder.Services.AddHttpClient();
Casos de Uso:
Migrar APIs legadas para .NET 8 enquanto manter .NET 6 em produção
Refatorar gradualmente lógica de negócio monolítica em microserviços
Suportar múltiplas versões de uma aplicação sem quebrar clientes
Este guia cobre padrões essenciais de design de microserviços organizados em quatro categorias-chave:
Padrões de Comunicação: Tratam comunicação entre serviços e interações com clientes
Padrões de Gerenciamento de Dados: Gerenciam dados distribuídos e transações
Padrões de Resiliência: Construem sistemas tolerantes a falhas e robustos
Padrões de Implantação e Operacional: Suportam evolução do sistema e preocupações operacionais
Cada padrão aborda desafios específicos em sistemas distribuídos e fornece soluções comprovadas com implementações práticas em C#. Escolha os padrões apropriados baseado nos requisitos, complexidade e objetivos arquiteturais do seu sistema.