DDD和Entity Framework类

时间:2017-01-06 19:13:48

标签: c# entity-framework model domain-driven-design

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

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

2 个答案:

答案 0 :(得分:2)

考虑这个简单的例子:

域名模型

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讨论规则,那么我们需要查看所有应用程序并查找规则。
  7. 在上述情况下,我们只有一条规则:年龄必须大于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中显示是错误的。也许我班上唯一的消费者是另一个网络服务。我不认为Display应该在EF模型中,但有些人可能不同意我的看法;你打电话。

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

    <强>的ViewModels

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

    结论并回答您的问题

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

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

    此外,无需手动完成从实体模型到域到MVC模型的转换。有一些库可以轻松地为您完成此操作,例如AutoMapper

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

答案 1 :(得分:-1)

此与DDD的关系

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

  

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

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

域模型持久性

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

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

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

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

彼此不匹配的模型

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

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

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

您可能还对另一个Vernon's article感兴趣,在那里他专门讨论使用EF。