【问题标题】:DDD Architecture for ASP.NET MVC2 ProjectASP.NET MVC2 项目的 DDD 架构
【发布时间】:2011-04-29 18:14:07
【问题描述】:

我正在尝试使用 Entity Framework 4 为我的新 ASP.NET MVC2 项目使用域驱动开发 (DDD)。在做一些研究后,我想出了以下层约定,每个层在其自己的类项目中:

MyCompany.Domain

     public class User
    {
        //Contains all the properties for the user entity
    }

    public interface IRepository<T> where T : class
    {
        IQueryable<T> GetQuery();
        IQueryable<T> GetAll();
        IQueryable<T> Find(Func<T, bool> condition);
        T Single(Func<T, bool> condition);
        T First(Func<T, bool> condition);
        T GetByID(int id);
        void Delete(T entity);
        void Add(T entity);
        void Attach(T entity);
        void SaveChanges();
    }

  public interface IUserRepository: IRepository<User> {}

    public class UserService
    {
        private IUserRepository _userRepository;
        public UserService(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }
    // This class will hold all the methods related to the User entity
    }

MyCompany.Repositories

public class UserRepository : IRepository<User>
{
    // Repository interface implementations
}

MyCompany.Web --> 这是 MVC2 项目

目前我的 Repositories 层包含对 Domain 层的引用。据我了解,将 UserRepository 注入 UserService 类非常适合单元测试,因为我们可以传入假用户存储库。因此,使用这种架构,我的 Web 项目看起来需要对我的 Domain 和 Repositories 层都有引用。但这是有效的吗?因为从历史上看,表示层只引用了业务逻辑层。

【问题讨论】:

  • 其实DDD就是领域驱动设计

标签: c# asp.net asp.net-mvc-2 domain-driven-design entity-framework-4


【解决方案1】:

关于@RPM1984 答案的一些注释...

但是,在您的问题中,您在域项目中有 IRepository,我会将其放入您的 Repositories 程序集中。

虽然您将东西放在物理位置并不重要,但重要的是要记住存储库的抽象是域的一部分。

我们还有一个服务层在 UI(控制器)和存储库之间进行中介。这允许一个中心位置放置不属于存储库的逻辑 - 例如分页和验证。

我认为仅仅为分页和验证而创建服务并不是一个好主意。如果它是人为创建的,那就更糟了——只是因为“一切都通过服务”和“这样你就不需要从 UI 中引用存储库”。应尽可能避免应用服务。

对于输入验证 - 开箱即用已经很不错了。如果您使用 asp.net mvc,请考虑遵循 MVVM 模式和框架将提供足够好的方法来验证您的输入视图模型。

如果您需要跨多个聚合根进行交互(这表明您可能缺少新的聚合根)- 编写 domain 服务。

您似乎走在了正确的轨道上,但由于它似乎想采用“DDD-All-The-Way”,您是否考虑过实施工作单元模式来管理共享相同上下文的多个存储库?

我认为工作单元也应该避免。 Here's why:

另一个例子,UoW 确实是一个基础设施问题,为什么要将它带入域?如果您的聚合边界正确,则不需要工作单元。


存储库不属于域。存储库是关于持久性(即基础设施)的,域是关于业务的。

存储库承担了多种责任。从一方面来看——企业并不关心它将如何以及在何处保存数据。从其他方面 - 可以通过购物车内容找到客户的知识是(过于简单的示例)。因此 - 我认为只有 abstractions 应该是域的一部分。另外 - 我反对直接使用域模型中的存储库,因为它应该是持久性无知的。

服务层允许“业务验证”的中心点。

Application services 基本上只是facades。如果每一个立面都增加了复杂性,超过了它所解决的问题,那么每一个立面都是不好的。

这是一个糟糕的外观:

public int incrementInteger(int val){
  return val++;
}

应用程序服务不得包含业务规则。这就是领域驱动设计的重点 - 无处不在的语言和孤立的代码,尽可能清晰和简单地反映业务。

MVC 适用于简单的验证,但不适用于复杂的业务规则(想想规范模式)。

这就是它的用途。例如。 - 检查发布的值是否可以解析为应该作为参数传递给域的日期时间。我称之为 UI 验证。有many of them

RE UoW - 我对那句话很感兴趣,但你能详细说明一下吗? UoW 确实是一个基础设施问题,但这又是在我的存储库/数据层,而不是我的域。我的域中只有业务对象/规范。

工作单元模式鼓励我们放宽聚合根边界。基本上 - 它允许我们在多个根上运行事务。尽管它位于域之外,但它仍然对我们做出的域和建模决策产生隐含影响。通常它会导致回到所谓的贫血域模型。

还有一件事:layers != tiers


我要说的另一点是 DDD 是一个指导方针,而不是万能的。

是的,但不应以此为借口。 :)

我们服务层的另一个原因是我们有一个 Web API。我们不希望我们的 Web API 调用到我们的存储库中,我们想要一个很好的流畅接口,Web 应用程序和 API 都可以通过它调用。

如果只是检索 root 并调用它的方法 - 服务只是为了包装它是没有必要的。因此 - 我怀疑您的真正问题是缺乏这种隔离,因此 - 需要协调根之间的交互。

Here你可以看到我当前方法的一些细节。

