O que TestRepo faz?
Como construir e manter a Arquitetura Vertical Slice no .NET — anatomia das fatias (variantes de arquivo único e pasta), despacho direto de handlers sem um mediator, preocupações transversais, o que as fatias podem compartilhar, como as fatias interagem com um modelo de domínio ou camadas Clean, e testes em nível de fatia. Use ao adicionar ou modificar um caso de uso/endpoint/recurso em qualquer módulo organizado por fatias verticais, ou quando a duplicação entre fatias está sendo discutida.
Anatomia de uma Fatia
Uma fatia vertical representa uma única funcionalidade ponta a ponta. Ela pode ser implementada como:
- Arquivo Único: Um único arquivo
.cscontendo Request, Handler e Response. - Pasta: Uma pasta contendo arquivos separados para Request, Handler, Response e, opcionalmente, Validação e Mapeamento.
Exemplo de Fatia (Arquivo Único)
public class GetUserById
{
public record Query(int UserId);
public record Response(int Id, string Name, string Email);
public class Handler
{
public async Task<Response> Handle(Query query, IUserRepository repo)
{
var user = await repo.GetByIdAsync(query.UserId);
return new Response(user.Id, user.Name, user.Email);
}
}
}
Despacho Direto sem Mediator
Evite o uso de um mediator. Em vez disso, injete diretamente o Handler como um serviço:
// Registro no DI
services.AddScoped<GetUserById.Handler>();
// Uso no Controller ou Endpoint
public class UsersController : ControllerBase
{
[HttpGet("{userId}")]
public async Task<IActionResult> Get(int userId, [FromServices] GetUserById.Handler handler)
{
var result = await handler.Handle(new GetUserById.Query(userId));
return Ok(result);
}
}
Preocupações Transversais
Preocupações transversais como logging, validação e autorização podem ser aplicadas usando:
- Decorators: Envolvendo o handler com um decorator para adicionar comportamento extra.
- Filtros de Action: Aplicando globalmente ou por endpoint.
- Atributos: Usando atributos customizados nos handlers.
O que as Fatias Podem Compartilhar
- Modelo de Domínio: Entidades, Value Objects e Agregados são compartilhados.
- Interfaces de Repositório: Abstrações para acesso a dados.
- Contratos de Serviço: Interfaces para serviços externos.
- Configuração: Classes de configuração e constantes.
Interação com Camadas Clean ou DDD
Cada fatia pode acessar diretamente ou ser composta por elementos de uma arquitetura em camadas:
- Camada de Domínio: Contém as entidades, regras de negócio e interfaces.
- Camada de Aplicação: Contém os handlers de caso de uso (que são as fatias).
- Camada de Infraestrutura: Implementações de acesso a dados, serviços externos.
Exemplo de interação:
public class CreateOrderHandler
{
private readonly IOrderRepository _repo;
private readonly IEmailService _email;
public CreateOrderHandler(IOrderRepository repo, IEmailService email)
{
_repo = repo;
_email = email;
}
public async Task<Response> Handle(CreateOrderCommand command)
{
var order = Order.Create(command.CustomerId, command.Items); // Domínio
await _repo.AddAsync(order); // Infraestrutura
await _email.SendConfirmationAsync(order); // Infraestrutura
return new Response(order.Id);
}
}
Testes em Nível de Fatia
Teste cada fatia de forma isolada, mockando as dependências:
[Test]
public async Task GetUserById_ShouldReturnUser()
{
var repo = new Mock<IUserRepository>();
repo.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(new User { Id = 1, Name = "John", Email = "[email protected]" });
var handler = new GetUserById.Handler();
var result = await handler.Handle(new GetUserById.Query(1), repo.Object);
Assert.AreEqual("John", result.Name);
}
Considerações Sobre Duplicação
- Duplicação acidental: Quando duas fatias contêm lógica semelhante, considere extrair para um serviço compartilhado ou classe base.
- Duplicação intencional: Às vezes é aceitável se a lógica serve a contextos diferentes que podem evoluir independentemente.
- Ferramentas de análise: Use ferramentas como
Roslynanalyzers para detectar duplicação entre fatias.