> ## Content Index
> Fetch the complete content index at: https://www.azurebrasil.cloud/llms.txt
> Use this file to discover other available public pages before exploring further.

# CQRS com Minimal API : Conceitos, cenários e armadilhas
- URL: https://www.azurebrasil.cloud/blog/cqrs-com-minimal-api-conceitos-cenarios-e-armadilhas/
- Published: 2026-04-29T12:07:53.000Z
- Updated: 2026-04-29T12:07:53.000Z
- Description: Descubra como CQRS melhora organização, performance e clareza no backend usando uma abordagem leve com Minimal APIs.
- Author: Charlison Fideles de Oliveira

## Introdução

Dentro do ecossistema .NET, é muito comum começarmos o desenvolvimento das nossas aplicações com uma abordagem simples: um único modelo de domínio responsável por leitura e escrita. Isso funciona bem no início, mas à medida que a aplicação cresce, tanto em volume de dados quanto em complexidade de regras, esse modelo começa a apresentar limitações.

É nesse contexto que o **CQRS (Command Query Responsibility Segregation)** entra como uma alternativa arquitetural. Ele propõe algo simples, porém poderoso: **separar completamente as operações de leitura (queries) das operações de escrita (commands)**.

Com a chegada das **Minimal APIs no ASP.NET Core**, ficou ainda mais fácil implementar esse padrão de forma leve, sem a necessidade de estruturas pesadas como Controllers ou camadas excessivas.

## O que é CQRS

CQRS é um padrão de arquitetura de software que divide a aplicação em dois fluxos distintos:

- **Command (escrita)** → altera estado (create, update, delete)
- **Query (leitura)** → apenas consulta dados, sem efeitos colaterais

A principal ideia é: **ler e escrever são problemas totalmente diferentes e devem ser tratados de formas diferentes**.

### Modelo tradicional (CRUD)

No modelo tradicional:

```csharp
public class ProductService
{
    public Product GetById(int id);
    public void Create(Product product);
    public void Update(Product product);
}
```

Aqui nesse exemplo, o mesmo modelo atende tanto para leitura quanto para escrita.

### Modelo CQRS

No CQRS, você separa de forma distinta cada operação:

```csharp
// Command
public record CreateProductCommand(string Name, decimal Price);

// Query
public record GetProductByIdQuery(int Id);
```

Cada um com possui o seu handler:

```csharp
public class CreateProductHandler
{
    public void Handle(CreateProductCommand command) { }
}

public class GetProductByIdHandler
{
    public Product Handle(GetProductByIdQuery query) { }
}
```

## CQRS não é sobre tecnologia é sobre modelo mental

Outro ponto importante: CQRS não depende de framework.

Você pode implementar com:

- Minimal API
- Controllers
- MediatR
- Sem nenhuma biblioteca externa

O valor está na **forma de pensar**, não na ferramenta.

Esse modelo mental traz algumas mudanças importantes:

### 1\. Pensar em ações, não em CRUD

Em vez de:

```csharp
CreateProduct
UpdateProduct
```

Você começa a pensar em:

```csharp
ActivateProduct
ApplyDiscount
ChangeProductPrice
```

Ou seja, comandos mais próximos da realidade do negócio.

### 2\. Pensar em respostas específicas

Em vez de:

```csharp
GetProduct()
```

Você passa a ter:

```csharp
GetProductDetails()
GetProductSummary()
GetProductsForDashboard()
```

Cada query atende um cenário específico.

### 3\. Redução de acoplamento acidental

Quando leitura e escrita compartilham o mesmo modelo, qualquer mudança impacta tudo.

Com CQRS:

- Alterações em queries não afetam commands
- Alterações no domínio não quebram leitura (se bem projetado)

## O custo real do CQRS

Apesar das vantagens, CQRS tem um custo que precisa ser entendido.

### 1\. Mais código

Você passa a ter:

- Commands
- Queries
- Handlers separados

Isso aumenta o número de arquivos e abstrações existentes dentro do seu projeto

### 2\. Maior esforço cognitivo

Para quem entra no projeto:

- Não existe mais “um lugar só” para tudo
- Precisa entender o fluxo de command vs query para localizar cada coisa

### 3\. Overengineering

Se aplicado cedo demais:

- Complica sem necessidade
- Dificulta manutenção
- Reduz produtividade

## Command não é “um método de escrita”

Um erro comum é tratar command como apenas um DTO para insert/update. Conceitualmente falando, isso não reflete sua verdadeira funcionalidade.

Um **Command representa uma intenção de mudança de estado**, não apenas uma operação técnica.

Exemplo:

