DDD和entity framework类

我已经阅读了很多关于DDD的文章并且理解,我应该在基础结构级别使用我的域模型类,因此,我应该使用与Entity Framework基础结构相同的类并使用它们来生成表(代码优先方法)等。但是我的域模型可以与Relational DB模型完全不同。

为什么我不能创建另一个模型,基础架构模型,创建关系数据库模型,不要将域模型与EF类混合?

考虑这个简单的例子:

领域模型

public class Customer { public Customer(IRegistrar registrar) { this.registrar = registrar; } public int Age { get { // Just for this example. This will not work for all locals etc but beyond the point here. var today = DateTime.Today; return today.Year - this.DateOfBirth.Year; } } public DateTime DateOfBirth { get; set; } public int Register() { if (this.Age < 18) { throw new InvalidOperationException("You must be at least 18 years old"); } int id = this.registrar.Register(this); return id; } } public interface IRegistrar { public int Register(Customer customer); } 

很多人没有域模型时会在MVC控制器中执行此操作:

 public ActionResult Search(Customer customer) { var today = DateTime.Today; var age = today.Year - this.DateOfBirth.Year; if (age < 18) { // Return an error page or the same page but with error etc. } // All is good int id = this.registrar.Register(customer); // The rest of code } 

有一些问题:

  1. 如果开发人员在致电registrar之前忘记检查年龄,该怎么办? 很多人会说,这是一个糟糕的开发者。 无论如何,这种类型的代码容易出错。

  2. 该产品运行良好,因此CFO决定开放API,因为有许多开发人员正在为客户注册制作出色的UI界面,他们希望使用我们的API。 所以开发人员继续创建这样的WCF服务:

     public int Register(Customer customer) { var today = DateTime.Today; var age = today.Year - this.DateOfBirth.Year; if (age < 18) { // Return a SOAP fault or some other error } int id = this.registrar.Register(customer); // The rest of code } 
  3. 现在,开发人员可能忘记在2个不同的地方检查年龄。

  4. 代码也在两个不同的地方。 如果有错误,我们需要记住在两个不同的地方修复它。
  5. 如果公司开始在法定年龄为21岁的地方开展业务,我们需要找到所有地方并添加此规则。
  6. 如果我们正在与BA讨论规则,那么我们需要查看所有应用程序并找到规则。

在上面的例子中,我们只有一条规则:年龄必须大于18.如果我们有更多的规则和更多的课程怎么办? 你可以看到这将走向何方。


EF模型

您的EF模型可能如下所示:

 public class Customer { public int Id { get; set; } public DateTime DateOfBirth { get; set; } // It may have a foreign key etc. } 

应用层模型

你的MVC视图模型可能是这样的:

 public class Customer { // Or instead of Domain.Customer, it may be a CustomerDto which is used // to transfer data from one layer or tier to another. // But you get the point. public Customer(Domain.Customer customer) { this.DateOfBirth = customer.DateOfBirth; this.Age = customer.Age; if (this.DateOfBirth.DayOfYear == DateTime.Today.DayOfYear) { this.Greeting = "Happy Birthday!!!"; } } public int Age { get; set; } [Required(ErrorMessage = "Date of birth is required.")] [Display(Name = "Data of birth")] public DateTime DateOfBirth { get; set; } public string Greeting { get; set; } } 

这是一个问题:您在Display属性中看到了多少个EF模型? 我将让您决定EF模型是否应该关注它在UI中的显示方式。 假设我的EF模型将在UI中显示是错误的。 也许我class上唯一的消费者是另一个网络服务。 我不认为Display应该在EF模型中,但有些人可能不同意我的意见; 你打电话。

stackoverflow上有很多关于人们要求有时需要PropertyX的问题,有时它不是,我怎么能这样做? 好吧,如果你没有在你的EF模型上放置Required属性并在你的视图中使用你的EF模型,那么你就不会遇到这个问题。 视图将有一个模型,其中PropertyX是必填字段。 该模型将使用Required属性修饰PropertyX,而不需要PropertyX的另一个视图模型将不会使用Required属性修饰该属性。


的ViewModels

然后你可能有一个WPF应用程序的客户的viewmodel,你可能有一个前端的javascript视图模型(KnockoutJS viewmodel)。


结论并回答您的问题

总而言之,您可以拥有与实体模型不同的域模型。 您的域模型应该不知道数据库。 如果由于规范化而决定从一个表中删除列并将其放入自己的表中,则实体模型将受到影响。 您的域模型不应受到影响。

我已经阅读了网上的论点,例如“这个设计需要太长时间,我只想快速推出一些东西并将其交给客户并获得报酬”。 好吧,如果你没有设计一个需要维护的产品,并且会添加function,但你只是为你的客户设计一个快速的小网站,那么就不要使用这种方法。 没有任何设计适用于所有情况。 要带走的是,您的设计应该明智地选择未来。

此外,从实体模型到域到MVC模型的转换不需要手动完成。 有些库可以很容易地为你做这些,比如AutoMapper 。

但我不得不承认,网络上有大量的例子,并且在许多应用程序中使用,其中实体模型在整个应用程序中使用,规则在任何地方都使用if语句加载。

这与DDD的关系

当我读到你的问题时,我发现一些引人注目的东西。 就是这个:

我已经阅读了很多关于DDD的文章并且理解,我应该在基础结构级别使用我的域模型类,因此,我应该使用与Entity Framework基础结构相同的类并使用它们来生成表(代码优先方法)

说实话,DDD知识的最佳来源仍然是蓝皮书。 我知道,我知道,它很厚,很难读。 可以看看Vernon蒸馏的DDD。 结论应该是DDD并不是真正关注持久性,而是更深入地了解领域,更好地了解您的领域专家。 当然,它对ORM一无所知。

域模型持久性

域模型通常由具有状态和行为的对象(如果我们讨论面向对象的模型)组成。 模型将具有一个或多个实体,并且可以是一些值对象。 在许多情况下,每个有界上下文只有一个实体。 实体按聚合分组,这些聚合一起更改,形成事务边界。 这意味着无论此更改触及多少个实体, Aggregate中的每个更改都是一个事务。 每个Aggregate都有一个且只有一个实体Aggregate Root ,它公开了其他人使用整个Aggregate的公共方法。

所以你的存储库应该照顾:

  • 对于新的和更新的对象,在一个事务中保持整个聚合 (无论有多少个实体)
  • 通过其标识( Aggregate Root Id属性)从持久性存储中获取整个Aggregate

您肯定需要一些查询但他们可以在不修改域模型状态时查询他们想要的内容。 许多人向存储库添加查询方法,但这取决于您。 我将它们作为一个单独的静态类与DbContext扩展方法实现。

模型彼此不匹配

您提到您的持久性模型与域模型不匹配。 情况可能就是这种情况,但在许多情况下并非如此。 有几种方法可以解决这个问题:

  • 保持状态与行为分开,并将其作为域对象中的属性。 就像使用AddLineOrder一样, OrderState包含所有这些TotalCustomerId和类似的东西。 请记住,这可能不适用于复杂的聚合。
  • 专注于我上面提到的存储库的两个主要方法 – AddGet 。 每个存储库仅适用于一种类型的聚合 ,您在它们之间的映射取决于您。
  • 结合上述观点,您可以重新考虑使用ORM并执行其他操作。 基本上你可以使用ADO.NET,但最简单的是使用某种面向文档的东西,比如NoSQL,尽管很多人不同意。 另请查阅本文关于PostgreSQL JSONB存储作为持久性的文章。

请记住,重点是让Repository能够为您完成工作,并且可能(可能这种情况永远不会发生,但仍然)使用另一个存储。

您可能也对另一篇Vernon的文章感兴趣,他在文章中特别讨论了使用EF的问题。