Minhas notas e observações de estudo sobre diversos temas de tecnologia. Esta página está em constante evolução.
Fonte: Claude IA
Achei esse conceito de Interactor e Boundaries parecido com o conceito de Command Query. Existe alguma semelhança? Quais pontos são semelhantes e quais são diferentes?
Você está absolutamente correto em perceber a semelhança! Ambos os padrões compartilham filosofias similares, mas têm propósitos e contextos diferentes.
| Aspecto | Clean Architecture (Boundaries) | CQRS (Command Query) |
|---|---|---|
| Origem | Clean Architecture (Uncle Bob) | CQRS Pattern (Greg Young) |
| Foco Principal | Separação de camadas arquiteturais | Separação de leitura e escrita |
| Granularidade | Por caso de uso (use case) | Por operação (comando ou consulta) |
| Objetivo | Independência de frameworks | Otimização e escalabilidade |
| Complexidade | Moderada | Pode ser muito alta |
| Input | Request Model | Command / Query |
| Output | Response Model via Presenter | Command Result / Query Result |
Clean Architecture:
// Entrada
IRegisterPersonInputBoundary.Execute(RequestModel)
// Saída
IRegisterPersonOutputBoundary.Present(ResponseModel)
CQRS:
// Command (escrita)
ICommandHandler<RegisterPersonCommand, RegisterPersonResult>
// Query (leitura)
IQueryHandler<GetPersonsQuery, List<PersonDto>>
Similaridade: Ambos separam claramente entrada e saída, usando objetos específicos (DTOs).
Clean Architecture:
// Request Model
public class RegisterPersonRequestModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public DateTime BirthDate { get; set; }
}
// Response Model
public class RegisterPersonResponseModel
{
public bool Success { get; set; }
public string Message { get; set; }
public Guid? PersonId { get; set; }
}
CQRS:
// Command
public class RegisterPersonCommand
{
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public DateTime BirthDate { get; set; }
}
// Command Result
public class RegisterPersonResult
{
public bool Success { get; set; }
public string Message { get; set; }
public Guid? PersonId { get; set; }
}
Similaridade: DTOs imutáveis que transportam dados entre camadas.
Clean Architecture:
public class RegisterPersonInteractor : IRegisterPersonInputBoundary
{
public void Execute(RegisterPersonRequestModel request)
{
// 1. Validar
// 2. Aplicar regras de negócio
// 3. Persistir
// 4. Retornar resposta via OutputBoundary
}
}
CQRS:
public class RegisterPersonCommandHandler
: ICommandHandler<RegisterPersonCommand, RegisterPersonResult>
{
public RegisterPersonResult Handle(RegisterPersonCommand command)
{
// 1. Validar
// 2. Aplicar regras de negócio
// 3. Persistir
// 4. Retornar resultado
}
}
Similaridade: Encapsulam lógica de negócio em handlers específicos para cada operação.
Ambos seguem o SRP criando:
Clean Architecture (Boundaries):
View → Controller → InputBoundary → Interactor → Gateway
↓
View ← Presenter ← OutputBoundary ← Interactor
CQRS:
Command → Write Model → Write Database
Query → Read Model → Read Database (pode ser diferente!)
Clean Architecture:
// Ambos usam IPersonGateway
RegisterPersonInteractor → IPersonGateway.Save()
ListPersonsInteractor → IPersonGateway.GetAll()
CQRS:
// Commands (Write)
RegisterPersonCommand → WriteRepository → SQL Database
// Queries (Read)
GetPersonsQuery → ReadRepository → NoSQL Database (denormalizado)
Clean Architecture:
Person entity serve tanto para salvar quanto para recuperar// Mesma entidade
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
// ... usado para tudo
}
CQRS:
// Write Model (normalizado)
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
// Read Model (denormalizado, otimizado para consulta)
public class PersonListItem
{
public Guid Id { get; set; }
public string FullDisplayName { get; set; } // pré-calculado
public int Age { get; set; } // pré-calculado
public string FormattedPhone { get; set; } // pré-formatado
}
Clean Architecture:
// Salva e está disponível imediatamente
gateway.Save(person);
var persons = gateway.GetAll(); // pessoa já está aqui
CQRS:
// Command
commandHandler.Handle(new RegisterPersonCommand());
// Publica evento
// Event Handler (assíncrono)
eventHandler.Handle(PersonRegisteredEvent);
// Atualiza Read Model
// Query (pode não ter a pessoa ainda!)
queryHandler.Handle(new GetPersonsQuery());
Clean Architecture:
// Interactor envia para Presenter
outputBoundary.Present(responseModel);
// Presenter cria ViewModel
public class RegisterPersonPresenter : IRegisterPersonOutputBoundary
{
public RegisterPersonViewModel ViewModel { get; private set; }
public void Present(RegisterPersonResponseModel response)
{
ViewModel = new RegisterPersonViewModel
{
Message = response.Success
? "✓ Sucesso!"
: "✗ Erro: " + response.Message
};
}
}
// View consome ViewModel
view.Display(presenter.ViewModel);
CQRS:
// Handler retorna resultado
public RegisterPersonResult Handle(RegisterPersonCommand cmd)
{
return new RegisterPersonResult
{
Success = true,
PersonId = person.Id
};
}
// Controller/View usa diretamente
var result = commandHandler.Handle(command);
return result.Success ? Ok(result) : BadRequest(result);
Vou mostrar como seria nossa aplicação usando CQRS puro:
Commands/
├── RegisterPerson/
│ ├── RegisterPersonCommand.cs
│ ├── RegisterPersonCommandHandler.cs
│ └── RegisterPersonCommandResult.cs
└── UpdatePerson/
├── UpdatePersonCommand.cs
└── UpdatePersonCommandHandler.cs
Queries/
├── GetAllPersons/
│ ├── GetAllPersonsQuery.cs
│ ├── GetAllPersonsQueryHandler.cs
│ └── PersonListDto.cs
└── GetPersonById/
├── GetPersonByIdQuery.cs
└── GetPersonByIdQueryHandler.cs
// Command (imutável)
public record RegisterPersonCommand(
string Name,
string Email,
string Phone,
DateTime BirthDate
);
// Command Handler
public class RegisterPersonCommandHandler
: ICommandHandler<RegisterPersonCommand, CommandResult<Guid>>
{
private readonly IWriteRepository _writeRepo;
private readonly IEventBus _eventBus;
public CommandResult<Guid> Handle(RegisterPersonCommand command)
{
// 1. Validar
if (CalculateAge(command.BirthDate) < 20)
return CommandResult<Guid>.Failure("Idade mínima: 20 anos");
// 2. Criar entidade
var person = new Person(
command.Name,
command.Email,
command.Phone,
command.BirthDate
);
// 3. Persistir no Write Model
_writeRepo.Save(person);
// 4. Publicar evento (para atualizar Read Model)
_eventBus.Publish(new PersonRegisteredEvent(person));
// 5. Retornar resultado
return CommandResult<Guid>.Success(person.Id);
}
}
// Query (imutável)
public record GetAllPersonsQuery();
// Query Handler
public class GetAllPersonsQueryHandler
: IQueryHandler<GetAllPersonsQuery, List<PersonListDto>>
{
private readonly IReadRepository _readRepo;
public List<PersonListDto> Handle(GetAllPersonsQuery query)
{
// Lê do Read Model (pode ser denormalizado)
return _readRepo.GetAllPersons();
}
}
// DTO otimizado para leitura
public class PersonListDto
{
public Guid Id { get; set; }
public string DisplayName { get; set; } // pré-formatado
public string ContactInfo { get; set; } // email + phone combinados
public int Age { get; set; } // pré-calculado
public string AgeGroup { get; set; } // "20-30", "31-40", etc
}
✅ Você precisa de independência de frameworks ✅ Testabilidade é prioridade ✅ Aplicação de complexidade média ✅ Time pequeno/médio ✅ Modelo de dados simples a moderado ✅ Consistência imediata é necessária
Exemplo: Aplicações corporativas típicas, APIs REST, sistemas CRUD
✅ Performance de leitura é crítica ✅ Leituras são muito mais frequentes que escritas (ratio 100:1) ✅ Necessita de modelos otimizados diferentes para leitura/escrita ✅ Escalabilidade horizontal é necessária ✅ Aceita consistência eventual ✅ Time experiente com arquiteturas distribuídas
Exemplo: E-commerce (catálogo de produtos), sistemas de relatórios, dashboards
✅ Sistema grande e complexo ✅ Necessita de todas as vantagens de ambos ✅ Recursos disponíveis (time experiente, tempo, orçamento)
Estrutura híbrida:
Clean Architecture (estrutura geral)
│
├── Entities (centro)
│
├── Use Cases
│ ├── Commands/ (CQRS)
│ │ ├── RegisterPersonCommand
│ │ └── UpdatePersonCommand
│ │
│ └── Queries/ (CQRS)
│ ├── GetAllPersonsQuery
│ └── GetPersonByIdQuery
│
├── Interface Adapters
│ ├── Controllers
│ └── Presenters
│
└── Frameworks & Drivers
├── WriteDatabase (SQL)
└── ReadDatabase (NoSQL/Cache)
CLEAN ARCHITECTURE (nossa implementação)
────────────────────────────────────────
Request → Controller → InputBoundary → Interactor → Gateway → Database
↓
Response ← View ← Presenter ← OutputBoundary ← Interactor
• Fluxo único para leitura e escrita
• Mesmo banco de dados
• Consistência imediata
• Complexidade moderada
CQRS PURO
─────────
WRITE:
Command → CommandHandler → WriteModel → WriteDB → Event
↓
EventHandler
↓
READ: ReadModel → ReadDB
Query → QueryHandler → ReadModel → ReadDB
• Fluxos separados
• Bancos diferentes (pode ser)
• Consistência eventual (pode ser)
• Complexidade alta
HÍBRIDO (Clean + CQRS)
──────────────────────
Clean Architecture
┌─────────────┐
│ Entities │
└──────┬──────┘
│
┌───────────┴───────────┐
│ │
Commands (Write) Queries (Read)
│ │
WriteModel ReadModel
│ │
WriteDB ReadDB
• Melhor dos dois mundos
• Máxima flexibilidade
• Complexidade muito alta
| Característica | Clean Arch | CQRS |
|---|---|---|
| Separation of Concerns | ✅ Por camada | ✅ Por operação |
| Read/Write Separation | ❌ Não | ✅ Sim |
| Multiple Databases | ❌ Não | ✅ Pode ter |
| Consistency | ✅ Imediata | ⚠️ Eventual |
| Complexity | 🟡 Média | 🔴 Alta |
| Learning Curve | 🟡 Moderada | 🔴 Íngreme |
| Use Cases | Interface clara | ✅ Commands/Queries |
| Presenters | ✅ Sim | ❌ Geralmente não |
Clean Architecture (Boundaries) e CQRS são padrões complementares, não excludentes:
Você pode:
Na prática, muitas aplicações começam com Clean Architecture e evoluem para CQRS apenas nas áreas que realmente precisam de otimização de leitura/escrita.
Referências: