Pular para o conteúdo

Domain Model e CQRS: Modernizando sua Arquitetura Delphi

Enterprise Patterns com Dext: Domain Model & CQRS - Como aplicar Domain Model e CQRS para modernizar sua arquitetura Delphi e preparar seu sistema para alta escalabilidade.

No desenvolvimento de software corporativo, frequentemente caímos na armadilha de usar uma única classe para tudo. O mesmo objeto valida regras, mapeia o banco de dados, calcula impostos e serve a tela.

O resultado todos conhecemos: sistemas rígidos, classes gigantes (“God Classes”) e o medo constante de alterar qualquer coisa em produção.

Inspirado nos padrões de arquitetura Enterprise (comumente vistos no ecossistema .NET Core e Java), o Dext traz para o Delphi a capacidade de aplicar o princípio da Segregação de Responsabilidades de forma elegante e performática.

Hoje vou demonstrar uma arquitetura híbrida que separa o Estado (Persistência) do Comportamento (Regras de Negócio).

O segredo da arquitetura robusta é aceitar que como guardamos o dado é diferente de como processamos o dado.

Esta classe deve ser leve, um espelho fiel da tabela. O banco de dados é a nossa “Fonte da Verdade”: se o dado está gravado lá, assumimos que ele está íntegro.

Para manter o código limpo, utilizo o Fluent Mapping do Dext, eliminando a poluição visual de atributos dentro da classe de domínio.

// POCO: Puro, leve e focado apenas em dados.
type
TOrderEntity = class
private
FId: Integer;
FStatus: string;
FTotal: Currency;
FItems: IList<TOrderItemEntity>;
public
property Id: Integer read FId write FId;
property Status: string read FStatus write FStatus;
property Total: Currency read FTotal write FTotal;
property Items: IList<TOrderItemEntity> read FItems;
end;

Aqui reside a “gravidade” das regras de negócio. O Model não é um mero transportador de dados; ele é o Guardião da Integridade. Observe como não expomos setters públicos; a única forma de alterar o estado é através de métodos que garantem as regras.

type
TOrderModel = class
private
FEntity: TOrderEntity; // O target dos dados
public
constructor Create(Entity: TOrderEntity);
// Comportamentos (Ações de Negócio)
procedure AdicionarItem(Produto: TProduct; Quantidade: Integer);
procedure SubmeterPedido;
// Acesso Read-Only (Encapsulamento Total)
property Entity: TOrderEntity read FEntity;
end;
implementation
procedure TOrderModel.SubmeterPedido;
begin
// 1. Validação de Estado
if FEntity.Status <> 'Rascunho' then
raise EDomainError.Create('Apenas pedidos em rascunho podem ser submetidos.');
// 2. Validação de Invariantes (Regras "Duras")
if FEntity.Total < 500.00 then
raise EDomainError.Create('O pedido mínimo para faturamento é R$ 500,00.');
// 3. Transição de Estado Segura
FEntity.Status := 'AguardandoAprovacao';
end;
procedure TOrderModel.AdicionarItem(Produto: TProduct; Quantidade: Integer);
begin
// Fail Fast: Impede dados sujos de entrarem no sistema
if Quantidade <= 0 then raise EDomainError.Create('Quantidade inválida.');
var Item := TOrderItemEntity.Create;
Item.ProductId := Produto.Id;
Item.Quantity := Quantidade;
Item.UnitPrice := Produto.PrecoVenda;
FEntity.Items.Add(Item);
// Atualiza o total imediatamente. A entidade nunca fica inconsistente.
FEntity.Total := FEntity.Total + (Item.UnitPrice * Quantidade);
end;

A beleza desta arquitetura se revela nos Endpoints (Controllers). Como separamos as responsabilidades, podemos usar o poder do Specification Pattern (estilo Ardalis) integrado ao Dext.

Veja a diferença brutal entre os dois mundos convivendo em harmonia:

Aqui usamos a sintaxe fluente do Dext. O código é declarativo: dizemos o que queremos, não como buscar.

// GET /orders/pending
App.MapGet('/orders/pending',
function(Context: THttpContext; Repo: IOrderRepository): IResult
begin
// "Traga pedidos onde Status é 'Submitted' E Total > 1000,
// incluindo os Itens, ordenados por Data"
var Spec := Specification.Where<TOrderEntity>(
(OrderEntity.Status = 'Submitted') and
(OrderEntity.Total > 1000)
)
.Include('Items')
.OrderBy(OrderEntity.CreatedAt.Desc);
// O Repository apenas executa a especificação. Limpo.
var Orders := Repo.ToList(Spec);
Result := Results.Ok(Orders);
end);

Aqui a disciplina entra em cena. Instanciamos o Model para garantir que nenhuma regra seja violada antes da persistência.

// POST /orders
App.MapPost('/orders',
function(Context: THttpContext; Repo: IOrderRepository; Dto: TCreateOrderDto): IResult
begin
var Entity := TOrderEntity.Create;
var Model := TOrderModel.Create(Entity);
try
// O Model orquestra as regras "pesadas"
for var Item in Dto.Items do
Model.AdicionarItem(Item.ProductId, Item.Qty);
Model.SubmeterPedido; // Valida estado e invariantes
// Persiste apenas se o Model aprovou tudo
Repo.Add(Model.Entity);
Result := Results.Created(Model.Entity.Id, Model.Entity);
except
on E: EDomainError do
Result := Results.BadRequest(E.Message);
end;
end);

Ao adotar este padrão, ganhamos um benefício que vale ouro em projetos grandes: a capacidade de testar regras de negócio isoladamente.

No modelo antigo (“God Class” acoplada ao componente de banco), para testar se o “Pedido Mínimo de R$ 500,00” funciona, você precisaria subir um banco de dados, inserir registros e rodar o sistema. Lento e frágil.

Nesta arquitetura híbrida, o TOrderModel é agnóstico ao banco. Você pode escrever um Teste Unitário (DUnit/DUnitX) que instancia a entidade em memória, passa para o Model e verifica o comportamento em milissegundos.

procedure TTestOrderModel.TestarValidacaoDeMinimo;
var
Entidade: TOrderEntity;
Model: TOrderModel;
begin
// Arrange: Entidade em memória, sem banco de dados!
Entidade := TOrderEntity.Create;
Entidade.Total := 100.00;
Model := TOrderModel.Create(Entidade);
// Act & Assert
Assert.WillRaise(procedure begin
Model.SubmeterPedido
end,
EDomainError,
'Deve bloquear pedidos abaixo de 500 reais'
);
end;

Isso garante a qualidade do software muito antes dele chegar em produção.

Adotar Enterprise Patterns com Dext transforma a engenharia do seu projeto Delphi. Ao separar Estado de Comportamento, atingimos o equilíbrio perfeito:

  1. Leitura: Expressiva e performática com Fluent Specifications.
  2. Escrita: Segura e blindada com Domain Models.
  3. Qualidade: Testabilidade real fora do banco de dados.

O Dext fornece a fundação (ORM, Fluent Mapping, Specifications) para que você pare de brigar com o código e comece a desenhar arquiteturas robustas.

Este é apenas o primeiro passo. Uma vez que seu domínio está rico e isolado, novas possibilidades se abrem: Event Sourcing, filas de mensageria e APIs de altíssima performance. Mas essa é uma conversa para o nosso próximo artigo.

Veja o repositório do Dext no GitHub: https://github.com/cesarliws/dext