领域驱动设计 - 使用代码优先方法

时间:2014-05-12 18:47:02

标签: entity-framework-4 domain-driven-design

使用代码优先方法从域模型创建数据库是一种好习惯,然后指示数据库模式的外观(使用实体框架)。或者,您是否应该首先设计数据库,然后创建域模型?

1 个答案:

答案 0 :(得分:0)

从2009年到2013年,这正是我所做的(并回答了许多SO问题!) - 使用实体框架作为我们的"聚合根源"并使用值对象(poco类)等从那里扩展它们。

我今天在这里告诉你:不要做它!

最新的" rutt-rough"来自DDD的C#是我们大多数人这样做的 - 直接"表到实体/聚合"映射。所有这一切都是在没有任何有限背景的情况下给出错误的分离感。

过去,我经常创建一个名为" Users"的文件夹。我还有一个名为User.cs的实体,用于代码优先(或者在设计器中,一个名为User的表)。我被允许将自己的方法和属性添加到User.cs部分。我还会在子文件夹中对与用户相关的所有值对象进行分组。所以,它看起来像这样:

\Users\
  ^- User.cs partial with additional so called "logic"
   - UserService acting with "business logic" on User and related properties.

\users\EmailTemplates\
         ^- I often needed a way in DDD to store other "Entities" related to this AR.

\Users\ValueObjects\
         ^- UserAddress.cs
          - UserTarget.cs
          - UserTypeEnum.cs
          - // basically a dumping ground

现在,经过一年的努力,我们已经意识到我们在UserTarget上做了很多工作,这是巨大的。它正在成为一个类似服务的巨大类,拥有1000行代码。在DDD中,这应该是它自己的有界上下文,称为UserTargets,并作为自己的聚合根,其中包含许多值对象。但是,数据库中没有存储任何内容。我们首先在代码中创建一个实体吗?在属性中有一个例外可以忽略它吗?等。

你切割它的感觉真是太脏了。

让我们从DDD剧本中拿出另一个例子:

Biz:"我有一个需要注册的用户。一旦他们注册,他们就会有一个项目。"

开发团队:"很棒,我们有以下实体:用户和项目"

public class User {
  public List<Projects> Projects { get; set; }
  public Team Team { get; set; }
}

public class Team {
  public List<Projects> Projects { get; set; }
}

public class Project {
  public User Administrator { get; set; }
  public List<User> Members { get; set; }
}

仅此一个问题。既然我们经常使用CQRS /事件采购,那么更容易对其进行建模:

User : AggreateRoot
Team : AggregateRoot
Project : AggregateRoot
UserProject : AggregateRoot
UserSignup : AggregateRoot
TeamProject : AggregateRoot
...etc

请注意其他聚合?你会如何在数据库中代表他们? FK对吗?那么,如果有像TeamProject.ContestEntrySubmitted datetime这样的其他属性会怎样?是的,现在那些FK子表在EF和代码中变得丑陋。

该域名适用于您的纯商业设计 - 解决方案。不是因为您可以组织多少POCO来使EF在代码中感到满意。不是你的CRUD层绕过所有业务逻辑,新建一个ObjectContext,并调用Save(new User())。我看到开发人员在这个或那个EF问题上浪费了太多时间,与#34;我们是否支持DDD以使EF做this?或者我们花了几天时间改变EF的内部和T4来代替that?我们仍然是DDD-ish,但很多人都做了很多工作。&#34;

所以我听到了,&#34;为什么要这样做?&#34;将域与任何存储模型分开。

在CQRS中,您的&#34;域&#34;无论如何都是只写的。将它们作为EF对象已经将事件从窗口中抛出,现在迫使你处理状态。

通过自由编写域聚合和实体,我们可以将多个项目或用户绑定到彼此,而不会影响其他聚合。如果其他聚合,比如用户,想要项目计数,当然,请为创建它的用户ID和ProjectCount ++监听ProjectCreated事件并在当天调用。

关键是,一旦您将DDD应用于事件采购模式,您就会以完全不同的方式查看您的域。它不再是一个有状态的&#34;在某种意义上,您可以随时查询域以获得注册的最近10年,或者知道您可以直接访问数据库并运行该SQL。不,更像是,&#34;我需要对X实体进行状态更改。它将由此,以及处于这些特定状态时产生。现在,当行星对齐时,我们会在新的ChangedUSernameBecausePlanetsAligned事件中将用户名更改为此。&#34;

我想我的观点是我试图让EF Entities-as-DDD AggregateRoots最终感觉如此干燥,并且没有任何DDD物质。你最终得到近30或50&#34; aggreateroot&#34;单片MyProject.Domain程序集中的文件夹。而Udi基本上称之为“泥球大球”。

我的一般规则现在不是为我的域使用任何框架 - 让我在纯粹的DDD中按照我认为合适的方式构建它,并且它非常有趣。没有限制框架或限制。我实际上已经在F#中重写了整个域名,只是为了好玩,因为不需要任何&#34;查询&#34;,它刚刚发生。

然后,wjem进入事件采购世界,你开始明白为什么你的域名是&#34;只写&#34;并考虑如何能够推动&#34;数据到视图,而不是从您的域中查询要查询的内容。从您的域中删除该查询部分确实使这个很好而且整洁。

- 呃,我想我刚刚意识到我写了一个答案基本上说,&#34;嘿,不要做实体框架和DDD。请查看CQRS和DDD。&#34; LOL