Separating the Domain Model and the Data Model

2019-04-13 12:32发布

问题:

My question is similar to this one: Repository pattern and mapping between domain models and Entity Framework.

I have done a lot of reading on here about the following:

1) Mapping the ORM directly to the domain model

2) Mapping the ORM to a data model and then mapping the data model to a domain model (and vice versa)

I understand the benefits and limitations of both approaches. I also understand the scenarios where one approach is favoured over the other.

There are plenty of examples online, which show how to do option 1. However, I cannot find any example code, which shows how to do option 2. I read questions on here about option two like the one referenced on the first line of this post i.e. the question is about option two but the answer is about option one - and there are comments that state that option two may be more appropriate.

Therefore my question is specifically about how to do option one from a mapping and validation perspective:

Mapping

I believe I can do this when mapping the Domain Model to the Data Model:

public PersonDomain GetById(Guid id)
{
    return AutoMapper.Mapper.Map<PersonDomain>(Session.Get<PersonData>(id)); 
}

I believe I have do this when mapping the Data Model to the Domain Model in the repository (to protect the invariants):

protected PersonDomain ToPersonDomain(PersonData personData) 
{
    return new PersonDomain(personData.ID, personData.Name, personData.DateOfBirth);
}

Validation

I want to do this in the PersonDomain class:

public class PersonDomain
{
   public Guid ID{ get; private set; }
   public DateTime DateOfBirth { get; private set; }
   public string Name { get; private set; }

   public PersonDomain(Guid id, DateTime dateOfBirth, string name)
   {
    if (id == Guid.Empty())
      throw new ArguementException("Guid cannot be empty");
    if (name =="")
       throw new ArguementException("Name cannot be empty");
    ID=id;
    Name=NAME;
    DateOfBirth=dateOfBirth;
   }

}

However, every example I find tells me not to put validation in the constructor. One idea I had was to avoid primitive obsession as follows:

public class PersonDomain
{
   public ID ID{ get; private set; }
   public DateOfBirth DateOfBirth { get; private set; }
   public Name Name { get; private set; }

   public PersonDomain(ID id, DateOfBirth dateOfBirth, Name name)
   {
    if (id == null)
      throw new ArguementNullException("ID cannot be null");
    if (name ==null)
       throw new ArguementNullException("Name cannot be null");
    ID=id;
    Name=name;
    DateOfBirth=dateOfBirth;
   }

}

However, in this case; there is still validation in the constructor.

Questions

My two questions are:

1) Have I understood the mapping between the Domain Model and Data Model (and vice versa) correctly or is there a more elegant way of approaching this (the mapping between the data model and domain model and vice versa)?

2) Should I be putting any validation logic in the constructor of the PersonDomain Entity in this case?

Update 27/02/18

This link helped me most: http://www.dataworks.ie/Blog/Item/entity_framework_5_with_automapper_and_repository_pattern

回答1:

every example I find tells me not to put validation in the constructor.

I think you need to find more examples.

It may help to think about what's going on at a deeper level. Fundamentally, what we are trying to do is ensure that a precondition holds. One way to do this is to verify the precondition "everywhere"; but the DRY principle suggests that we would prefer to capture the precondition at a choke point, and ensure that all code paths that require that precondition must pass through that choke point.

In Java (where DDD began) and C#, we can get the type system to do a lot of the heavy lifting; the type system enforces the guarantee that any use of the type has gone through the constructor, so if we establish in the constructor that the precondition holds, we're good to go.

The key idea here isn't "constructor", but "choke point"; using a named constructor, or a factory, can serve just as well.

If your mapping code path passes through the choke point, great.

If it doesn't..., you lose the advantage that the type checking was providing.

One possible answer is to make your domain model more explicit; and acknowledge the existence of unvalidated representations of domain concepts, which can later be explicitly validated.

If you squint, you might recognize this as a way of handling inputs from untrusted sources. We explicitly model untrusted data, and let our mapping code produce it for us, and then within the domain model we arrange for the untrusted data to pass through the choke points, and then do work on the sanitized variants.

Domain Modeling Made Functional covers this idea well; you can get a preview of the main themes by watching Scott Wlaschin's talk Domain Driven Design with the F# type System



回答2:

1) Have I understood the mapping between the Domain Model and Data Model (and vice versa) correctly or is there a more elegant way of approaching this (the mapping between the data model and domain model and vice versa)?

I would say that the ORM should map the Domain Model (Entity) to the database while you would use a Data Model for representing data to the outside world (UI, REST...).

2) Should I be putting any validation logic in the constructor of the PersonDomain Entity in this case?

It's ok to put domain validation logic into the domain object constructor. But if you want to do validation that is UI specific it should probably be done in some validation class mapped to the Data Model so that you can return a nice error to the user.