```csharp
public record UpdateProductPriceCommand(int Id, decimal NewPrice);
```

Aqui você não está dizendo *“atualize esse registro”*, mas sim:

> “eu quero alterar o preço desse produto”

Essa diferença parece pequena e quase imperceptivel, mas muda completamente a forma como você modela o sistema:

- Você pode validar regras antes da mudança
- Pode rejeitar comandos inválidos
- Pode auditar intenções
- Pode evoluir comportamento sem quebrar contrato

Ou seja, o command está muito mais próximo do **domínio** do que da infraestrutura.

## Query não é “um select genérico”

Da mesma forma, uma query não deveria retornar entidades completas por padrão.

O objetivo da query é **responder uma pergunta específica da forma mais eficiente possível**.

Exemplo:

```csharp
public record ProductListItemDto(string Name, decimal Price);
```

Aqui você está modelando a resposta para um caso de uso e não reutilizando uma entidade.

Isso permite:

- Evitar overfetching(mais dados que o necessário)
- Melhorar performance
- Reduzir acoplamento
- Customizar formato por endpoint

No CQRS, queries são orientadas a **casos de uso**, não a entidades.

### Por que usar CQRS?

### 1\. Escalabilidade

Você pode escalar leitura e escrita de maneira individual. Em sistemas com muitas consultas (ex: dashboards), isso faz muita diferença.

### 2\. Performance

Queries podem usar projeções otimizadas (DTOs, views, SQL direto com Dapper), sem depender do modelo definido no domínio.

### 3\. Separação de responsabilidades

Cada operação tem sua própria lógica isolada, isso torna a aplicação muito mais fácil de manter e evoluir.

### 4\. Flexibilidade

Você pode usar tecnologias diferentes para leitura e escrita (ex: EF Core para escrita, Dapper para leitura).

## Quando usar CQRS

CQRS não é a tecnologia que vai resolver tudo, é importante saber quais os cenários onde ele é melhor aplicado para você conseguie o máximo de desempenho:

### Bons cenários

- Sistemas com alta carga de leitura (relatórios, dashboards)
- Domínios complexos (regras de negócio pesadas)
- Sistemas distribuídos ou baseados em eventos
- Aplicações que precisam evoluir com independência entre leitura e escrita

### Cenários onde pode ser exagero

- CRUD simples
- Aplicações pequenas
- Sistemas sem complexidade de domínio

Se o seu sistema é basicamente um CRUD, CQRS pode mais atrapalhar do que ajudar.

## CQRS com Minimal API: por que faz sentido?

Minimal APIs são ideais para CQRS porque:

- Reduzem boilerplate
- Facilitam organização por feature
- Funcionam bem com handlers simples
- Integram naturalmente com DI

Você consegue implementar CQRS sem precisar de frameworks adicionais como MediatR (embora possa usar, se quiser).

## Comparação com outras abordagens

### CQRS vs CRUD tradicional

```Table
Aspecto	        CRUD tradicional	CQRS
Simplicidade	Alta	            Média
Escalabilidade	Baixa	            Alta
Organização	    Média	            Alta
Performance	    Média	            Alta
```

### CQRS vs uso com MediatR

MediatR é frequentemente usado com CQRS, mas não é obrigatório.

#### Com MediatR

- Mais desacoplamento
- Pipeline behaviors (logging, validação)
- Mais abstração

#### Sem MediatR (como aqui)

- Mais simples
- Menos indireção
- Mais controle direto

Para sistemas menores/médios, evitar MediatR pode ser uma decisão mais pragmática.

## Erros comuns ao usar CQRS

### 1\. Usar CQRS para tudo

Aplicar CQRS em um CRUD simples só aumenta complexidade sem ganho real.

### 2\. Não separar de verdade

Exemplo errado:

```csharp
public class ProductService
{
    public Product Get(int id);
    public void Create(Product product);
}
```

Isso não é CQRS, é só um service tradicional.

### 3\. Reutilizar entidades de escrita na leitura

Na leitura, prefira DTOs:

```csharp
public record ProductDto(string Name, decimal Price);
```

### 4\. Ignorar performance nas queries

CQRS permite otimizar leitura, mas isso só funciona se você realmente fizer isso.

Exemplo com Dapper seria mais performático que EF em alguns cenários.

### 5\. Complexidade excessiva

Separar demais (ex: 1 handler por micro-operação irrelevante) pode tornar o sistema difícil de navegar.

## Exemplo prático completo

Vamos montar um cenário real e aplicar o que aprendemos até aqui com as seguintes funções:

- Criar produto (Command)
- Listar produtos (Query)

## Estrutura sugerida

Uma estrutura simples:

