使用数据映射器模式,实体(领域对象)是否应该知道映射器?

11
我是第一次使用Doctrine2,但我认为这个问题足够通用,不依赖于特定的ORM。
在数据映射器模式中,实体是否应该知道 - 并使用 - 映射器?
我有一些具体的例子,但它们似乎都可以归结为相同的一般问题。
例如,如果我正在处理来自外部源的数据 - 例如一个用户有许多消息 - 而外部源仅提供最新的一些实体(如RSS提要),那么除非它知道映射器或者搜索整个集合查找重复项(似乎效率低下),否则如何检查重复项?
当然,控制器或事务脚本可以在将消息添加到用户之前检查重复项 - 但这似乎不太好,会导致代码重复。
如果我有一个大的集合 - 再次是一个用户和许多消息 - 用户实体如何为集合提供限制和分页而不实际代理映射器调用?
同样,控制器或事务脚本或者使用实体的任何其他东西都可以直接使用映射器检索由计数、日期范围或其他因素限制的用户消息集合 - 但这也会导致代码重复。
答案是使用仓库并使实体意识到它们吗? (至少对于Doctrine2和其他ORM使用的类似概念来说。)此时,实体仍然相对独立于映射器。
3个回答

8
规则1:保持您的领域模型简单明了。
首先,不要过早优化某些东西,因为您认为它可能效率低下。构建您的领域,使对象和语法正确流动。保持接口干净:$user->addMessage($message) 是干净、精确且无歧义的。在底层,您可以利用任何数量的模式/技术来确保维护完整性(缓存、查找等)。您可以利用服务来协调(复杂的)对象依赖关系,这可能有点过头了,但这里是一个基本的示例/想法。
class User
{
  public function addMessage(Message $message)
  {
     // One solution, loop through all messages first, throw error if already exists
     $this->messages[] $message;
  }
  public function getMessage()
  {
     return $this->messages;
  }
}
class MessageService
{
  public function addUserMessage(User $user, Message $message)
  {
     // Ensure unique message for user
     // One solution is loop through $user->getMessages() here and make sure unique
     // This is more or less the only path to adding a message, so ensure its integrity here before proceeding 
     // There could also be ACL checks placed here as well
     // You could also create functions that provide checks to determine whether certain criteria are met/unmet before proceeding
     if ($this->doesUserHaveMessage($user,$message)) {
       throw Exception...
     }
     $user->addMessage($message);
  }
  // Note, this may not be the correct place for this function to "live"
  public function doesUserHaveMessage(User $user, Message $message)
  {
     // Do a database lookup here
     return ($user->hasMessage($message) ? true
  }
}
class MessageRepository
{
  public function find(/* criteria */)
  {
     // Use caching here
     return $message;
  }
}

class MessageFactory
{
   public function createMessage($data)
   {
     //
     $message = new Message();
     // setters
     return $message;
   }
}

// Application code
$user = $userRepository->find(/* lookup criteria */);
$message = $messageFactory->create(/* data */);
// Could wrap in try/catch
$messageService->sendUserMessage($user,$message);

我也使用Doctrine2。你的领域实体对象仅仅是对象……它们不应该知道自己来自哪里,领域模型只是管理它们并将它们传递给各种管理和操作它们的函数。

回头看一下,我不确定我是否完全回答了你的问题。然而,我认为实体本身不应该有任何访问映射器的权限。创建服务/存储库/其他操作对象的函数,并在这些函数中利用适当的技术……

也不要从一开始就过度设计。保持你的领域专注于其目标,并在性能真正成为问题时进行重构。


是的,我同意设计模式的观点,并且循环遍历所有消息是一个简单的解决方案 - 但这意味着(假设有大量的消息)在简单查询可以得到相同结果时,您正在从存储中加载所有记录。 - Tim Lytle
是的,这就是我提到可能使用doesUserHaveMessage函数(或类似的东西)的原因。这可能是查询执行的地方。它仍然使你的实体保持干净和专注,但允许你保持域一致性。此外,你可以从一个简单/琐碎的实现开始(通过数组循环),但当需要性能时可以重构……只要你的接口保持不变,其他代码就不必更改。 - jsuggs

1
在我看来,一个实体应该不知道它来自哪里,由谁创建以及如何填充其相关实体。在我使用的ORM(我的自己的)中,我能够定义两个表之间的连接,并通过指定(在C#中)来限制其结果:
SearchCriteria sc = new SearchCriteria();
sc.AddSort("Message.CREATED_DATE","DESC");
sc.MaxRows = 10;
results = Mapper.Read(sc, new User(new Message());

这将导致一个连接,限制为10个项目,按消息创建日期排序。消息项目将添加到每个用户中。如果我写:

results = Mapper.Read(sc, new  Message(new User());

连接被反转了。

因此,可以使实体完全不知道映射器的存在。


是啊,我倾向于希望实体与任何东西都解耦。你的方法大致是我所说的:“……控制器或事务脚本或使用实体的任何内容可以直接使用映射器检索用户消息集合,并进行限制...”那么如何检查Message是否已经存在呢?在实体外面完成这个操作就可以了吗? - Tim Lytle
我假设你正在从第三方服务(如你所提到的RSS订阅)中消费消息。在这种情况下,你可以搜索最后接收到的消息;如果没有找到,则添加它。继续对其他消息执行此操作,直到找到已存在的消息-这意味着你已经赶上了它们(除非我没有理解你的要求)。 - Otávio Décio
那样做是可行的,但它将发生在实体之外-是吗?虽然我想保持我的实体解耦,但似乎添加Message的代码也应该在实体中(以避免代码重复)。 - Tim Lytle
@Tim - 我的实体中绝对没有任何与业务相关的代码。在我的模型中添加新消息需要创建一个新的Message对象,将其UserId外键分配给我正在使用的用户的外键,然后分配其他信息,最后调用Mapper.Write(Message)。再次强调,这完全不属于实体范畴。 - Otávio Décio

1

不行。

原因在于信任。你不能信任数据会为系统的利益而行动。你只能信任系统会根据数据行动。这是编程逻辑的基本原则。

假设有些恶意代码混入了数据,而且它是用于 XSS 的。如果一个数据块正在执行操作或者被评估,那么 XSS 代码就会混入其中,从而打开一个安全漏洞。

让左手不知道右手在干什么!(主要是因为你不想知道)


将其推出到一个新的控制类中。在编码时最好像建造一堆工具一样思考。你永远不会把锤子固定在某人的手臂上——你会给他们机会去拿起并使用它。对于你的数据也是如此。你必须紧密地控制代码,以便根据数据执行某些有效操作,但它必须受到你的规则的限制。当你让数据控制系统时,你就给了数据损坏系统的机会。 - Geekster
我认为实体(或模型)既代表又验证数据。 - Tim Lytle
数据模型无法自我验证。这违反了哲学法则。请参见普特南的“坛中之脑”思想实验:http://en.wikipedia.org/wiki/Brain_in_a_vat。这意味着,数据将假定它是完美无瑕的,但如果它在伦理上受到损害,它将不知道它已被损害(按设计)。没有系统可以自我评估。这就像暗示一个学生可以给自己的文章打分一样荒谬。只有存在于数据范围之外的系统才能评估数据。希望这足以为您提供证明,但如果有帮助的话,我愿意继续辩论。此外,数据必须具备可移植性。 - Geekster
我再想了一下,有另外一点要补充。每当有数据在调节系统功能(例如设置文件等),你需要保留默认值以防出现问题,但如果它们有效,则将其视为已验证并进行处理。如果用户可以更改它们,则需要小心处理。这与数据不同,不能以相同的方式处理。这是选项,需要仔细权衡,因为它们会修改系统。你仍然不能使用 eval 处理它们。永远不要 eval 设置文件。但从用户那里加载系统的完整部分是危险的。不要这样做! :) - Geekster
显然,我不是在谈论使用数据来验证数据(当然也不会评估任何东西),只是包含数据的对象(数据映射器模式中的实体/模型)确保每次更改或设置数据时都是有效的。当然,这也可以在第二层中完成,我也看到了这一点的意义。 - Tim Lytle
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接