Introducao: Protocolo e Decisao de Arquitetura
Em sistemas corporativos construidos com ASP.NET Core, a escolha do protocolo de comunicacao nao e um detalhe tecnico isolado. Ela influencia diretamente latencia percebida, custo de infraestrutura, estrategia de testes, observabilidade, forma de evolucao de contratos e ate risco de incidentes em producao.
O erro mais comum em times iniciantes e escolher protocolo por preferencia pessoal ("gosto de GraphQL", "REST e mais facil") sem conectar a decisao ao caso de uso. Em arquitetura profissional, o fluxo correto e o inverso: primeiro se modela o comportamento do negocio e as restricoes nao funcionais (SLO, volume, frequencia, acoplamento); depois se escolhe o protocolo mais adequado.
Regra de ouro da aula
Protocolos sao adaptadores de transporte. A regra de negocio deve ficar desacoplada no core da aplicacao (handlers, dominio e servicos), nao no controller/resolver.
1. Mapa de Protocolos e Criterios de Escolha
| Opcao | Quando usar | Principal vantagem | Principal risco |
|---|---|---|---|
| REST (HTTP/JSON) | CRUD, integracoes externas, APIs publicas | Interoperabilidade e debug simples | Overfetch/underfetch, contratos menos estritos |
| gRPC (HTTP/2 + Protobuf) | Servico-servico de alta frequencia | Baixa latencia, payload menor, contrato forte | Debug mais tecnico e integracao browser indireta |
| GraphQL | Front-end com visoes de dados variaveis | Cliente pede exatamente os campos | N+1, query cara e governanca de schema |
| WebSocket | Atualizacao em tempo real | Canal full-duplex persistente | Escalar conexoes e estado de sessao |
No Projeto 9, o caminho mais realista e adotar combinacao de protocolos: REST/gRPC para comandos e leitura base, GraphQL para composicao de telas ricas e WebSocket para atualizacao live quando fizer sentido. O importante e preservar coerencia arquitetural no core.
2. REST no ASP.NET Core em Profundidade
2.1 Contrato HTTP, semantica e idempotencia
REST em ASP.NET Core deve usar verbos HTTP com semantica consistente. GET para leitura, POST para criacao/comando nao idempotente, PUT para substituicao idempotente e PATCH para alteracao parcial.
Idempotencia e central em resiliencia: se houver retry automatico de rede e o endpoint nao for idempotente, o mesmo comando pode ser executado duas vezes. Em produção, isso gera duplicidade de pagamento, pedido, envio de evento etc.
// DTOs
public record CriarPedidoRequest(string ClienteId, List<ItemDto> Itens);
public record ItemDto(string ProdutoId, int Quantidade);
[ApiController]
[Route("api/pedidos")]
public class PedidosController : ControllerBase
{
[HttpPost]
public IActionResult Criar([FromBody] CriarPedidoRequest req)
{
// Regra de negocio no core (service/handler), nao no controller.
return Ok(new { pedidoId = "P123", status = "CRIADO" });
}
}2.2 Payload JSON e evolucao de API
POST /api/pedidos
Content-Type: application/json
{
"clienteId": "C1",
"itens": [
{ "produtoId": "P10", "quantidade": 2 },
{ "produtoId": "P20", "quantidade": 1 }
]
}Para evoluir API REST sem quebrar clientes: adicionar campos opcionais, manter contrato anterior por periodo de transicao, usar versionamento (URL, header ou media type) quando houver mudanca incompatÃvel e retornar erro padronizado com ProblemDetails.
2.3 Testes e riscos frequentes
- Testar status code, contrato de resposta e regras de validacao.
- Testar cenarios negativos: 400, 404, 409 e 422.
- Validar idempotencia quando houver retry.
- Controlar breaking changes com testes de contrato (OpenAPI/consumer tests).
Erro comum em REST
Misturar regra de negocio no controller. Resultado: endpoint grande, teste caro, manutencao fraca e acoplamento ao transporte.
3. gRPC no ASP.NET Core em Profundidade
3.1 Contract-first com Protobuf
Em gRPC, o contrato vem primeiro (arquivo .proto). Isso cria uma fronteira forte entre cliente e servidor: o compilador gera stubs e mensagens tipadas para ambas as pontas.
syntax = "proto3";
service PedidoService {
rpc CriarPedido (CriarPedidoRequest) returns (CriarPedidoResponse);
}
message CriarPedidoRequest {
string cliente_id = 1;
repeated Item itens = 2;
}
message Item {
string produto_id = 1;
int32 quantidade = 2;
}
message CriarPedidoResponse {
string pedido_id = 1;
string status = 2;
}3.2 Unary, streaming, deadlines e cancellation
Unary e a chamada mais simples: 1 request e 1 response. Em termos didaticos, e o equivalente gRPC de um endpoint REST tradicional.
Alem de unary, gRPC oferece streaming server, streaming client e streaming bidirecional. Isso reduz overhead em cenarios de telemetria, eventos e sincronizacao continua.
public class PedidoGrpcService : PedidoService.PedidoServiceBase
{
public override Task<CriarPedidoResponse> CriarPedido(
CriarPedidoRequest request, ServerCallContext context)
{
return Task.FromResult(new CriarPedidoResponse
{
PedidoId = "P123",
Status = "CRIADO"
});
}
}Teste unary (definicao pratica)
Teste unary verifica 1 requisicao -> 1 resposta, validando payload tipado, StatusCode gRPC e regras de erro (ex.: DeadlineExceeded).
3.3 Compatibilidade e versionamento
- Nao reutilizar numero de campo no
.proto. - Preferir adicao de novos campos opcionais a remocao agressiva.
- Controlar versao de contrato em pipeline (schema checks).
- Definir padrao de erros e metadados de correlacao.
4. GraphQL no ASP.NET Core em Profundidade
4.1 Schema, query e mutation
Em GraphQL, o cliente manda uma consulta declarando os campos desejados. O servidor retorna exatamente essa forma de dado. No ASP.NET Core, o stack comum e HotChocolate.
// Program.cs
builder.Services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>();
var app = builder.Build();
app.MapGraphQL("/graphql");
app.Run();// Query (request)
POST /graphql
Content-Type: application/json
{
"query": "query Pedido($id: String!) { pedidoPorId(id: $id) { pedidoId clienteId status itens { produtoId quantidade } } }",
"variables": { "id": "P123" }
}// Mutation (request)
POST /graphql
Content-Type: application/json
{
"query": "mutation Criar($input: CriarPedidoInput!) { criarPedido(input: $input) { pedidoId status clienteId } }",
"variables": {
"input": {
"clienteId": "C1",
"itens": [
{ "produtoId": "P10", "quantidade": 2 },
{ "produtoId": "P20", "quantidade": 1 }
]
}
}
}4.2 Autorizacao por campo e caso sensivel
Diferente de REST puro, GraphQL permite granularidade por campo. Isso e poderoso, mas exige politicas claras de autorizacao para evitar vazamento de dados sensiveis.
// Caso de teste: campo sensivel sem permissao
POST /graphql
Content-Type: application/json
{
"query": "query { usuario(id:\"U1\") { id nome salario } }"
}
// Resposta esperada (resumo)
{
"errors": [
{
"message": "Nao autorizado para acessar o campo 'salario'.",
"path": ["usuario", "salario"]
}
],
"data": {
"usuario": {
"id": "U1",
"nome": "Ana",
"salario": null
}
}
}4.3 Performance: N+1, DataLoader e complexidade
- Problema N+1: resolver filho fazendo query individual por item.
- Mitigacao: DataLoader, batch loading e cache por request.
- Controle de custo: depth limit, complexity limit e persisted queries.
- Observabilidade por resolver para detectar gargalos cedo.
Trade-off real do GraphQL
Flexibilidade para cliente aumenta responsabilidade do backend em governanca de schema, autorizacao fina e controle de performance.
5. Estrategia de Testing (Black Box Primeiro)
Nesta aula, o modelo central e black box: validar entrada, saida e comportamento observavel, sem depender de detalhes internos de implementacao. Isso aumenta robustez dos testes frente a refatoracoes.
| Tecnologia | O que validar primeiro | Tipos de teste | Falha comum |
|---|---|---|---|
| REST | Status code + payload + validacao | Unit + Integration + Contract | Quebra de contrato sem aviso |
| GraphQL | Schema + auth por campo + custo query | Schema tests + Integration + E2E query | Exposicao de campo sensivel |
| gRPC | Compatibilidade .proto + StatusCode | Contract + Integration + Streaming | Breaking change de campo |
| MediatR | Comportamento de handler/pipeline | Unit de handlers + behavior tests | Regra vazando para controller |
| Filas | Idempotencia + retry + DLQ | Integration com broker + falha induzida | Duplicidade e perda silenciosa |
Exemplos objetivos de casos de teste
| Situacao | Entrada de teste | Resultado esperado |
|---|---|---|
| REST com payload invalido | POST sem clienteId |
HTTP 400 com erro de validacao |
| GraphQL com campo nao autorizado | Query pedindo salario sem permissao |
Erro de autorizacao + campo nulo/bloqueado |
| gRPC com timeout curto | Deadline menor que tempo do metodo | DeadlineExceeded |
| Fila com reentrega | Mesma mensagem processada 2x | Estado final sem duplicidade (idempotente) |
| Fila com falha persistente | Consumidor falha ate esgotar retry | Mensagem em DLQ + metrica incrementada |
Distribuicao recomendada de suite
Como baseline: ~70% testes unitarios, ~20% integracao e ~10% E2E. Ajustar conforme criticidade do dominio.
6. CQRS com MediatR e Filas
O papel de CQRS aqui e separar comandos (escrita) de consultas (leitura), permitindo evoluir performance, observabilidade e estrategia de consistencia sem contaminar o transporte.
// Command
public record RequestReportCommand(Guid DashboardId) : IRequest<Guid>;
// Handler
public class RequestReportHandler : IRequestHandler<RequestReportCommand, Guid>
{
private readonly IReportQueue _queue;
public RequestReportHandler(IReportQueue queue) => _queue = queue;
public async Task<Guid> Handle(RequestReportCommand cmd, CancellationToken ct)
{
var operationId = Guid.NewGuid();
await _queue.PublishAsync(new ReportRequested(operationId, cmd.DashboardId), ct);
return operationId;
}
}Padroes obrigatorios em mensageria
- Outbox: persistir evento junto da transacao para evitar perda entre banco e broker.
- Retry com jitter: tratar falha transiente sem sincronizar tempestade de retries.
- DLQ: isolar falha nao recuperavel para analise e replay controlado.
- Idempotencia: reprocessar mensagem sem duplicar efeito no estado.
Falha classica em filas
Consumidor sem idempotencia parece funcionar em homologacao e quebra em producao quando ocorre redelivery.
7. Observabilidade Operacional com OpenTelemetry + Prometheus
Sem telemetria, incidentes viram suposicao. Com OpenTelemetry, registramos traces, metrics e logs correlacionados por trace-id/correlation-id. Com Prometheus, coletamos series temporais para SLO, alertas e capacidade.
// Exemplo conceitual de setup no ASP.NET Core
builder.Services.AddOpenTelemetry()
.WithTracing(t => t
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation())
.WithMetrics(m => m
.AddAspNetCoreInstrumentation()
.AddRuntimeInstrumentation()
.AddPrometheusExporter());
app.MapPrometheusScrapingEndpoint(); // /metricsMetricas minimas recomendadas
| Metrica | Leitura operacional | Sinal de risco |
|---|---|---|
request_duration_ms (p95/p99) |
Experiencia de cliente em chamadas sincronas | Crescimento continuo sem ganho de throughput |
queue_depth / consumer_lag |
Saude do processamento assincrono | Fila cresce por janela prolongada |
dlq_messages_total |
Falhas nao recuperaveis | Pico apos deploy/schema change |
handler_failures_total |
Confiabilidade do core (CQRS) | Taxa acima do error budget |
retry_attempts_total |
Instabilidade transiente de dependencia | Aumento cronico com latencia alta |
8. Fluxo End-to-End Consolidado
A arquitetura final trabalhada na aula integra transporte, core de negocio e operacao:
Fluxo principal
Cliente -> REST/gRPC/GraphQL -> MediatR (Command/Query) -> Outbox -> Broker (Kafka/RabbitMQ) -> Worker -> Banco/Read Model -> WebSocket para atualizacao live.
Fluxo de observabilidade
OpenTelemetry (API + Worker + headers) -> Prometheus (/metrics) -> Alertas por SLO (latencia, erro, lag, DLQ).
Note que o protocolo pode mudar sem reescrever a regra de negocio, desde que o core permaneça isolado. Esse e o ganho arquitetural central do modelo.
9. Atividade de Sala: Entrega e Rubrica
Instrucao oficial da entrega
Implementar um fluxo end-to-end funcional em uma rota ja existente do Projeto 9, com validacao black box (entrada/saida), usando o protocolo escolhido pelo grupo (REST, gRPC ou GraphQL).
Tempo de execucao: 40 minutos para desenvolvimento e 20 minutos para apresentacao.
| Item avaliado | Descricao objetiva | Evidencia esperada |
|---|---|---|
| Fluxo principal | Codigo end-to-end passando por rota existente | Execucao completa sem quebra de fluxo |
| Teste black box | Foco em entrada/saida e comportamento | Request, resposta e criterio de aceite claro |
| Core da aplicacao | Uso de MediatR no command/query | Handler executando regra de negocio |
| Assincrono | Fila quando aplicavel ao caso | Publicacao/consumo + controle de falha |
| Observabilidade | Metricas Prometheus minimas | Evidencia de latencia, erro e/ou lag |
| Defesa tecnica | Justificar escolha do protocolo | Trade-off explicado de forma objetiva |
Meta pedagogica
Nao basta "funcionar". O objetivo e demonstrar criterio de arquitetura, previsibilidade operacional e qualidade de teste em um recorte realista de backend Core.