1

上个月,我问了一个previous question,关于实体是否应该访问存储库,虽然看起来大多数人认为他们不应该这样做,但我不得不承认对我来说很难得到说服。DDD - 从实体访问存储库,

我有,我真的想不出任何(合理)方式做逻辑没有在我的实体注入库的使用情况:

我们有一个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; 
    } 
} 

你有任何固体替代将支持普遍接受的观点是,这种设计本身不好?

回答

1

从您提供的描述看来,Point实际上不是您的无处不在的语言的一部分 - 区域是。您不需要为商店设置点 - 您可以为其指定一个区域。换句话说,这个操作的签名不应该是setLocation(Point location) - 它应该是assignZone(Zone locationZone)。用户和区域选择的点之间的转换应在域模型操作执行之前进行。这就是我的模型。

评论后添加的示例(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 
     } 
    } 

问题是,您不必欺骗您的域实体中的所有内容 - Zone似乎是Store的角度的Value Object。此外,这些两个概念进一步分离可能导致额外的可能,如识别区不在线,但通过某种后台进程(对于性能或可伸缩性的缘故)。另外,在我看来,考虑到Dependency Injection的原则,它更合适。

毕竟,域名不关心如何将区域创建或检索,它关心商店,它的位置和它落入的区域之间的关联。为什么它应该为点和区域之间的翻译负责?至少这是我看到它的方式。

+0

确实有趣的方法,但这种翻译在哪里呢?另外,我忘了提及Location实际上也是Store实体的一部分,我编辑了我的代码以反映这一点。 – Benjamin

+0

我已经添加了一些解释和示例。 – kstaruch

+0

我非常喜欢你的方法,但是我有点担心与Store的合同,它基本上允许应用程序的任何层为它自己分配不匹配的位置和区域。如何强制**只有** StoreLocationHandler(唯一知道与区域匹配的位置的正确业务规则的StoreLocationHandler)可以调用'store.assignLocation()'?我最喜欢的方式是这个规则是在商店本身执行的,并且在应用程序的任何地方都不可能有违规行为。 – Benjamin

1

对于搜索点的DDD而言,您应该为它提供单独的服务,如LocationService这将封装您的存储库。并且此服务应该对User商店实体一窍不通,并且尤其不要将存储库包含到实体中。

+0

那么服务是我的实体和我的知识库之间的链接呢?我可以在Store实体中注入服务吗? – Benjamin

1

我用同样的方法和你一样,不能确信它是一个糟糕的设计。不过,我有我的资料库界面,所以我不使用ZoneRepository的具体实现,因为这方式,它可能成为辛苦,这取决于不可能测试和模拟它的上下文。

另一点,就像@Samich所说的那样,它听起来很好,可以根据Store对象中的一个点找到一个区域的知识,因为以后您可能想在另一个地方使用此方法。如果您为区域提供服务,那么您将集中您的代码并避免冗余。

+0

我完全同意你这样一个事实,即这种模型与模拟存储库的单元测试很好地兼容!关于服务,根据点找到区域的知识不在Store对象中,它实际上在ZoneRepository中,它也可以从其他地方访问,不是吗? – Benjamin

+0

Benjamin当然,我相信服务是你用来插入业务逻辑的地方,而存储负责人只是抽象的从某处获取数据的方式,在应用程序中看到,我工作的时候我们有cache-repos(mcached)每一个这个回购里面都有另一个回购,我注入(spring.net)一个SqlServer2008实现(*数据导向impl for repos *)..所以如果我有这种规则(如果null然后exeption)这是显然位于在你的领域,因此“一个不集中的商务逻辑”我会把它移动到一个服务和服务然后将消耗yourrepo – renatoargh

+0

我甚至会有我的回购界面,我可以测试/嘲笑他们! (其实就是我工作的方式)..在这个系统架构下工作了4个月之后,我发现它会减慢开发速度,但是您拥有充足的优势,即您的bussines逻辑完全分离/模块化,您花费更多时间进行开发,并节省维护时间,而当你需要维护或发现一个更简单的错误。 (只是我的观点,当然是嘿) – renatoargh

3

这并不一定说这种设计不好。它只是提出了问题。大多数情况下,不需要使用实体内的存储库。让Store根据地理位置查找自己的区域似乎有点奇怪。它看起来不属于Store的责任。

点总是需要吗?如果用户想从“Last 5 Zones”列表中选择区域,该怎么办?或从国家/城市下降?或从搜索文本框?在这些情况下不需要点。需求点和存储库似乎将域模型定制为UI模型。

如果你确实需要每个商店的一个点,那么你可能不需要在同一时间区域字段。如果您只是在需要时才动态查找商店的区域,该怎么办?使用最新的点对点数据库。

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

+0

为好评,并链接到ndepend.com! – renatoargh

+0

的确很有意思。要回答你的问题,是的,位置总是需要的,我们不想手动分配区域,因为区域**必须**匹配位置(对于位置没有两个可能的区域),所以我们希望只强制按位置设置区域(这在我的问题中可能不清楚)。另外,因为我们需要在区域层次结构中搜索商店,动态查找区域的效率非常低。这就是说,你的评论非常有建设性,我理解并且有点赞同你对单一责任原则的担忧! – Benjamin