封装通用逻辑(领域驱动设计、最佳实践)
更新: 09/02/2009 - 修改了问题,提供了更好的示例,增加了赏金。
你好,
我正在使用数据库和实体(域对象)之间的数据映射器模式构建 PHP 应用程序。 我的问题是:
封装常用执行任务的最佳方法是什么?
例如,一项常见任务是从站点映射器中检索一个或多个站点实体,以及从页面映射器。 目前,我会这样做:
$siteMapper = new Site_Mapper();
$site = $siteMapper->findByid(1);
$pageMapper = new Page_Mapper();
$site->addPage($pageMapper->findHome($site->getId()));
现在这是一个相当简单的示例,但实际上它变得更加复杂,因为每个站点也有一个关联的区域设置,并且该页面实际上有多个修订(尽管出于此任务的目的,我只对最近的一个感兴趣)。
我需要在应用程序中的多个位置执行此操作(获取站点和关联的主页、区域设置等),并且我想不出封装此任务的最佳方法/位置,因此我不会必须到处重复。 理想情况下,我希望最终得到这样的结果:
$someObject = new SomeClass();
$site = $someObject->someMethod(1); // or
$sites = $someObject->someOtherMethod();
生成的站点实体已经创建了关联实体并可供使用。
将这些对象保存回来时也会出现同样的问题。 假设我有一个站点实体和关联的主页实体,并且它们都已被修改,我必须执行以下操作:
$siteMapper->save($site);
$pageMapper->save($site->getHomePage());
再次,微不足道,但此示例已简化。 重复代码仍然适用。
在我看来,拥有某种可以处理的中心对象是有意义的:
- 检索一个或多个站点和所有必要的关联实体使用
- 新的关联实体创建新的站点实体
- 获取一个或多个站点并保存它并所有关联的实体(如果它们已更改)
那么回到我的问题,这个对象应该是什么?
- 现有的映射器对象?
- 基于存储库模式的东西?*
- 基于工作单元模式的东西?*
- 还有其他东西吗?
* 正如您可能猜到的那样,我完全不理解其中任何一个。
是否有解决这个问题的标准方法,有人可以提供他们如何实现它的简短描述吗? 我并不是在寻找任何人提供完整的工作实现,只是理论。
谢谢,
杰克
Updated: 09/02/2009 - Revised question, provided better examples, added bounty.
Hi,
I'm building a PHP application using the data mapper pattern between the database and the entities (domain objects). My question is:
What is the best way to encapsulate a commonly performed task?
For example, one common task is retrieving one or more site entities from the site mapper, and their associated (home) page entities from the page mapper. At present, I would do that like this:
$siteMapper = new Site_Mapper();
$site = $siteMapper->findByid(1);
$pageMapper = new Page_Mapper();
$site->addPage($pageMapper->findHome($site->getId()));
Now that's a fairly trivial example, but it gets more complicated in reality, as each site also has an associated locale, and the page actually has multiple revisions (although for the purposes of this task I'd only be interested in the most recent one).
I'm going to need to do this (get the site and associated home page, locale etc.) in multiple places within my application, and I cant think of the best way/place to encapsulate this task, so that I don't have to repeat it all over the place. Ideally I'd like to end up with something like this:
$someObject = new SomeClass();
$site = $someObject->someMethod(1); // or
$sites = $someObject->someOtherMethod();
Where the resulting site entities already have their associated entities created and ready for use.
The same problem occurs when saving these objects back. Say I have a site entity and associated home page entity, and they've both been modified, I have to do something like this:
$siteMapper->save($site);
$pageMapper->save($site->getHomePage());
Again, trivial, but this example is simplified. Duplication of code still applies.
In my mind it makes sense to have some sort of central object that could take care of:
- Retrieving a site (or sites) and all nessessary associated entities
- Creating new site entities with new associated entities
- Taking a site (or sites) and saving it and all associated entities (if they've changed)
So back to my question, what should this object be?
- The existing mapper object?
- Something based on the repository pattern?*
- Something based on the unit of work patten?*
- Something else?
* I don't fully understand either of these, as you can probably guess.
Is there a standard way to approach this problem, and could someone provide a short description of how they'd implement it? I'm not looking for anyone to provide a fully working implementation, just the theory.
Thanks,
Jack
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
使用存储库/服务模式,您的存储库类将为每个实体提供一个简单的 CRUD 接口,然后服务类将是一个附加层,用于执行附加逻辑(例如附加实体依赖项)。 然后,您的应用程序的其余部分仅使用服务。 您的示例可能如下所示:
然后在 SiteService 类中,您将看到如下内容:
我不太了解 PHP,所以如果语法上有问题,请原谅。
Using the repository/service pattern, your Repository classes would provide a simple CRUD interface for each of your entities, then the Service classes would be an additional layer that performs additional logic like attaching entity dependencies. The rest of your app then only utilizes the Services. Your example might look like this:
Then inside the SiteService class you would have something like this:
I don't know PHP that well so please excuse if there is something wrong syntactically.
[编辑:此条目试图解决这样一个事实,即编写自定义代码来直接处理情况通常比尝试将问题适应模式更容易。]
模式在以下方面很好概念,但它们并不总是“映射”。 经过多年的高端 PHP 开发,我们已经确定了一种非常直接的方式来处理此类问题。 考虑一下:
文件:Site.php
然后,为了(从任何地方)调用它,只需运行:
关于 OO 编程和 PHP 要记住的一件事... PHP 不会在请求之间保留“状态”,因此创建一个仅仅将对象实例立即销毁通常是没有意义的。
[Edit: this entry attempts to address the fact that it is oftentimes easier to write custom code to directly deal with a situation than it is to try to fit the problem into a pattern.]
Patterns are nice in concept, but they don't always "map". After years of high end PHP development, we have settled on a very direct way of handling such matters. Consider this:
File: Site.php
Then, in order to call it (from anywhere), just run:
One thing to keep in mind about OO programming and PHP... PHP does not keep "state" between requests, so creating an object instance just to have it immediately destroyed does not often make sense.
我可能会首先将常见任务提取到某个地方的辅助方法,然后等待看看设计需要什么。 感觉现在说还为时过早。
您将这个方法命名为什么? 该名称通常暗示该方法所属的位置。
I'd probably start by extracting the common task to a helper method somewhere, then waiting to see what the design calls for. It feels like it's too early to tell.
What would you name this method ? The name usually hints at where the method belongs.
让您的 Site 对象成为聚合根,以封装复杂的关联并确保一致性。
然后创建一个 SiteRepository ,负责检索站点聚合并填充其子项 (包括所有页面)。
您不需要单独的 PageRepository(假设您没有将 Page 设为单独的聚合根),并且您的 SiteRepository 也应该负责检索 Page 对象(在您的情况下通过使用现有的映射器)。
所以:
然后
findById
方法将负责查找站点的所有页面子项。 这将具有与 CodeMonkey1 给出的答案类似的结构,但是我相信通过使用聚合和存储库模式,您将受益更多,而不是为此任务创建特定的服务。 站点聚合的任何其他检索/查询/更新(包括其任何子对象)都将通过同一个 SiteRepository 完成。编辑:这里有一个简短的 DDD 指南来帮助您了解术语,不过,如果您想要了解整个情况,我真的建议您阅读Evans。
Make your Site object an Aggregate Root to encapsulate the complex association and ensure consistency.
Then create a SiteRepository that has the responsibility of retrieving the Site aggregate and populating its children (including all Pages).
You will not need a separate PageRepository (assuming that you don't make Page a separate Aggregate Root), and your SiteRepository should have the responsibility of retrieving the Page objects as well (in your case by using your existing Mappers).
So:
And then the
findById
method would be responsible for also finding all Page children of the Site. This will have a similar structure to the answer CodeMonkey1 gave, however I believe you will benefit more by using the Aggregate and Repository patterns, rather than creating a specific Service for this task. Any other retrieval/querying/updating of the Site aggregate, including any of its child objects, would be done through the same SiteRepository.Edit: Here's a short DDD Guide to help you with the terminology, although I'd really recommend reading Evans if you want the whole picture.