I have been reading about domain driven design and how to implement it while using code first approach for generating a database. From what I've read and researched there are two opinions around this subject:
Have 1 class that serves both as a domain model and a persistence model
Have 2 different classes, one implementing the domain logic and one used for a code-first approach
Now I know opinion 1) is said to simplify small solutions that do not have many differences between the domain and persistence models but I think it breaks the single responsibility principle and by that introduces a lot of issues when an ORM's conventions interfere with DDD.
What is a surprise to me is there are numerous code examples of how to implement opinion 1). But a haven't found a single example of how to implement opinion 2) and how to map the 2 objects. (Probably there are such examples but I failed to find a C# one)
So I tried to implement an example on my own but I am not sure if that's a good way to do it.
Let's say I have a ticketing system and tickets have expiration date. My domain model will look like this:
/// <summary>
/// Domain Model
/// </summary>
public class TicketEntity
{
public int Id { get; private set; }
public decimal Cost { get; private set; }
public DateTime ExpiryDate { get; private set; }
public TicketEntity(int id, decimal cost, DateTime expiryDate)
{
this.Id = id;
this.Cost = cost;
this.ExpiryDate = expiryDate;
}
public bool IsTicketExpired()
{
if (DateTime.Now > this.ExpiryDate)
{
return true;
}
else
{
return false;
}
}
}
The persistence model using Entity Framework as ORM will look almost the same but as the solution grows this might not be the case
/// <summary>
/// ORM code first Persistence Model
/// </summary>
public class Ticket
{
[Key]
public int Id { get; set; }
public decimal Cost { get; set; }
public DateTime ExpiryDate { get; set; }
}
Everything looking great so far. Now what I am not sure about is which is the best place to get a Ticket
persistence model from the repository and how to map it to the TicketEntity
domain model
I have done this in an application/service layer.
public class ApplicationService
{
private ITicketsRepository ticketsRepository;
public ApplicationService(ITicketsRepository ticketsRepository)
{
this.ticketsRepository = ticketsRepository;
}
public bool IsTicketExpired(int ticketId)
{
Ticket persistanceModel = this.ticketsRepository.GetById(ticketId);
TicketEntity domainModel = new TicketEntity(
persistanceModel.Id,
persistanceModel.Cost,
persistanceModel.ExpiryDate);
return domainModel.IsTicketExpired();
}
}
My questions are:
Are there any reasons opinion 1) would be preferred to opinion 2) other than speeding up development and reusing code.
Are there any issues in my approach of mapping the models? Is there something I missed that would bring up issues when a solution grows?
Are there any reasons opinion 1) would be preferred to opinion 2) other than speeding up development and reusing code.
Option 1 is just because of pure laziness and imagined increased development speed. It's true that those applications will get version 1.0 built faster. But when those developers reach version 3.0 of the application, they do not think it's so fun to maintain the application due to all compromises that they have had to do in the domain model due to the ORM mapper.
Are there any issues in my approach of mapping the models? Is there something I missed that would bring up issues when a solution grows?
Yes. The repository should be responsible of hiding the persistence mechanism. It's API should only work with domain entities and not persistence entities.
The repository is responsible of doing conversions to/from domain entities (to be able to persist them). A fetch method typically uses ADO.NET or an ORM like Entity Framework to load the database object/entity. Then convert it to the correct business entity and finally return it.
Otherwise you would force every service to have knowledge about persistence AND working with your domain model, thus having two responsibilities.
If you work with application services per the DDD definition you will probably want to look at the Command/Query separation pattern which can be a replacement of the application services. The code gets cleaner and you also get a much more lightweight API wrapping your domain model.