【问题标题】:Projections from Repository in classic and DDD perspective经典和 DDD 角度的存储库投影
【发布时间】:2013-04-04 13:45:42
【问题描述】:

我想谈谈您如何看待存储库模式。

在“旧”域概念(例如来自P of EAAA)中,存储库应该像“内存中的集合”,所以它应该总是返回相同的类型,所以如果你需要一个投影,你必须制作它出,所以投影将在服务层进行,对吧?还是可以直接进入“Domain”项目?

例如

public class CustomerRepository
{
    //Constructor accepts an IRepository<Customer>

    public IQueryable<Customer> GetAllDebtors()
    {
        //Use IRepository<Customer> here to make the query
    }
}

相反,在 DDD 中,存储库,尤其是与 CQRS 结合使用,可以直接返回投影类型,因为存储库成为非规范化服务,对吧?

例如

public class CustomerDenormalizer
{
    //Constructor *could* accept an IRepository<Customer>

    public IQueryable<Debtor> GetAllDebtors()
    {
        //Use IRepository<Customer> here to make the query and the projection
    }
}

【问题讨论】:

    标签: c# domain-driven-design repository-pattern


    【解决方案1】:

    IMO,与“内存中”集合的对应关系被过分强调了。存储库不应该隐藏它封装了一些繁重的 IO 的事实——这将是一个泄漏的抽象。此外,IQueryable&lt;T&gt; 也是一个泄漏抽象,因为几乎没有任何提供者会支持所有操作。我建议将尽可能多的项目委托给数据库,因为它非常擅长。

    CQRS 中的投影有些不同。它通常被实现为一个事件消费者,它更新存储在一些底层存储机制中的数据结构,这些机制本身可能是 SQL 服务器或键值存储。主要区别在于,在这种情况下,是对来自消息队列的事件的投影响应。这些事件可能来自外部系统。

    【讨论】:

    • 谢谢,我并不是说在“旧”方法中投影没有完成,我的意思是服务完成了它,而不是存储库,如果你通过一个 IQueryable 是可能的,不加载整个对象。我更喜欢存储库也进行投影,但我正在与一位同事讨论这个不同意的问题。
    • 我不确定我是否明白你在问什么。问题是你是否应该使用 IQueryable 在内存中进行预测?
    • 我的问题是,在“旧”域方法中,我可以将投影直接放入存储库,因此我返回不同类型的对象(基于投影的类型)还是更好始终返回“基本”实体类型并在我使用存储库的外部(例如在服务层中)进行投影。显然,这两种方法都不应该加载完整的对象。所以问题是:从设计的角度来看,在哪里进行投影更好?对我来说,就是让它进入存储库。
    • 如果您在内存中进行投影,则将它们封装在存储库中。这样,返回投影的责任只委托给存储库,而不是分散在其他类中。
    • +1 恕我直言,任何暴露 IQueryable&lt;T&gt; 的东西根本不是存储库,只是有点胖。
    【解决方案2】:

    说“原始形式”的存储库必须只返回相同实体类型的对象,这有点夸张。例如,人们一直在他们的存储库中包含 Count() 方法或其他计算 - 这甚至在 Evan 的蓝皮书中都有记录。

    另一方面,我不确定您所说的“非规范化类型”是什么意思,但如果这是从多个不同实体借用以按原样或合并的方式公开其数据,或公开单个域实体的部分视图的类型,我倾向于认为它不再是域。事实证明,它们通常用于特定于应用程序的目的,例如生成 Excel 报告或显示统计数据或摘要屏幕。

    如果是这种情况,并且出于性能原因,我想直接利用数据库而不是依赖域,我更愿意创建一个单独的项目,在其中放置所有这些“报告”相关逻辑。没关系,如果您仍然在其中将数据访问对象命名为 Repositories(毕竟,它们也是内存中集合的幻觉,只是其他类型的集合)。

    【讨论】:

    • 不,我认为非规范化意味着来自多个数据库表的字段(无论是否通过视图)。
    【解决方案3】:

    在“旧”域概念中(例如来自 EAAA 的 P),存储库应该像“内存中的集合”,所以它应该总是返回相同的类型,所以如果你需要一个投影,你必须制作它出,所以例如在服务层进行投影,对吧?

    在我自己的解决方案中,I keep distinct 领域模型(这是我从领域专家那里学到的语言的 C# 表达式,几乎是内部 DSL)和与领域相关的应用程序关注点(例如存储库,可以应对具有持久性的应用程序需要)。这意味着它们在不同的项目中编码。

    在这样的结构中,我尝试了两种不同的方法:queryable repositories and custom ones

    • 实现 IQueryable 的存储库非常适合使用该域构建 UI 或向第三方公开的服务的开发人员,但需要在基础架构方面进行大量工作。我们在这方面使用了不同的方法,从 Linq2NHibernate 到 re-linq,各有利弊,但每一种都相当昂贵。如果您打算使用此技术,请定义良好的指标,以确保您在应用程序开发期间节省的时间值得花在自定义基础架构上的时间。
    • 自定义存储库(那些公开返回IEnumerables 的方法)更容易设计和实现,但它们需要UI 和服务的开发人员付出更多的努力。我们还有一个案例,域规则需要使用自定义存储库,因为用于获取结果的query objectsspecifications,这也是通用语言的一部分,我们(法律上)需要授予使用的方法to query 和表示这样的值和谓词是一样的。
      但是,在自定义存储库中,我们也经常公开投影方法。

    或者是否可以直接进入“域”项目?

    这是可能的,但是当您要处理许多不同(且非常复杂)的有界上下文时,这会变得很痛苦。这就是为什么我为表达普遍存在的语言的领域类和服务于应用目的的类使用不同的项目。

    相反,在 DDD 中,存储库,尤其是与 CQRS 结合使用,可以直接返回投影类型,因为存储库成为非规范化服务,对吧?

    是的,他们可以。
    这就是我们对自定义存储库所做的事情,即使没有 CQRS。此外,即使某些存储库实现了IQueryable,我们偶尔也会公开直接生成投影的方法。

    【讨论】:

    • 从我的角度来看,存储库是域逻辑的重要组成部分,例如CustomerRepository.GetAllGreatDebtorsOrderedByOlder() 实现了一个业务规则,所以我将它们放入域项目中。请注意,CustomerRepository 不继承自任何类,因此它没有基本方法,而只有“业务方法”。
    • 我建议您从存储库中删除此类业务规则。尝试类似:CustomerRepository.GetCustomerSatifying(greatDebtorsSpecification, OrderBy.Older)。相关的business 规则应该是领域模型的一部分。但是请注意,如果此类规则对其他 对象的行为没有影响,则它不是业务规则,而是应用程序问题!
    • 我更喜欢存储库的特定方法而不是规范模式,因为它更面向 BDD,在方法内部我可以像现在一样使用规范模式。也因为我的 Repository 接受 IRepository 到构造函数中,所以与持久性技术无关。
    • 我同意@GiacomoTesio 业务规则在存储库中没有位置。存储库应该只关心持久化/恢复域对象。另外,我认为存储库实现应该非常了解底层存储。另一个存储库之上的存储库对我来说毫无意义。
    【解决方案4】:
    • 没有通用存储库。
    • 没有 IQueryable。
    • 拥有 ICustomerRepository。
    • 让您的 CustomerRepository 的特定实施利用底层存储系统的所有花里胡哨。
    • 有存储库返回预测。

      公共接口 ICustomerRepository {

      公共 IEnumerable GetAllCustomer()

      public IEnumerable GetAllDebtors()

      public IEnumerable GetCustomerSummaryByName(字符串名称)

      public IEnumerable GetCustomerSummaryById(string id) }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-05
      • 2010-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-23
      相关资源
      最近更新 更多