Domain
Driven Design - Usando Repositórios
![]() |
Hoje veremos o conceito de Repositório na abordagem do Domain Driven Design - DDD. |
O que é um repositório
Um repositório pode ser visto como uma interface semelhante a uma coleção que atua como uma ponte entre as camadas de domínio e de aplicação, fornecendo uma maneira padronizada de interagir com a camada de persistência de dados. Ele encapsula a lógica necessária para executar operações CRUD (Criar, Ler, Atualizar, Excluir) em entidades comerciais.
Princípios do Repositório
Para implementar repositórios de forma eficaz usando DDD, é essencial aderir a
um conjunto de princípios comuns. Vejamos as principais:
1. Defina uma interface de repositório na camada de
domínio
Os repositórios devem ser definidos como interfaces na
camada de domínio pois elas representam contratos que definem como os
objetos no domínio podem ser acessados e manipulados.
Ao fazer isso,
garantimos que os repositórios possam ser utilizados tanto pelas camadas de
domínio quanto de aplicação. A interface serve como um contrato que define as
operações disponíveis para acessar e manipular objetos de negócios.
Exemplo de definição de interface para repositório de produto:
public interface IProductRepository
{
Product GetById(Guid productId);
void Add(Product product);
void Update(Product product);
void Remove(Product product);
}
|
As interfaces são implementadas na camada de Infraestrutura e implementações dos repositórios são responsáveis por traduzir as operações definidas nas interfaces em ações específicas no armazenamento de dados. Aqui são incluídas tecnologias para acesso a dados como o Entity Framework Core.
public class ProductRepository : IProductRepository
{
private readonly DbContext _context;
public ProductRepository(DbContext context)
{
_context = context;
}
public Product GetById(Guid productId)
{
// implementação
}
public void Add(Product product)
{
// implementação
}
public void Update(Product product)
{
// implementação
}
public void Remove(Product product)
{
// implementação
}
} |
Lembre-se de que a camada Application (onde estão os casos de uso ou use cases) interage com os repositórios por meio das interfaces definidas na camada de domínio. Isso mantém a lógica de aplicação desacoplada da infraestrutura de armazenamento de dados, facilitando a testabilidade e a manutenção do código.
Evite incluir lógica de negócios em repositórios
Os repositórios devem se concentrar exclusivamente em tarefas de acesso e
manipulação de dados. É importante mantê-los livres de qualquer lógica de
negócios. As regras e validações de negócios devem ser tratadas pelas entidades
e serviços do domínio, garantindo uma separação clara de preocupações.
A interface do repositório deve ser independente do
provedor de banco de dados/ORM
Para promover flexibilidade e evitar o acoplamento dos repositórios a um
provedor de banco de dados específico ou a uma estrutura de Mapeamento
Objeto-Relacional (ORM), a interface do repositório não deve expor tipos
específicos do provedor. Por exemplo, é recomendado evitar o retorno de um DbSet
(fornecido pelo EF Core) de um método de repositório.
Crie repositórios para Aggregate Roots
No DDD, os repositórios são normalmente criados para
Aggregate Roots, em vez de entidades individuais. Um aggregate root ou raiz agregada
representa um cluster de entidades associadas que devem ser tratadas como uma
única unidade. O acesso às entidades da subcoleção dentro de um agregado deve
ser feito através da raiz agregada, garantindo consistência e limites
transacionais.
Suponha que estamos trabalhando em um sistema de e-commerce e temos um Aggregate Root chamado Order que gerencia pedidos de clientes.
Vamos começar definindo a classe Order como nosso Aggregate Root no namespace de domínio ou seja no projeto Domain:
public class Order
{
public int Id { get; private set; }
public DateTime OrderDate { get; private set; }
private List<OrderItem> _orderItems = new List<OrderItem>();
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();
// Construtores, métodos de domínio e validações aqui
}
public class OrderItem
{
public int Id { get; private set; }
public int ProductId { get; private set; }
public decimal Quantity { get; private set; }
public decimal Price { get; private set; }
// Construtores e métodos de domínio aqui
}
|
Lembrando que as entidades dentro do Aggregate devem ser acessadas através do Aggregate Root, e elas devem ter visibilidade privada ou protegida (protected) para garantir que não sejam acessadas diretamente de fora do Aggregate.
Por isso as propriedades de OrderItems foram definidas para serem de apenas leitura (IReadOnlyCollection<OrderItem>) para garantir que a lista de itens do pedido não seja modificada diretamente de fora da classe Order.
Apenas a título de ilustração, um esboço para o aggregate Order contendo os métodos de domínio para incluir e remover um item de um pedido poderia ser feita da seguinte forma:
public class Order
{
public int Id { get; private set; }
public DateTime OrderDate { get; private set; }
private List<OrderItem> _orderItems = new List<OrderItem>();
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();
// Construtor para criar um novo pedido
public Order(DateTime orderDate)
{
// Realize validações aqui, se necessário.
Id = GenerateUniqueId();
OrderDate = orderDate;
}
// Método de domínio para adicionar um item ao pedido
public void AddOrderItem(int productId, decimal quantity, decimal price)
{
// Realize validações aqui, se necessário.
var orderItem = new OrderItem(productId, quantity, price);
_orderItems.Add(orderItem);
}
// Método de domínio para remover um item do pedido
public void RemoveOrderItem(int orderItemId)
{
// Realize validações aqui, se necessário.
var orderItemToRemove = _orderItems.SingleOrDefault(item => item.Id == orderItemId);
if (orderItemToRemove != null)
{
_orderItems.Remove(orderItemToRemove);
}
}
// Outros métodos de domínio e validações aqui
// Métodos privados para encapsular a lógica de geração de ID único, se necessário
private int GenerateUniqueId()
{
// Lógica para gerar um ID único aqui
}
}
public class OrderItem
{
public int Id { get; private set; }
public int ProductId { get; private set; }
public decimal Quantity { get; private set; }
public decimal Price { get; private set; }
public OrderItem(int productId, decimal quantity, decimal price)
{
// Realize validações aqui, se necessário.
Id = GenerateUniqueId();
ProductId = productId;
Quantity = quantity;
Price = price;
}
// Outros métodos de domínio e validações aqui
// Métodos privados para encapsular a lógica de geração de ID único, se necessário
private int GenerateUniqueId()
{
// Lógica para gerar um ID único aqui
}
}
|
Desta forma, ao definir o seu Aggregate Root procure levar em conta as seguintes recomendações:
Agora, vamos criar a interface do repositório que define as operações que o repositório deve fornecer para acessar e manipular os Orders.
public interface IOrderRepository
{
Task<Order> GetByIdAsync(int id);
Task AddAsync(Order order);
Task UpdateAsync(Order order);
Task DeleteAsync(Order order);
// Outras operações específicas do Aggregate Root
}
|
A implementação do repositório será feita na camada de infraestrutura, e, esta implementação se conectará ao banco de dados ou outro meio de armazenamento de dados.
public class OrderRepository : IOrderRepository
{
private readonly DbContext _context;
public OrderRepository(DbContext context)
{
_context = context;
}
public async Task<Order> GetByIdAsync(int id)
{
return await _context.Orders.FindAsync(id);
}
public async Task AddAsync(Order order)
{
await _context.Orders.AddAsync(order);
}
public async Task UpdateAsync(Order order)
{
_context.Orders.Update(order);
}
public async Task DeleteAsync(Order order)
{
_context.Orders.Remove(order);
}
// Implementações de outras operações específicas do Aggregate Root
}
|
Neste exemplo, o OrderRepository é responsável por acessar e manipular os objetos Order no banco de dados. As operações definidas na interface do repositório (como GetByIdAsync, AddAsync, UpdateAsync, DeleteAsync) refletem as principais operações que podem ser realizadas no Aggregate Root.
Lembre-se de que as classes de entidades e repositórios são parte da camada de domínio. A camada de aplicação (Application Layer) usará o repositório para acessar e manipular os Aggregates Roots, encapsulando a lógica de negócios e permitindo que o domínio permaneça independente da implementação da infraestrutura.
Desta forma os repositórios devem se concentrar principalmente no acesso e manipulação de dados, deixando a lógica do domínio para as entidades e serviços do domínio.
E estamos
conversados...
"Se confessarmos os nossos pecados, ele é fiel e
justo para nos perdoar os pecados, e nos purificar de toda a injustiça."
1 João 1:9
Referências:
C# - Calculando a diferença entre duas datas
NET - Padrão de Projeto - Null Object Pattern
C# - Fundamentos : Definindo DateTime como Null ...
C# - Os tipos Nullable (Tipos Anuláveis)