【问题标题】:DDD - Access repositories from entities, bisDDD - 从实体访问存储库,之二
【发布时间】:2011-09-02 23:51:11
【问题描述】:

我上个月问过previous question,关于实体是否应该访问存储库,虽然看起来大多数人认为他们不应该,但我不得不承认我很难被说服。

我有一个用例,我真的想不出任何(合理)方法来执行逻辑而不在我的实体中注入存储库:

我们有一个Store,它被分配了一个Zone(城市、地区、... - 用户定义)。 为了减少负责将 Store 添加到数据库的员工的工作量,并确保一致性,我们不要求他选择要添加 Store 的 Zone。他只是放大地图,点击确定商店位置,然后保存。然后,应用程序必须找到与该位置最相关的区域。

我目前拥有的是这样的:

class Store
{
    protected Zone zone;
    protected Point location;
    protected ZoneRepository zoneRepository;

    public void setLocation(Point location)
    {
        Zone matchingZone = this.zoneRepository.findByLocation(location);
        if (matchingZone == null) {
            throw new ApplicationException(
                "The store location must be within a zone boundary");
        }
        this.location = location;
        this.zone = matchingZone;
    }
}

您是否有任何 可靠 替代方案来支持普遍接受的观点,即这种设计本质上是不好的?

【问题讨论】:

    标签: orm domain-driven-design entity repository-pattern ddd-repositories


    【解决方案1】:

    不一定说这种设计不好。它只是提出问题。大多数时候不需要在实体中使用存储库。让 Store 根据地理点查找自己的区域似乎有点奇怪。看起来这个责任不属于商店。

    是否总是需要积分?如果用户想从“最后 5 个区域”列表中选择区域怎么办?还是从国家/城市下拉菜单?还是从搜索文本框中?在这些情况下没有必要指出。要求点和存储库看起来像是根据 UI 模型定制域模型。

    如果您确实需要为每个商店都设置一个积分,那么您可能不需要同时使用区域字段。如果您仅在需要时动态查找 Store 的 Zone 会怎样?使用最新的点对区数据库。

    此外,如果您的 Store 类有 5 或 6 个方法,并且其中只有一个需要 zoneRepository 字段,那么该类就不是很cohesive

    【讨论】:

    • 确实非常有趣的链接。要回答您的问题,是的,始终需要位置,并且我们不想手动分配区域,因为区域必须与位置匹配(一个位置没有两个可能的区域),所以我们只想强制按位置设置区域(这在我的问题中可能不清楚)。此外,因为我们需要在 Zones 的层次结构中搜索 Stores,动态查找 Zone 的效率非常低。也就是说,您的评论很有建设性,我理解并在一定程度上同意您对单一责任原则的担忧!
    【解决方案2】:

    从您提供的描述看来,Point 实际上并不是您的通用语言的一部分——Zone 是。您没有为商店设置点 - 您为其分配了一个区域。换句话说,这个操作的签名不应该是setLocation(Point location) - 它应该是assignZone(Zone locationZone)。用户选择的点和区域之间的转换应该在执行域模型操作之前发生。我就是这样建模的。

    在 cmets 之后添加的示例(如果您不介意的话,可以使用 C# - 并且只显示一个概念)。代码采用基于命令的方法来执行用户操作 - 我倾向于这样做,但也可能是 Application ServiceController,具体取决于应用程序的结构。

        public class StoreLocationHandler : Handles<LocateStoreCommand>
        {
            public void Handle(LocationStoreCommand command)
            {
                // Location contains coordinates as well as zone info and can be obtained only via LocationService
                Location location = LocationService.IdentifyZone(command.Coordinates); 
    
                Store store = StoreRepository.Get(command.StoreId);
    
                store.AssignLocation(location);
    
                // persist changes - either by Unit of Work or Repository
            }
        }
    

    关键是您不必在域实体中创建所有内容 - 从 Store 的角度来看,区域似乎是值对象。此外,这两个概念的进一步分离可能会带来额外的可能性,例如通过某种后台进程而不是在线识别区域(为了性能或可伸缩性)。此外,在我看来,考虑到Dependency Injection 原则,它更适合。

    毕竟,域并不关心区域是如何创建或检索的,它关心的是 Store 之间的关联、它的位置和它所在的区域。为什么它要负责一个点和一个区域之间的转换?至少我是这么看的。

    【讨论】:

    • 确实很有趣,但是这个翻译应该发生在哪里呢?另外,我忘了提到 Location 实际上也是 Store 实体的一部分,我编辑了我的代码以反映这一点。
    • 我已经添加了一些解释和示例。
    • 我很喜欢你的方法,但我有点担心与商店的这个合同,它基本上允许应用程序的任何层为其自身分配不匹配的位置和区域。您如何强制只有 StoreLocationHandler(它是唯一一个知道将位置与区域匹配的正确业务规则的人)可以调用store.assignLocation()这一事实?我喜欢原始方法中的一点是,这条规则在 Store 本身中强制执行,并且应用程序中的任何地方都不可能出现违规行为。
    • 我明白你的意思。如果保持点和区域之间的一致性很重要,而不是将这两个作为单独的参数传递,我将添加另一个值对象 - 我们称之为位置,它将包含有关坐标和区域的信息。我将修改示例代码以反映这一点。您可能仍然会争辩说,现在逻辑不是在 Store 中强制执行,而是在某些 Service 或 Repository 中强制执行,因此有人可以创建一个人为的、无效的对象并将其传递给您的域。在这种情况下,不允许在此 Service/Repo 之外创建对象。
    【解决方案3】:

    就用于逐点搜索位置的 DDD 而言,您应该为它提供单独的服务,例如 LocationService,它将您的存储库封装在其中。并且该服务应该对User 存储实体一无所知,尤其是永远不要将存储库包含到实体中。

    【讨论】:

    • 那么服务是我的实体和我的存储库之间的链接吗?我可以在 Store 实体中注入服务吗?
    【解决方案4】:

    我使用相同的方法,并且和你一样,无法相信这是一个糟糕的设计。但是,我的存储库有接口,所以我不使用 ZoneRepository 的具体实现,因为这样它可能会变得很困难,并且取决于无法测试和模拟它的上下文。

    另一点,正如@Samich 所说,从 Store 对象中获取基于点查找区域的知识听起来不错,因为稍后您可能想在其他地方使用此方法。如果您为区域实施服务,那么您将集中您的代码并避免冗余。

    【讨论】:

    • 我完全同意你的观点,即这种模型通过模拟存储库与单元测试很好地兼容!关于Service,基于一个点找一个zone的知识不在Store对象中,它实际上是在ZoneRepository中,也可以从其他任何地方访问,不是吗?
    • 当然,本杰明,碰巧我相信服务是您用来插入业务逻辑的地方,而存储库只负责抽象一种从任何地方获取数据的方式,看,在我工作的应用程序中,我们有缓存回购(mcached)这个回购中的每一个里面都有另一个回购,我注入(spring.net)一个SqlServer2008实现(面向数据的回购实现)..所以如果我有这种明确位于您的域中的规则(如果为空,则为例外)因此“非集中式业务逻辑”我会将其移至服务,然后该服务将使用您的存储库
    • 我什至可以为我的存储库提供接口,我也可以测试/模拟它们! (它实际上是我的工作方式).. 在使用这个系统架构工作了 4 个月后,我发现它会减慢你的开发速度,但你有一个巨大的优势,即你的业务逻辑完全分离/模块化,你花了更多的开发时间,但节省了维护,当您需要维护或发现错误时,它变得更加简单。 (只是我的观点,当然嘿嘿)
    • 这是有道理的。我想我只是在理解服务的概念(和需求)方面还没有突破!那么您将如何模拟我的需求:您是在 Store 中注入 Service,还是使用 Location 参数调用 Service,然后将 Location 和 Zone 都分配给 Store?
    猜你喜欢
    • 1970-01-01
    • 2013-12-05
    • 2017-05-18
    • 2016-11-30
    • 2010-11-24
    • 2011-09-28
    • 2018-08-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多