```project
/Features
  /Products
    /Commands
      CreateProductCommand.cs
      CreateProductHandler.cs
    /Queries
      GetProductQuery.cs
      GetProductHandler.cs
```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2026/04/image-20.png)

Estrutura

## Instalação básica

Crie um projeto:

```bash
dotnet new web -n CqrsMinimalApi
cd CqrsMinimalApi
```

Se quiser usar EF Core:

```bash
dotnet add package Microsoft.EntityFrameworkCore.InMemory
```

1\. Modelo

```csharp
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public decimal Price { get; set; }
}
```

2\. DbContext

```csharp
using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public DbSet<Product> Products => Set<Product>();

    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options) { }
}
```

3\. Command (CreateProduct)

```csharp
public record CreateProductCommand(string Name, decimal Price);
```

Handler:

```csharp
public class CreateProductHandler
{
    private readonly AppDbContext _context;

    public CreateProductHandler(AppDbContext context)
    {
        _context = context;
    }

    public async Task<int> Handle(CreateProductCommand command)
    {
        var product = new Product
        {
            Name = command.Name,
            Price = command.Price
        };

        _context.Products.Add(product);
        await _context.SaveChangesAsync();

        return product.Id;
    }
}
```

4\. Query (GetAllProducts)

```csharp
public record GetAllProductsQuery();
```

Handler:

```csharp
public class GetAllProductsHandler
{
    private readonly AppDbContext _context;

    public GetAllProductsHandler(AppDbContext context)
    {
        _context = context;
    }

    public async Task<List<Product>> Handle()
    {
        return await _context.Products
            .AsNoTracking()
            .ToListAsync();
    }
}
```

5\. Program.cs (Minimal API)

```csharp
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(opt =>
    opt.UseInMemoryDatabase("db"));

builder.Services.AddScoped<CreateProductHandler>();
builder.Services.AddScoped<GetAllProductsHandler>();

var app = builder.Build();

// Command
app.MapPost("/products", async (
    CreateProductCommand command,
    CreateProductHandler handler) =>
{
    var id = await handler.Handle(command);
    return Results.Created($"/products/{id}", id);
});

// Query
app.MapGet("/products", async (
    GetAllProductsHandler handler) =>
{
    var products = await handler.Handle();
    return Results.Ok(products);
});

app.Run();
```

## Testando a API

### Criar produto

```cURL
POST /products
{
  "name": "Teclado",
  "price": 100
}
```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2026/04/image-18.png)

Adicionando um produto

Listar produtos

```cURL
GET /products
```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2026/04/image-19.png)

Obtendo lista de produtos

## Evoluções possíveis

A partir desse exemplo simples, você pode evoluir para:

- Validação com FluentValidation
- Pipeline de logging
- Uso de MediatR
- Event sourcing
- Banco separado para leitura
- Cache (Redis) para queries

## Conclusão

CQRS é um padrão poderoso, mas que deve ser aplicado com critério. Ele resolve problemas reais de escalabilidade, organização e performance, especialmente em sistemas mais complexos.

Com Minimal APIs, ficou muito mais simples adotar CQRS sem introduzir peso desnecessário na aplicação. Você consegue manter a arquitetura limpa, previsível e fácil de evoluir.

A chave é saber quando usar:

- Se o sistema é simples → mantenha simples
- Se começa a crescer em complexidade → CQRS pode ser o próximo passo natural

No fim, CQRS não é sobre separar código é sobre **separar responsabilidades de forma inteligente** sabendo separar essas responsabilidades de forma aderquada, você ganha:

- Clareza arquitetural
- Melhor capacidade de evolução
- Mais controle sobre performance

Mas isso vem com um custo, e a maturidade está em saber quando esse custo se justifica.

No contexto de aplicações modernas em .NET, especialmente com Minimal APIs, CQRS deixa de ser um padrão “pesado” e passa a ser uma **ferramenta prática**, que pode ser aplicada de forma incremental e consciente.

## Código-fonte do projeto

Para facilitar o entendimento, disponibilizei o projeto completo com o exemplo de **CQRS**, já organizado com separação de responsabilidades e boas práticas:

👉 **Baixar o projeto no GitHub** [**neste link**](https://github.com/charlisonOliveira/CqrsMinimalApi-BLOG?ref=azurebrasil.cloud)**:**

## Quer ver mais conteúdos como este?

Se você trabalha com **.NET, C# e backend**, este blog seguirá trazendo conteúdos práticos e objetivos sobre o **ecossistema Microsoft**, com foco em código, boas práticas e decisões técnicas aplicadas ao mundo real.

Acompanhe para continuar aprofundando seu domínio sobre a plataforma **.NET**.