分离领域模型和数据模型

3

我的问题与这个问题类似:仓储模式与领域模型和实体框架之间的映射

我在这里读了很多关于以下内容的文章:

  1. 将ORM直接映射到领域模型

  2. 将ORM映射到数据模型,然后将数据模型映射到领域模型(反之亦然)

我理解两种方法的优点和局限性。我也理解哪种方法在什么场景下更受欢迎。

网上有很多示例演示如何执行选项1。但是,我找不到任何示例代码来显示如何执行选项2。我阅读了关于选项二的问题,例如本帖子第一行引用的问题,即问题涉及选项二,但答案涉及选项一,并且有评论指出选项二可能更合适。

因此,我的问题具体涉及从映射和验证角度执行选项一的方式:

映射

我相信我可以在将领域模型映射到数据模型时执行此操作:

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

我相信在将数据模型映射到存储库中的领域模型时(为了保护不变量),我已经做到了这一点:

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

验证

我想在PersonDomain类中实现此操作:

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 ArgumentException("Guid cannot be empty");
       if (name =="")
           throw new ArgumentException("Name cannot be empty");
       ID = id;
       Name = NAME;
       DateOfBirth = dateOfBirth;
   }
}

然而,我找到的每个示例都告诉我不要将验证放在构造函数中。我想到的一个办法是避免原始类型偏执,具体如下:

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 ArgumentNullException("ID cannot be null");

       if (name ==null)
           throw new ArgumentNullException("Name cannot be null");

       ID = id;
       Name = name;
       DateOfBirth = dateOfBirth;
   }
}

然而,在这种情况下,构造函数中仍然有验证。
问题:
我的两个问题是:
1. 我是否正确理解了领域模型和数据模型之间的映射(以及反之),或者有更优雅的方法来处理这个问题(数据模型和领域模型之间的映射和反之)?
2. 在这种情况下,我应该在PersonDomain实体的构造函数中放置任何验证逻辑吗?
更新于27/02/18
此链接对我最有帮助:http://www.dataworks.ie/Blog/Item/entity_framework_5_with_automapper_and_repository_pattern

1
你可能想要看一下https://vaughnvernon.co/?p=879 - guillaume31
@guillaume31,谢谢。他在他的书中讨论了这种方法吗?哪一章?也许需要买这本书。 - w0051977
据我记得,他不会。书中的代码是用Java + Hibernate编写的。 - guillaume31
不完全是。我使用了他在博客文章中解释的变体,效果不错。需要注意的是,与Entity Framework相比,NHibernate可能没有相同的技术限制,所以你可能更喜欢选项1)。 - guillaume31
@guillaume31,将NHibernate映射到领域模型时会丧失一些面向对象的优势,例如无法使用seal类、必须将成员设置为virtual、必须有零参数构造函数等。你知道还有其他的代码示例(可能不是完整的示例)吗?我不确定是否喜欢将状态作为领域对象的一个成员来单独保存。 - w0051977
显示剩余5条评论
2个回答

4
我发现的每个例子都告诉我不要将验证放在构造函数中。
我认为你需要找到更多的例子。
深入思考可能会有所帮助。从根本上说,我们试图做的是确保前置条件成立。一种方法是"全面"验证前置条件;但DRY原则表明,我们更喜欢在一个关键点捕获前置条件,并确保所有需要该前置条件的代码路径必须通过该关键点。
在Java(DDD的起源)和C#中,我们可以让类型系统承担大部分重载;类型系统强制保证类型的任何使用都经过了构造函数,因此,如果我们在构造函数中建立了前置条件成立的条件,我们就可以安心使用了。
这里的关键思想不是“构造函数”,而是“瓶颈”; 使用命名构造函数或工厂同样可以起到作用。
如果您的映射代码路径通过了瓶颈,那很好。
如果没有……,您将失去类型检查提供的优势。
一个可能的解决方案是使您的领域模型更加明确; 并承认领域概念的未经验证的表示存在,稍后可以明确验证它们。
如果您眯起眼睛看,您可能会认识到这是处理来自不受信任来源的输入的一种方式。 我们明确地建模不受信任的数据,并让我们的映射代码为我们生成它,然后在领域模型内部,我们安排不受信任的数据通过瓶颈,然后对经过消毒的变体进行处理。

领域建模的函数式方法很好地涵盖了这个想法;你可以通过观看Scott Wlaschin的演讲F#类型系统中的领域驱动设计来预览主题。


如果你开始将数据库视为不可信的来源(这取决于软件的重要性),那么你必须考虑在公共界面之外再增加一层不变执行层的成本,因为在重新加载和正常使用过程中,数据的分配方式并不总是相同的。也许使用一个不可变的事件源数据存储,并通过通过公共接口重放所有事件来恢复聚合对象,既更安全又更易维护。 - guillaume31

0
1) 我是否正确理解了域模型和数据模型之间的映射(以及反过来),或者是否有更优雅的方法来处理这个问题(数据模型和域模型之间的映射)?
我认为ORM应将“域模型(实体)”映射到数据库,而使用“数据模型”表示对外界(UI、REST等)的数据。
2) 在这种情况下,我应该将任何验证逻辑放入PersonDomain实体的构造函数中吗?
在领域对象的构造函数中放置领域验证逻辑是可以的。但如果您想进行特定于UI的验证,可能应该在与数据模型映射的某个验证类中完成,以便向用户返回一个友好的错误提示信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接