REST, gRPC, GraphQL, WebSocket, TCP/UDP e WebTransport conectados a CQRS + MediatR + filas + telemetria.
| Protocolo/Estilo | Melhor para | Trade-off principal |
|---|---|---|
| REST (HTTP) | CRUD, APIs publicas e interoperabilidade | Over/under fetching e contratos mais frouxos |
| gRPC (HTTP/2 + Protobuf) | Servico-servico com baixa latencia | Debug e consumo no browser exigem gateway/adaptacao |
| GraphQL | Frontends com multiplas visoes de dados | Governanca de schema e custo de queries complexas |
| WebSocket | Tempo real full-duplex | Estado de conexao e escalabilidade de conexoes abertas |
| TCP/UDP Sockets | Protocolos custom e baixa latencia extrema | Maior complexidade operacional |
| WebTransport (HTTP/3/QUIC) | Streams + datagrams em browser moderno | Ecossistema ainda em consolidacao |
Como você define
// DTO (objeto do body JSON)
public record CriarPedidoRequest(string ClienteId, List<ItemDto> Itens);
public record ItemDto(string ProdutoId, int Quantidade);// Controller REST
[ApiController]
[Route("api/pedidos")]
public class PedidosController : ControllerBase
{
[HttpPost]
public IActionResult Criar([FromBody] CriarPedidoRequest req)
{
// regra de negocio...
return Ok(new { pedidoId = "P123", status = "CRIADO" });
}
}Payload REST (o que vai no body)
POST /api/pedidos
Content-Type: application/json{
"clienteId": "C1",
"itens": [
{ "produtoId": "P10", "quantidade": 2 },
{ "produtoId": "P20", "quantidade": 1 }
]
}Resposta:
{
"pedidoId": "P123",
"status": "CRIADO"
}Aqui o payload é JSON legível.
2. gRPC no backend .NET
Como você define (contrato .proto)
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;
}O .NET gera classes C# automaticamente desse .proto.
Implementação do servidor
public class PedidoGrpcService : PedidoService.PedidoServiceBase
{
public override Task<CriarPedidoResponse> CriarPedido(
CriarPedidoRequest request, ServerCallContext context)
{
// regra de negocio...
return Task.FromResult(new CriarPedidoResponse
{
PedidoId = "P123",
Status = "CRIADO"
});
}
}Chamada do cliente gRPC
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new PedidoService.PedidoServiceClient(channel);
var resp = await client.CriarPedidoAsync(new CriarPedidoRequest
{
ClienteId = "C1",
Itens = { new Item { ProdutoId = "P10", Quantidade = 2 },
new Item { ProdutoId = "P20", Quantidade = 1 } }
});3. Então qual é o “payload” no gRPC?
Logicamente é o mesmo dado (cliente, itens, etc).
A diferença é o formato na rede:
Ou seja:
Cliente pede exatamente os campos necessarios.
Canal persistente full-duplex para tempo real.
GraphQL para leitura + WebSocket para subscription/eventos.
Exemplo com HotChocolate
// Program.cs
builder.Services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>();
var app = builder.Build();
app.MapGraphQL("/graphql");
app.Run();// Tipos de entrada/saida
public record ItemDto(string ProdutoId, int Quantidade);
public record PedidoDto(string PedidoId, string ClienteId, List<ItemDto> Itens, string Status);
public record CriarPedidoInput(string ClienteId, List<ItemDto> Itens);
public class Query
{
public PedidoDto PedidoPorId(string id) =>
new("P123", "C1", new List<ItemDto> { new("P10", 2), new("P20", 1) }, "CRIADO");
}
public class Mutation
{
public PedidoDto CriarPedido(CriarPedidoInput input) =>
new("P123", input.ClienteId, input.Itens, "CRIADO");
}Request HTTP para GraphQL
POST /graphql
Content-Type: application/json{
"query": "query Pedido($id: String!) { pedidoPorId(id: $id) { pedidoId clienteId status itens { produtoId quantidade } } }",
"variables": {
"id": "P123"
}
}Resposta:
{
"data": {
"pedidoPorId": {
"pedidoId": "P123",
"clienteId": "C1",
"status": "CRIADO",
"itens": [
{ "produtoId": "P10", "quantidade": 2 },
{ "produtoId": "P20", "quantidade": 1 }
]
}
}
}Criando um pedido (mesmo caso do REST/gRPC)
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 }
]
}
}
}Resposta:
{
"data": {
"criarPedido": {
"pedidoId": "P123",
"status": "CRIADO",
"clienteId": "C1"
}
}
}/api/pedidos/{id}, /api/clientes/{id}./graphql.| Tecnologia | O que validar primeiro | Testes principais | Risco comum |
|---|---|---|---|
| REST | Contrato HTTP, status code e validacao | Unit + Integration + Contract | Quebrar clientes por mudanca de payload |
| GraphQL | Schema, resolvers e autorizacao por campo | Schema snapshot + Integration + E2E de query | N+1 e exposicao indevida de dados |
| gRPC | Compatibilidade .proto e codigos de erro | Contract + Integration + teste de streaming | Breaking change de campo/mensagem |
| MediatR | Comportamento de command/query handler | Unit de handler + teste de pipeline behavior | Regra dispersa fora do handler |
| Filas | Idempotencia, retry e DLQ | Integration com broker + teste de falha | Duplicidade e perda silenciosa de mensagem |
| Situacao | Caso de teste | Resultado esperado |
|---|---|---|
| REST - payload invalido | POST /api/pedidos sem clienteId |
HTTP 400 com mensagem de validacao |
| GraphQL - campo nao autorizado | Query pedindo campo sensivel sem permissao | Erro GraphQL de autorizacao e campo bloqueado |
| gRPC - deadline excedido | Chamada com timeout curto em metodo lento | Status DeadlineExceeded |
| MediatR - validacao de command | Command com dados inconsistentes no pipeline behavior | Falha de validacao sem executar o handler |
| Fila - processamento duplicado | Reentrega da mesma mensagem para o consumidor | Nenhuma duplicidade no banco (idempotencia) |
| Fila - falha persistente | Consumidor falha ate esgotar retries | Mensagem enviada para DLQ + metrica incrementada |
var res = await client.PostAsJsonAsync("/api/pedidos", req);
res.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await res.Content.ReadFromJsonAsync<PedidoResponse>();
body!.Status.Should().Be("CRIADO");var payload = new {
query = "query { pedidoPorId(id:\"P123\"){ pedidoId status } }"
};
var res = await client.PostAsJsonAsync("/graphql", payload);
res.EnsureSuccessStatusCode();POST /graphql
Content-Type: application/json
{
"query": "query { usuario(id:\"U1\") { id nome salario } }"
}Observacao: salario e um campo sensivel.
{
"errors": [
{
"message": "Nao autorizado para acessar o campo 'salario'.",
"path": ["usuario", "salario"]
}
],
"data": {
"usuario": {
"id": "U1",
"nome": "Ana",
"salario": null
}
}
}// 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;
}
}
// Controller REST
[HttpPost("reports")]
public async Task<IActionResult> Create([FromBody] CreateReportDto dto)
{
var operationId = await _mediator.Send(new RequestReportCommand(dto.DashboardId));
return Accepted($"/operations/{operationId}");
}/metrics.| Metrica | Por que importa | Sinal de risco |
|---|---|---|
| request_duration_ms (p95/p99) | Experiencia do cliente no protocolo sincrono | p95 crescente sem aumento de throughput |
| queue_depth / consumer_lag | Saude do processamento assincrono | Fila cresce por tempo prolongado |
| dlq_messages_total | Erros nao recuperaveis | picos apos deploy ou mudanca de schema |
| handler_failures_total | Confiabilidade dos handlers CQRS | taxa de erro acima do error budget |
| retry_attempts_total | Instabilidade transitora de dependencias | aumento continuo e latencia alta |
Requisitos da entrega
| Item | Descricao | Criterio de aceite |
|---|---|---|
| Fluxo principal | Codigo end-to-end funcional passando por rota ja existente do Projeto 9. | Execucao completa sem quebra do fluxo. |
| Modelo de teste | Validacao black box (entrada e saida), sem acoplamento ao internals. | Evidencia de request, resposta e comportamento esperado. |
| Protocolo | Escolha livre do grupo: REST, gRPC ou GraphQL. | Justificar a escolha para o caso de uso implementado. |
| Camadas obrigatorias | Rota -> MediatR (command/query) -> fila (quando aplicavel) -> persistencia/read model. | Demonstrar passagem por cada etapa prevista. |
| Observabilidade | Metricas Prometheus minimas para o fluxo implementado. | Exibir metricas coletadas (latencia, erro, lag ou equivalente). |