Loading repository data…
Loading repository data…
EngSoftUnisanta / repository
Roteiro de aprendizagem sobre Vertical Slice Architecture usando .NET 8 e C#. Projeto didático completo com teoria, exemplos práticos e atividade de implementação.
Bem-vindo ao roteiro de aprendizagem sobre Vertical Slice Architecture! Este projeto foi criado para ajudá-lo a entender e aplicar esta arquitetura moderna usando .NET 9 e C#.
Na arquitetura tradicional, organizamos o código por camadas técnicas:
📁 Projeto
├── 📁 Controllers
│ ├── ProdutoController.cs
│ └── CategoriaController.cs
├── 📁 Services
│ ├── ProdutoService.cs
│ └── CategoriaService.cs
├── 📁 Repositories
│ ├── ProdutoRepository.cs
│ └── CategoriaRepository.cs
└── 📁 Models
├── Produto.cs
└── Categoria.cs
Problema: Quando precisamos adicionar ou modificar uma funcionalidade, precisamos mexer em arquivos espalhados por várias camadas diferentes.
Na Vertical Slice Architecture, organizamos o código por funcionalidades de negócio:
📁 Projeto
├── 📁 Funcionalidades
│ ├── 📁 Produtos
│ │ ├── Produto.cs
│ │ ├── 📁 ObterTodosProdutos
│ │ │ ├── ObterTodosProdutosHandler.cs
│ │ │ └── ObterTodosProdutosEndpoint.cs
│ │ ├── 📁 CriarProduto
│ │ │ ├── CriarProdutoHandler.cs
│ │ │ └── CriarProdutoEndpoint.cs
│ │ └── ...
│ └── 📁 Categorias
│ ├── Categoria.cs
│ └── ...
└── 📁 Infraestrutura
└── BancoDeDados.cs
Vantagens:
✅ Bom para:
❌ Talvez não seja ideal para:
Este projeto demonstra a Vertical Slice Architecture com um sistema simples de catálogo de produtos:
AprendizadoVerticalSlice/
├── 📁 Funcionalidades/
│ ├── 📁 Produtos/
│ │ ├── Produto.cs # Entidade Produto
│ │ ├── 📁 ObterTodosProdutos/
│ │ │ ├── ObterTodosProdutosHandler.cs # Lógica de negócio
│ │ │ └── ObterTodosProdutosEndpoint.cs # Configuração do endpoint
│ │ ├── 📁 ObterProdutoPorId/
│ │ │ ├── ObterProdutoPorIdHandler.cs
│ │ │ └── ObterProdutoPorIdEndpoint.cs
│ │ ├── 📁 CriarProduto/
│ │ │ ├── CriarProdutoHandler.cs
│ │ │ └── CriarProdutoEndpoint.cs
│ │ ├── 📁 AtualizarProduto/
│ │ │ ├── AtualizarProdutoHandler.cs
│ │ │ └── AtualizarProdutoEndpoint.cs
│ │ └── 📁 ExcluirProduto/
│ │ ├── ExcluirProdutoHandler.cs
│ │ └── ExcluirProdutoEndpoint.cs
│ └── 📁 Categorias/
│ ├── Categoria.cs # Entidade Categoria
│ ├── 📁 ObterTodasCategorias/
│ │ ├── ObterTodasCategoriasHandler.cs
│ │ └── ObterTodasCategoriasEndpoint.cs
│ └── 📁 CriarCategoria/
│ ├── CriarCategoriaHandler.cs
│ └── CriarCategoriaEndpoint.cs
├── 📁 Infraestrutura/
│ └── BancoDeDados.cs # Contexto do EF Core
├── Program.cs # Configuração da aplicação
└── appsettings.json # Configurações
Produto.cs, Categoria.cs): Representa a entidade do domínio*Handler.cs): Contém a lógica de negócio da funcionalidade*Endpoint.cs): Define e configura o endpoint HTTPO Handler é o coração de cada slice. Ele contém toda a lógica de negócio para executar uma funcionalidade específica.
Características:
Exemplo:
public class CriarProdutoHandler
{
private readonly BancoDeDados _bancoDeDados;
public CriarProdutoHandler(BancoDeDados bancoDeDados)
{
_bancoDeDados = bancoDeDados;
}
public async Task<Resultado> Executar(CriarProdutoRequisicao requisicao)
{
// Toda a lógica para criar um produto fica aqui
// Validações, transformações, persistência, etc.
}
}
O Endpoint é responsável por configurar a rota HTTP que expõe a funcionalidade.
Características:
Exemplo:
public static class CriarProdutoEndpoint
{
public static IEndpointRouteBuilder MapCriarProduto(this IEndpointRouteBuilder endpoints)
{
endpoints.MapPost("/api/produtos", async (
[FromBody] CriarProdutoRequisicao requisicao,
[FromServices] CriarProdutoHandler handler) =>
{
var resultado = await handler.Executar(requisicao);
return Results.Created($"/api/produtos/{resultado.Id}", resultado);
});
return endpoints;
}
}
Records são estruturas imutáveis perfeitas para representar dados que trafegam pela API.
Request Record: Define o que o cliente envia
public record CriarProdutoRequisicao(
string Nome,
decimal Preco,
int CategoriaId
);
Response Record: Define o que a API retorna
public record ProdutoResposta(
int Id,
string Nome,
decimal Preco
);
Cada Handler é registrado no container de DI do ASP.NET Core:
// No Program.cs
builder.Services.AddScoped<CriarProdutoHandler>();
Isso permite que:
Vamos analisar como funciona a funcionalidade de Criar Produto passo a passo.
CriarProdutoHandler.cs)public class CriarProdutoHandler
{
private readonly BancoDeDados _bancoDeDados;
public CriarProdutoHandler(BancoDeDados bancoDeDados)
{
_bancoDeDados = bancoDeDados;
}
public async Task<(bool Sucesso, string? Erro, CriarProdutoResposta? Produto)>
Executar(CriarProdutoRequisicao requisicao)
{
// 1. Validações
if (string.IsNullOrWhiteSpace(requisicao.Nome))
return (false, "O nome do produto é obrigatório.", null);
if (requisicao.Preco <= 0)
return (false, "O preço deve ser maior que zero.", null);
// 2. Validação de relacionamento
var categoriaExiste = await _bancoDeDados.Categorias
.AnyAsync(c => c.Id == requisicao.CategoriaId);
if (!categoriaExiste)
return (false, $"Categoria não encontrada.", null);
// 3. Criação da entidade
var novoProduto = new Produto
{
Nome = requisicao.Nome,
Preco = requisicao.Preco,
// ... outros campos
};
// 4. Persistência
_bancoDeDados.Produtos.Add(novoProduto);
await _bancoDeDados.SaveChangesAsync();
// 5. Retorno
var resposta = new CriarProdutoResposta(
novoProduto.Id,
novoProduto.Nome,
novoProduto.Preco
);
return (true, null, resposta);
}
}
O que está acontecendo:
CriarProdutoEndpoint.cs)public static class CriarProdutoEndpoint
{
public static IEndpointRouteBuilder MapCriarProduto(this IEndpointRouteBuilder endpoints)
{
endpoints.MapPost("/api/produtos", async (
[FromBody] CriarProdutoRequisicao requisicao,
[FromServices] CriarProdutoHandler handler) =>
{
var (sucesso, erro, produto) = await handler.Executar(requisicao);
if (!sucesso)
return Results.BadRequest(new { mensagem = erro });
return Results.Created($"/api/produtos/{produto!.Id}", produto);
})
.WithName("CriarProduto")
.WithTags("Produtos")
.Produces<CriarProdutoResposta>(201)
.Produces(400);
return endpoints;
}
}
O que está acontecendo:
/api/produtos// Registra o Handler
builder.Services.AddScoped<CriarProdutoHandler>();
// Mapeia o Endpoint
app.MapCriarProduto();
Vertical Slice:
CriarProduto/
├── CriarProdutoHandler.cs ← Toda lógica aqui
└── CriarProdutoEndpoint.cs ← Configuração HTTP aqui
✅ Tudo relacionado a "Criar Produto" está em um único lugar!
Camadas Tradicionais:
Controllers/ProdutoController.cs ← Endpoint
Services/ProdutoService.cs ← Lógica
Repositories/ProdutoRepository.cs ← Acesso a dados
Validators/ProdutoValidator.cs ← Validações
❌ Para entender "Criar Produto", preciso abrir 4+ arquivos diferentes!
git clone <url-do-repositorio>
cd AprendizadoVerticalSlice
dotnet restore
dotnet run
Abra seu navegador em:
http://localhost:5156/scalar/v1
(A porta pode variar - verifique no console)
A interface Scalar oferece uma documentação interativa moderna onde você pode:
GET /api/categorias
POST /api/produtos
Content-Type: application/json
{
"nome": "Mouse Gamer",
"descricao": "Mouse gamer RGB",
"preco": 150.00,
"qu