API REST desenvolvida em .NET para gerenciamento de usuários, produtos, filiais e vendas, com aplicação de regras de negócio para cálculo de descontos, controle de status e operações CRUD.
O projeto foi desenvolvido utilizando princípios de DDD, separando regras de negócio, casos de uso, persistência e camada de apresentação.
A aplicação representa um sistema de vendas onde é possível gerenciar:
- Usuários
- Produtos
- Filiais
- Vendas
- Itens de venda
A principal regra de negócio está no módulo de vendas, onde o sistema calcula automaticamente descontos com base na quantidade de itens idênticos vendidos.
O desconto é aplicado por item da venda, considerando a quantidade de produtos idênticos:
| Quantidade de itens idênticos | Desconto |
|---|---|
| Menos de 4 itens | 0% |
| De 4 a 9 itens | 10% |
| De 10 a 20 itens | 20% |
| Acima de 20 itens | Não permitido |
- Não é permitido vender mais de 20 unidades do mesmo produto.
- Produtos repetidos na request são agrupados pelo
ProductId. - O valor total da venda é recalculado automaticamente.
- O preço utilizado na venda é o preço atual do produto no momento da operação.
- Itens removidos em uma atualização de venda são marcados como cancelados/inativos.
- Operações de delete são tratadas como soft delete/desativação lógica quando aplicável.
- .NET 8
- C#
- ASP.NET Core Web API
- Entity Framework Core
- PostgreSQL
- AutoMapper
- MediatR
- FluentValidation
- Rebus
- RabbitMQ
- Serilog
- Docker
- Docker Compose
- Swagger/OpenAPI
O projeto foi desenvolvido seguindo princípios de DDD, buscando separar as responsabilidades da aplicação em camadas bem definidas.
A ideia principal é manter o domínio da aplicação mais protegido e independente, concentrando nele as entidades, regras de negócio e contratos principais. A camada de aplicação coordena os casos de uso por meio de comandos e handlers, enquanto a camada de API fica responsável por receber requisições HTTP, validar entradas e retornar respostas.
A persistência fica isolada na camada de ORM, utilizando Entity Framework Core, e as dependências são registradas por meio do projeto de IoC.
Estrutura geral:
src/
├── SalesProject.WebApi
├── SalesProject.Application
├── SalesProject.Domain
├── SalesProject.ORM
├── SalesProject.IoC
└── SalesProject.Common
- DDD
- Repository Pattern
- CQRS simplificado com MediatR
- DTO Pattern
- FluentValidation
- AutoMapper
- Soft Delete
- Event-driven processing com Rebus/RabbitMQ
- Dependency Injection
- Global Exception Handling
- Logging estruturado com Serilog
Responsável pelo gerenciamento de usuários.
| Método | Rota | Descrição |
|---|---|---|
| GET | /api/users |
Lista usuários com paginação e filtros |
| GET | /api/users/{id} |
Busca usuário por ID |
| POST | /api/users |
Cria um novo usuário |
| PUT | /api/users/{id} |
Atualiza um usuário existente |
| DELETE | /api/users/{id} |
Remove/desativa um usuário |
Responsável pelo gerenciamento de produtos.
| Método | Rota | Descrição |
|---|---|---|
| GET | /api/products |
Lista produtos com paginação e filtros |
| GET | /api/products/{id} |
Busca produto por ID |
| POST | /api/products |
Cria um novo produto |
| PUT | /api/products/{id} |
Atualiza um produto existente |
| DELETE | /api/products/{id} |
Remove/desativa um produto |
Responsável pelo gerenciamento de filiais.
| Método | Rota | Descrição |
|---|---|---|
| GET | /api/branches |
Lista filiais com paginação e filtros |
| GET | /api/branches/{id} |
Busca filial por ID |
| POST | /api/branches |
Cria uma nova filial |
| PUT | /api/branches/{id} |
Atualiza uma filial existente |
| DELETE | /api/branches/{id} |
Remove/desativa uma filial |
Responsável pelo gerenciamento de vendas.
| Método | Rota | Descrição |
|---|---|---|
| GET | /api/sales |
Lista vendas com paginação e filtros |
| GET | /api/sales/{id} |
Busca venda por ID |
| POST | /api/sales |
Cria uma nova venda |
| PUT | /api/sales/{id} |
Atualiza uma venda existente |
| PUT | /api/sales/{id}/reactivate |
Reativa uma venda |
| DELETE | /api/sales/{id} |
Cancela/remove uma venda |
POST /api/products
Content-Type: application/json{
"name": "Notebook",
"currentPrice": 3500.00,
"status": 1
}POST /api/branches
Content-Type: application/json{
"name": "Recife Branch",
"address": "Av. Boa Viagem, 1000 - Recife/PE",
"status": 1
}POST /api/sales
Content-Type: application/json{
"userId": "11111111-1111-1111-1111-111111111111",
"branchId": "22222222-2222-2222-2222-222222222222",
"items": [
{
"productId": "33333333-3333-3333-3333-333333333333",
"quantity": 5
}
]
}Nesse exemplo, como a venda possui 5 unidades do mesmo produto, o sistema aplica 10% de desconto para esse item.
PUT /api/sales/{id}
Content-Type: application/json{
"userId": "11111111-1111-1111-1111-111111111111",
"branchId": "22222222-2222-2222-2222-222222222222",
"items": [
{
"productId": "33333333-3333-3333-3333-333333333333",
"quantity": 11
}
]
}Nesse exemplo, como a venda possui 11 unidades do mesmo produto, o sistema aplica 20% de desconto para esse item.
Itens que existiam anteriormente na venda e não forem enviados na nova request são marcados como cancelados/inativos.
Os endpoints de listagem seguem o padrão de paginação por query string.
Exemplo:
GET /api/products?pageNumber=1&pageSize=10Exemplo com filtros:
GET /api/products?pageNumber=1&pageSize=10&name=notebook&status=1Exemplo para vendas:
GET /api/sales?pageNumber=1&pageSize=10&status=1&startDate=2026-01-01&endDate=2026-12-31Resposta paginada:
{
"success": true,
"data": [
{
"id": "e6dd0d5c-b1d2-40a7-a6b0-f9d82a0dd001",
"name": "Notebook",
"currentPrice": 3500.00,
"status": 1
}
],
"currentPage": 1,
"totalPages": 1,
"totalCount": 1
}O projeto utiliza Rebus com RabbitMQ para publicar eventos de aplicação.
Atualmente, o principal evento utilizado é:
SaleCreatedEvent
Quando uma venda é criada com sucesso, a aplicação publica um evento informando que a venda foi registrada.
Fluxo simplificado:
CreateSaleHandler
↓
Cria a venda no banco
↓
Publica SaleCreatedEvent
↓
Rebus envia a mensagem para o RabbitMQ
↓
SaleCreatedEventHandler consome o evento
↓
A aplicação registra um log da venda criada
A abstração de mensageria fica na camada de aplicação, enquanto a implementação concreta com Rebus fica no projeto de IoC.
A Aplicação também possui outros eventos:
SaleCancelledEvent
SaleModifiedEvent
SaleItemsCancelledEvent
SaleReactivatedEvent
ProductCreatedEvent
ProductDeletedEvent
ProductModifiedEvent
BranchCreatedEvent
BranchDeletedEvent
BranchModifiedEvent
UserCreatedEvent
UserDeletedEvent
UserModifiedEvent
Todos possuem seus logs bem definidos.
O projeto utiliza Serilog para logs estruturados.
Os logs podem ser exibidos no console, Docker logs ou arquivos locais, dependendo da forma de execução da aplicação.
Exemplo de log gerado ao processar uma venda criada:
Sale created event received and processed. SaleId: ..., SaleNumber: ..., UserId: ..., BranchId: ..., TotalAmount: ...
A API utiliza tratamento global de exceções e respostas padronizadas.
Exemplos de cenários tratados:
| Cenário | Status HTTP |
|---|---|
| Entidade não encontrada | 404 |
| Request inválida | 400 |
| Erro de validação | 400 |
| Erro interno inesperado | 500 |
Antes de começar, certifique-se de ter instalado:
- .NET 8 SDK
- Docker
- Docker Compose
- Git
Opcional:
- Visual Studio 2022
- DBeaver, pgAdmin ou outro cliente PostgreSQL
Localmente (Caso não use o docker):
- PostgreSQL 16
- Rabbitmq:3
Na pasta onde está o arquivo docker-compose.yml, execute:
docker compose up --buildPara executar em background:
docker compose up -d --buildPara parar os containers:
docker compose downPara parar os containers e remover volumes:
docker compose down -vUse -v somente se quiser apagar também os dados persistidos dos volumes.
Atenção: No caso do docker as migrations são aplicadas automaticamente caso a flag APPLY_MIGRATIONS esteja true.
Exemplo para RabbitMQ:
{
"ConnectionStrings": {
"RabbitMq": "amqp://guest:guest@localhost:5672"
}
}Exemplo para PostgreSQL:
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=salesproject;Username=postgres;Password=Pass@word"
}
}Quando a API também está dentro do Docker Compose, o host deve ser o nome do serviço.
Exemplo:
{
"ConnectionStrings": {
"RabbitMq": "amqp://guest:guest@rabbitmq:5672"
}
}git clone <repository-url>
cd <repository-folder>A pasta que você deve ir com change directory é a backend que tem a SalesProject.sln
dotnet restore .\SalesProject.slnCaso o nome da solution seja diferente, ajuste o comando conforme o arquivo .sln existente no projeto.
dotnet build .\SalesProject.slnAjuste o comando conforme o nome real dos projetos da solução.
Exemplo:
dotnet ef database update \
--project src/SalesProject.ORM \
--startup-project src/SalesProject.WebApiNo Windows PowerShell, você também pode usar em uma linha:
dotnet ef database update --project .\src\SalesProject.ORM --startup-project .\src\SalesProject.WebApidotnet run --project .\src\SalesProject.WebApiCaso queira executar pelo Visual Studio essas são as 2 images separadas do postgreSQL e do rabbitMQ
version: '3.8'
services:
postgres:
image: postgres:16
container_name: salesproject_postgres
environment:
POSTGRES_DB: salesproject
POSTGRES_USER: postgres
POSTGRES_PASSWORD: Pass@word
ports:
- "5432:5432"
volumes:
- salesproject_postgres_data_test:/var/lib/postgresql/data
rabbitmq:
image: rabbitmq:3-management
container_name: salesproject_rabbitmq
ports:
- "5672:5672"
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
volumes:
salesproject_postgres_data_test:Não se esqueça de executar as migrations, se preferir você pode acessar o painel console gerenciador de pacotes e usar o seguinte comando apontando para o projeto ORM:
Update-DatabaseO projeto possui testes automatizados para validar os principais casos de uso da aplicação, incluindo handlers, validators e regras de negócio.
Execute os comandos abaixo na pasta raiz do projeto, onde está o arquivo .sln.
Para executar todos os testes da solução:
dotnet test .\SalesProject.slnPara executar apenas os testes unitários:
dotnet test .\tests\SalesProject.Unit\Ou informe diretamente o arquivo .csproj do projeto de testes:
dotnet test .\tests\SalesProject.Unit\SalesProject.Unit.csprojPara exibir mais informações durante a execução dos testes:
dotnet test .\tests\SalesProject.Unit\ --verbosity normalPara executar apenas testes que contenham determinado nome na classe, namespace ou método:
dotnet test .\tests\SalesProject.Unit\ --filter "FullyQualifiedName~CreateSaleHandlerTests"Exemplo para executar testes relacionados a produtos:
dotnet test .\tests\SalesProject.Unit\ --filter "FullyQualifiedName~Product"Exemplo para executar testes relacionados a vendas:
dotnet test .\tests\SalesProject.Unit\ --filter "FullyQualifiedName~Sale"Use sempre dotnet test para executar testes e informe a solução ou o projeto de testes quando houver mais de um .sln ou .csproj na mesma pasta.
Exemplo executando o projeto de testes unitários:
dotnet test .\tests\SalesProject.Unit\Com a API em execução, acesse:
http://localhost:8080/swagger
O Swagger permite testar os endpoints diretamente pelo navegador.
- O Docker Compose executa os serviços localmente.
- Para limpar os containers:
docker compose down- Para limpar containers e volumes:
docker compose down -vFuncionalidades implementadas:
- CRUD de usuários
- CRUD de produtos
- CRUD de filiais
- CRUD de vendas
- Paginação e filtros em listagens
- Validação com FluentValidation
- Mapeamentos com AutoMapper
- Casos de uso com MediatR
- Persistência com EF Core e PostgreSQL
- Mensageria com Rebus e RabbitMQ
- Logs com Serilog
- Docker Compose para ambiente local
Desenvolvido por Kawhan Laurindo de Lima.