Padrão de resultado e fluxo de resposta
ResumoExceções para controlar fluxo comum têm custo. O Result Pattern representa sucesso/falha como dados, evitando exceções em caminhos esperados e melhorando o desempenho sob carga.
1. Objetivos de Aprendizagem#
- Entender o custo de usar exceções para fluxo esperado.
- Implementar um tipo
Result/ApiBaseResponse. - Refatorar serviço e controller para o fluxo baseado em resultado.
2. Pré-requisitos#
- Criação de recursos e exceções de domínio (Módulos 06/09).
3. Conceito#
Lançar exceções é caro (captura de stack trace). Para fluxos esperados e frequentes (ex.: "não encontrado" num endpoint muito acessado), o Result Pattern retorna um objeto que descreve o desfecho, e o controller decide o status code. O porquê: menor overhead e fluxo explícito. Exceções permanecem para o que é realmente excepcional.
Fluxo baseado em resultado, sem exceções no caminho esperado:
4. Mão na Massa#
4.1. Setup#
Sem pacotes novos (implementação própria).
4.2. Implementação Passo a Passo#
- Tipos de resposta:
public abstract class ApiBaseResponse
{
public bool Success { get; }
protected ApiBaseResponse(bool success) => Success = success;
}
public sealed class ApiOkResponse<T> : ApiBaseResponse
{
public T Result { get; }
public ApiOkResponse(T result) : base(true) => Result = result;
}
public sealed class ProdutoNotFoundResponse : ApiBaseResponse
{
public Guid Id { get; }
public ProdutoNotFoundResponse(Guid id) : base(false) => Id = id;
}
- Serviço retornando
ApiBaseResponse:
public async Task<ApiBaseResponse> GetAsync(Guid id, bool trackChanges)
{
var produto = await _repo.Produto.GetByIdAsync(id, trackChanges);
if (produto is null) return new ProdutoNotFoundResponse(id);
return new ApiOkResponse<ProdutoDto>(_mapper.Map<ProdutoDto>(produto));
}
- Controller traduz o resultado em HTTP:
[HttpGet("{id:guid}")]
public async Task<IActionResult> Get(Guid id)
{
var response = await _service.Produto.GetAsync(id, false);
if (!response.Success && response is ProdutoNotFoundResponse nf)
return NotFound($"Produto {nf.Id} não encontrado.");
return Ok(((ApiOkResponse<ProdutoDto>)response).Result);
}
4.3. Executando#
Sob carga, endpoints que antes lançavam exceções para "não encontrado" passam a responder sem o custo delas.
5. Exemplo Completo#
Um helper no controller pode centralizar a tradução de ApiBaseResponse → IActionResult, evitando if/cast repetidos.
6. Boas Práticas e Armadilhas#
| Faça | Evite |
|---|---|
| Result Pattern para fluxos esperados/frequentes | Exceções em caminhos comuns de alto tráfego |
| Reservar exceções para o realmente excepcional | Trocar tudo por Result sem necessidade |
| Medir antes e depois (benchmark) | Otimização sem evidência |
Atençãonão é "exceções são ruins". Para erros raros e de infraestrutura, exceções + handler global (Módulo 19) continuam sendo a melhor escolha. Result brilha em fluxos esperados de alto volume.
7. Segurança e Produção#
- Otimize com base em métricas reais (profiling), não em suposições.
- Mantenha o handler global para o que escapa do fluxo de resultado.
8. Exercícios#
- Fácil: adicione um
ApiBadRequestResponse. - Médio: crie um helper de tradução
ProcessErrornoControllerBase. - Desafio: faça um benchmark (BenchmarkDotNet) comparando exceção vs. Result no caminho "não encontrado".
9. Resumo#
O Result Pattern evita o custo de exceções em fluxos esperados de alto tráfego, retornando desfechos como dados que o controller traduz em status HTTP. Exceções permanecem para o excepcional.
10. Próximos Passos#
Módulo 44 (Bônus): CQRS e MediatR.
11. Referências#
- Microsoft Learn — Boas práticas de desempenho no ASP.NET Core
- Microsoft Learn — Tratamento de exceções em C#
- Documentação — BenchmarkDotNet