DDD - 从实体访问存储库,之二
上个月我问过上一个问题,关于实体是否应该访问一个存储库,虽然看起来大多数人认为不应该,但我不得不承认我很难相信。
我有一个用例,我真的想不出任何(合理)方法来执行逻辑而不在我的实体中注入存储库:
我们有一个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;
}
}
您是否有任何可靠的替代方案来支持这种设计本质上不好的普遍接受的观点?
I've asked a previous question last month, about whether an Entity should access a Repository, and although it looks like most people think they shouldn't, I have to admit it's hard for me to get convinced.
I have a use case for which I really can't think of any (reasonable) way to do the logic without injecting the Repository in my Entity:
We have a Store
, which is assigned a Zone
(city, district, ... - user defined).
To reduce the workload of the employee in charge of adding Stores to the database, and to ensure consistency, we don't ask him to select the Zone in which he wants to add the Store. He just zooms on a map, clicks to pinpoint the Store location, and saves. The application then has to find the most relevant Zone for this location.
What I currently have is something like:
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;
}
}
Do you have any solid alternative which would support the commonly accepted opinion that this design is inherently bad?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
不一定说这个设计不好。它只是提出了问题。大多数时候,不需要从实体内部使用存储库。让商店根据地理位置查找自己的区域似乎有点奇怪。看起来这个责任不属于商店。
点总是需要的吗?如果用户想要从“最后 5 个区域”列表中选择区域怎么办?或者从国家/城市下拉列表中?或者从搜索文本框?在这些情况下没有必要指出。需要点和存储库似乎是根据 UI 模型定制域模型。
如果您确实需要每个商店都有一个积分,那么您可能不需要同时需要区域字段。如果您仅在需要时动态查找商店区域怎么办?使用最新的点到区数据库。
另外,如果您的 Store 类有 5 或 6 个方法,并且只有其中一个需要 zoneRepository 字段,那么该类就不是很 有凝聚力。
It would not necessarily say that this design is bad. It just raises questions. Most of the time there is no need to use repository from within an Entity. Having Store look up its own zone based on geographical point seems a bit strange. It does not look like this responsibility belongs to Store.
Is point always required? What if the user would want to select zone from the list of 'Last 5 Zones'? Or from Country/City drop downs? Or from search text box? There is no need for point in these cases. Requiring point and repository would seems like tailoring domain model to UI model.
If you indeed need a point for every single Store then you may not need Zone field at the same time. What if you lookup Store's Zone dynamically, only when you need it? Using most up to date Point-to-Zone database.
Also if your Store class has 5 or 6 methods and only one of them needs zoneRepository field than the class is not very cohesive.
从您提供的描述来看,Point 实际上并不是您的通用语言的一部分,而 Zone 则是。您无需为存储设置一个点 - 您可以为其分配一个区域。换句话说,此操作的签名不应该是
setLocation(Point location)
- 它应该是assignZone(Zone locationZone)
。用户选择的点和区域之间的转换应该在执行域模型操作之前发生。这就是我建模的方式。在注释后添加示例(如果您不介意,则为 C# - 并且仅显示一个概念)。代码采用基于命令的方法来执行用户操作 - 这就是我倾向于这样做的方式,但甚至可能是应用程序服务或控制器,具体取决于应用程序的结构。
要点是,您不必在域实体内创建所有内容 - 从存储的角度来看,区域似乎是值对象。此外,这两个概念的进一步分离可能会带来额外的可能性,例如不在线而是通过某种后台进程(出于性能或可扩展性的考虑)来识别区域。此外,在我看来,考虑依赖注入原则更适合。
毕竟,域并不关心区域是如何创建或检索的,它关心的是 Store、它的位置和它所属的区域之间的关联。为什么它要负责点和区域之间的转换?至少我是这么看的。
From the description you've provided it seems that Point is actually not part of your Ubiquitous Language - the Zone is. You don't set a point for store - you assign a Zone to it. In other words, the signature of this operation shouldn't be
setLocation(Point location)
- it should beassignZone(Zone locationZone)
. The translation between point selected by user and the zone should occur before domain model operation is performed. That's how I'd model it.Example added after comments (C# if you don't mind - and shows only a concept). Code assumes command-based approach to performing user actions - that's how I tend to do it, but might be an
Application Service
orController
even, depending on the structure of your application.The point is that you don't have to craete everything inside your domain entities - Zone seems to be Value Object from perspective of Store. Besides, further separation of those two concepts might lead to additional possibilities, like identifying the zone not online but through some kind of background process (for the sake of performance or scalability). In addition, in my opinion it fits better considering Dependency Injection principle.
After all, domain doesn't care about how the zone is created or retrieved, it cares about association between Store, its location and the zone it falls into. Why should it be responsible for translation between a point and a zone? At least that's the way I see it.
就按点搜索位置的 DDD 而言,您应该为其提供单独的服务,例如
LocationService
,它将封装您的存储库。并且此服务应该对User
存储实体一无所知,尤其是永远不要将存储库包含到实体中。In terms of DDD for searching Location by point you should have separate service for it like
LocationService
which will encapsulate your repository inside. And this service should know nothing aboutUser
store entity and especially never include repository into the Entity.我使用同样的方法,和你一样,无法确信这是一个糟糕的设计。然而,我有到我的存储库的接口,所以我不使用 ZoneRepository 的具体实现,因为这样它可能会变得很困难,并且根据上下文无法测试和模拟它。
另一点,正如 @Samich 所说,根据 Store 对象中的点查找区域的知识听起来不错,因为稍后您可能想在另一个地方使用此方法。如果您为区域实现服务,您将集中您的代码并避免冗余。
I use the same approach and as you, couldnt get convinced that its a bad design. HOWEVER, I have interfaces to my repositories, so I dont use concrete implementations of ZoneRepository as this way it could become to hard, and depending on the context impossible to test and mock it.
The other point, as @Samich said, is that its sounds good to take the knowledge of finding a zone based on a point from the Store object as later you may wanna use this method at another place. If you imoplement a service for the Zones youll be then centralizing your code and avoiding redundancy as well.