我们的服务层的另一个重要原因是我们的存储库返回 IQueryable,因此它们没有任何逻辑。我们的服务层将 linq 表达式投射到具体中

这听起来不对。同样的问题 - 缺乏隔离。

在这种情况下 - 存储库没有足够抽象持久性。他们应该有逻辑 - 一个与持久性相关的逻辑。了解如何存储和检索数据。委派“外部某处”会破坏存储库的整个点以及将剩下的所有内容 - 模拟数据访问以进行测试的扩展点(这不是坏事)。

另一件事 - 如果存储库返回原始 IQueryable,它会自动将服务层与未知 (!) LINQ 提供程序联系起来。

而且更不坏的事情(你甚至可能不需要那个) - 因为high coupling,将持久性切换到另一种技术可能相当困难。

不要忘记我们谈论的是技术问题。这些事情与设计本身无关,它们只是使之成为可能。


如果您不使用服务层,您将如何(例如)检索产品的订单列表?您需要在您的存储库中使用一个名为“GetOrdersForProduct”的方法——我认为这不是好的设计。您的存储库界面变得巨大,无法维护。我们使用一个非常通用的存储库(查找、添加、删除等)。这些方法对我们模型中的对象集起作用。通用性/查询能力被带到了服务层

这个很棘手。我就离开good reference

我的意思是,对于未知的提供者 - 你现在无法真正了解服务层下面的内容。它可能是 Linq 到对象,它可能是 Linq 到 xml,Linq 到 sql,NHIbernate.Linq 和一堆其他提供程序实现。坏事是 - 你不知道支持什么。

如果是 Linq to objects,这将运行得很好,如果需要翻译为 sql,则会失败:

customers.Where(c=>{var pickItUp=c.IsWhatever; return pickItUp;});

【讨论】:

  • 我从来没有见过任何人对我有如此不同的看法,很有趣。我们可以争论/不同意的事情太多了,所以我什至不知道从哪里开始。 :) 很高兴看到不同的意见。
  • @RPM1984 这只是我目前的理解。像往常一样 - 没有 spoon 灵丹妙药。
  • 虽然有几点(我无法抗拒)。这些只是我的意见。存储库不属于域。存储库是关于持久性(即基础设施)的,域是关于业务的。服务层允许“业务验证”的中心点。 MVC 适用于简单的验证,但不适用于复杂的业务规则(想想规范模式)。 RE UoW - 我对那句话很感兴趣,但你能详细说明一下吗? UoW 确实是一个基础设施问题,但这又是在我的存储库/数据层,而不是我的域。我所有的域都是业务对象/规范。就是这样。
  • @RPM1984 检查答案以获取响应。 :)
  • "应用程序服务不得包含业务规则" +1, "自动将服务层与未知 (!) LINQ 提供者联系起来" +1, "你现在不能真正了解服务层下面的内容" - 这很棒。我完全同意,因为:您的领域模型的用户不能担心特定的IEnumerable 是否会崩溃。您需要一个一致的界面,例如 IListICollection,其行为是可预测的。让 Repo 担心不同的执行和延迟加载——只要这些问题永远不会下降到领域模型。很棒的帖子!
【解决方案2】:

您可能想探索Sharp Architecture 框架解决此问题的方式,因为看起来您基本上是在重新实现相同的想法。 Northwind application tutorial 对这些概念进行了很好的讨论。

【讨论】:

    【解决方案3】:

    这是非常有效的,实际上与我们的设置非常相似。

    但是,在您的问题中,您在域项目中有 IRepository,我会将其放入您的存储库程序集中。

    您的 Domain 层应该包含您的域实体和业务逻辑。 您的 Repository 层应该具有通用的存储库接口,以及对此的具体实现,每个聚合根都有一个。

    我们还有一个 Service 层在 UI(控制器)和存储库之间进行中介。这允许一个中心位置放置不属于存储库的逻辑——比如分页和验证。

    这样,UI 不引用存储库,只引用服务层。

    我们还使用 DI 将 EntityFrameworkRepository 注入我们的服务层,并将 MockRepository 注入我们的测试项目。

    您似乎走在了正确的轨道上,但由于它似乎想采用“DDD-All-The-Way”,您是否考虑过实施 工作单元 模式来管理多个存储库共享相同的上下文?

    【讨论】:

    • 感谢您的回复。我肯定想使用工作单元模式。你能提供一下这个模式的简要实现吗?
    • 简而言之,使用 EF,它被实现为数据上下文的包装器,它通过存储库的 ctor 传递。因此,您创建一个新的 Uow(它会创建一个新的 DC),然后将其传递给每个存储库(最好使用 DI 容器)。然后,您在 UoW 上“提交”,这相当于 ctx 上的“SaveChanges”,但它将在您使用的所有存储库中保存更改。这里有一个很棒的关于 EF/Repository/UoW 的 MSDN 博客 - blogs.msdn.com/b/adonet/archive/2009/06/16/…。 HTH
    • 发布了一些 cmets 作为答案。
    猜你喜欢
    • 2010-10-23
    • 2011-03-18
    • 2010-10-25
    • 2010-10-01
    • 2013-09-14
    • 1970-01-01
    • 1970-01-01
    • 2012-09-18
    • 1970-01-01
    相关资源
    最近更新 更多