I've been considering to inherit my Persistence Models (PM) from my pristine Domain Model (DM) classes in order to add some properties that are important for persistence logic. This way I won't have to deal with a lot of mapping code.
For the sake of a simple example case, here's a DM representing a Credit that can be used by Users or Organizations to place Orders.
namespace Foo.Domain
{
// Domain Model
public class Credit : IDomainEntity, IAggregateRoot
{
public Credit(int? userId, int? organizationId, DateTime expiryDate)
{
if(userId == null && organizationId == null)
throw new InvalidOperationException("MUST assign credit to user OR organization.");
if(userId != null && organizationId != null)
throw new InvalidOperationException("CANNOT assign credit to user AND organization.");
UserId = userId;
OrganizationId = organizationId;
ExpiryDate = expiryDate;
}
public virtual int Id { get; private set; }
public virtual DateTime ExpiryDate { get; private set; }
// external IAggregateRoot objects are linked by reference
public virtual int? UserId { get; private set; }
public virtual int? OrganizationId { get; private set; }
public virtual int? OrderId { get; private set; }
public void AttachToOrder(int orderId)
{
if (ExpiryDate >= DateTime.UtcNow)
throw new InvalidOperationException("Credit is expired.");
if (OrderId != null)
throw new InvalidOperationException("Credit is unavailable.");
OrderId = orderId;
}
public void DetachFromOrder()
{
if (OrderId == null)
throw new InvalidOperationException("Credit is available.");
OrderId = null;
}
}
}
Let's say that I want to persist this model with a foreign key constraints. Entity Framework Fluent API requires a navigation property to do that. So I can't directly persist the Domain Model and benefit from the constraint checking of a foreign key in the relational database.
My idea is to inherit a PM to add the required navigation properties. Since EF also requires a parameterless constructor, I would unfortunately be forced to add that to the DM, although it luckily won't have an effect on its invariants.
namespace Foo.Domain
{
// Adjusted Domain Model
public class Credit : IDomainEntity, IAggregateRoot
{
protected Credit() { }
public Credit(int? userId, int? organizationId, DateTime expiryDate)
: this()
// remainder of the class unchanged
}
}
namespace Foo.Infrastructure
{
// Persistence Model
public class Credit : Foo.Domain.Credit
{
protected Credit()
: base()
{
}
public Credit(int? userId, int? organizationId, DateTime expiryDate)
: base(userId, organizationId, expiryDate)
{
}
public virtual User User { get; private set; }
public virtual Organization Organization { get; private set; }
public virtual Order Order { get; private set; }
}
}
Now I assume that I can then use it like this in the Fluent API:
modelBuilder.Entity<Credit>()
.HasOptional<Order>(c => c.Order) // this is why I need the navigation property
.WithMany()
.HasForeignKey(c => c.OrderId);
..and define my DbContext like this:
namespace Foo.Infrastructure
{
public interface IFooDbContext
{
IDbSet<Credit> Credits { get; }
Task<int> SaveChangesAsync();
}
}
What are your thoughts on this approach?