Regras de Domínio no Core
Esta página consolida princípios para manter o Core/Domain centrado na regra de negócio. Para acelerar a migração de legado, admite-se uso controlado de anotações EF básicas, sem introduzir dependências de infraestrutura no domínio.
Princípios
- Toda regra de negócio reside no
Core/Domain. - Exceção pragmática: permitir anotações EF básicas no domínio (
[Key],[Column],[Table]) para acelerar a migração de legado. - Mesmo com as anotações, o
Domainnão deve usarDbContextnem depender de implementações de infraestrutura. - Entidades e Value Objects expressam invariantes; Domain Services encapsulam regras que não pertencem a uma entidade específica.
Anotações EF permitidas (exceção controlada)
- Permitidas:
[Key],[Column],[Table]. - Continue evitando mapeamentos complexos no domínio (conversores, relacionamentos avançados, etc.). Esses ficam na
Infra.
Exemplo de Entidade com Data Annotations EF
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("Orders")]
public sealed class Order : AggregateRoot
{
private readonly List<OrderLine> lines = new();
[Key]
[Column("Id")]
public Guid Id { get; private set; }
[Column("CustomerId")]
public Guid CustomerId { get; private set; }
public IReadOnlyCollection<OrderLine> Lines => lines;
private Order(Guid customerId)
{
if (customerId == Guid.Empty) throw new DomainException("Cliente inválido.");
Id = Guid.NewGuid();
CustomerId = customerId;
}
public static Order Create(Guid customerId, IEnumerable<(Guid productId, int qty, decimal price, string currency)> input)
{
var order = new Order(customerId);
foreach (var (productId, qty, price, currency) in input)
order.lines.Add(OrderLine.Create(productId, qty, Money.From(price, currency)));
return order;
}
}
public sealed class OrderLine : Entity
{
public Guid ProductId { get; private set; }
public int Quantity { get; private set; }
public Money UnitPrice { get; private set; }
private OrderLine(Guid productId, int quantity, Money unitPrice)
{
if (productId == Guid.Empty) throw new DomainException("Produto inválido.");
if (quantity <= 0) throw new DomainException("Quantidade deve ser positiva.");
ProductId = productId;
Quantity = quantity;
UnitPrice = unitPrice;
}
public static OrderLine Create(Guid productId, int quantity, Money unitPrice)
=> new(productId, quantity, unitPrice);
}
Exemplo de Value Object (sem dependência de EF)
public sealed class Money : ValueObject
{
public decimal Amount { get; }
public string Currency { get; }
private Money(decimal amount, string currency)
{
if (amount < 0) throw new DomainException("Valor negativo não permitido.");
if (string.IsNullOrWhiteSpace(currency)) throw new DomainException("Moeda é obrigatória.");
Amount = amount;
Currency = currency;
}
public static Money From(decimal amount, string currency) => new(amount, currency);
protected override IEnumerable<object?> GetEqualityComponents()
{
yield return Amount;
yield return Currency;
}
}
Dependência mínima de EF
- Não usar
DbContextnoDomain. - Restringir-se a anotações simples para mapear tabelas/colunas.
- Mapeamentos avançados permanecem em
Infra(ex.:EntityTypeConfiguration). - Para mapeamento de views, ver a página Views.