Having Separate Domain Model and Persistence Model

2019-01-21 01:54发布

问题:

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:

  1. Have 1 class that serves both as a domain model and a persistence model

  2. 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:

  1. Are there any reasons opinion 1) would be preferred to opinion 2) other than speeding up development and reusing code.

  2. 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?

回答1:

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.



回答2:

I got into this dilemma this year in a big project I was working at and it was a really tough decision to make... I would like to talk about this topic during hours, but I'll resume my thoughts for you:

1) Persistence and Domain model as the same thing

If you are in a new project with a database designed from zero for it I would probablly suggest this option. Yes, the domain and your knowledge about it will change constantly and this will demand refactoring that will affect your database, but I think in most cases it's worth it.

With Entity Framework as your ORM you can almost keep your domain models entirely free of ORM concerns using fluent mappings.

Good parts:

  • Fast, easy, beautiful (if the database is designed for that problem)

Bad parts:

  • Maybe the developers starts to think twice before to do a change/refactoring in the domain fearing that it will affect the database. This fear is not good for the domain.
  • If the domain starts to diverge too much from the database you will face some difficulties to maintain the domain in harmony with the ORM. The closer to the domain the harder to configure the ORM. The closer to the ORM the dirtier the domain gets.

2) Persistence and Domain model as two separated things

It will get you free to do whatever you want with your domain. No fear of refactorings, no limitations provinients from ORM and database. I would recomend this approach for systems that deal with a legacy or bad designed database, something that will probably end messing up your domain.

Good parts:

  • Completely free to refactor the domain
  • It'll get easy to dig into another topics of DDD like Bounded Context.

Bad parts:

  • More efforts with data conversions between the layers. Development time (maybe also runtime) will get slower.

  • But the principal and, believe me, what will hurt more: You will lose the main beneffits of using an ORM! Like tracking changes. Maybe you will end up using frameworks like GraphDiff or even abandon ORM's and go to the pure ADO.NET.

Are there any issues in my approach of mapping the models?

I agree with @jgauffin: "it's in the repository that the mapping should take place". This way your Persistence models will never get out from the Repository layer, in preference no one should see those entities (unless the repository itself).



回答3:

Are there any reasons opinion 1) would be preferred to opinion 2) other than speeding up development and reusing code.

I can see a big one (opinionated stuff ahead) : there is no "Persistence Model". All you've got is a relational model in your database, and a Domain object model. Mapping between the two is a set of actions, not data structures. What's more, this is precisely what ORM's are supposed to do.

Most ORM's now support what they should have provided from the start -- a way to declare these actions directly in code without touching your domain entities. Entity Framework's fluent configurations for instance allow you to do that.

You may be under the impression that no persistence model = violating SRP and trampling on DDD, because many implementations you can find out there do. But it doesn't have to be like that.