【问题标题】:The repository pattern with DI带有 DI 的存储库模式
【发布时间】:2016-03-18 20:33:54
【问题描述】:

到目前为止,我将存储库视为“食谱书”,我会将模型中的几乎所有查询都放在其中。

例如:

Model -> Post
Repo -> PostRepository with Post injected via DI
Method -> find() uses $this->post->find

到目前为止,这对于减少重复和单元测试很有帮助。

但后来我遇到了一个需要在另一个存储库中的存储库的情况。 然后我需要另一个存储库中的第一个存储库。 我将这两个注入到彼此的构造函数中。

我的测试会失败并且会抛出一个限制耗尽错误。无限注入是个问题。

经过一番思考,我想出了 3 个潜在的解决方案:

1) 仅在方法中注入 Repos。这将解决我的实际问题,但我必须格外小心,我永远不会注入相同的 repo 并返回案例 1。我讨厌这个选项。

2) 存储库应该是关于概念的,而不是关于模型的。如果我需要另一个存储库,也许它们共享相同的概念,应该将其提取到另一个存储库。 这不会永远解决问题,因为我觉得如果应用程序增长,我必须在某个时候将其中一个存储库注入另一个存储库。 我对这个选项有点“好”。

3) 我需要另一种类型/级别的课程。仅在模型的最低级别上查询的一种。这些将被任何存储库以任何需要的方式使用,这绝不会导致任何问题。这也有助于不犯由问题 1) 引起的错误。我最喜欢这个选项。

我觉得最好的解决方案可能是上述所有内容的混合,但我现在有点迷失了。 这显然都是在 Laravel 环境中,尽管我觉得这比框架甚至语言级别更抽象。

谢谢。

编辑:

正如评论中所问的,这是一个简单的代码示例,我需要在另一个 repo 中进行查询:

class PostRepository {
    private $post;
    private $commentRepository;

    public function __construct(Post $post, CommentRepository $commentRepository) {
        $this->post = $post;
        $this->commentRepository = $commentRepository;
    }

    public function hasComments($post) {
        return $this->commentRepository->countCommentsForPost($post->id) > 0;
    }

    public function exists($post) {
        // ...
    }
}

class CommentRepository {
    private $comment;
    private $postRepository;

    public function __construct(Comment $comment, PostRepository $postRepository) {
        $this->comment = $comment;
        $this->postRepository = $postRepository;
    }

    public function create($content, $post) {
        if ($this->postRepository->exists($post)) {
            return $this->comment->create(['content' => $content, 'post_id' => $post->id]);
        }
    }

    public function countCommentsForPost($post) {
        // ...
    }
}

这是一个非常简单(而且不太聪明)的示例,您可能会争辩说 Post 模型应该具有“cmets”关系,但我仍然觉得这种逻辑在这里是错误的,我不认为就像把它放在控制器中一样。该代码只会获得带有“自动”DI 的无限循环。

【问题讨论】:

  • 您能否添加一个(简化的)示例,说明何时需要访问另一个存储库?在 Symfony 术语(它使用存储库来存储数据库)中,您所描述的内容听起来像是一项服务
  • 为什么要将commentRepository 注入PostRepository。您可以将 Comment 模型(甚至违反单一职责原则)注入 PostRepository。您可以创建 PostCommentRepository,而不是我认为这将是理想的..
  • 我想过这样做,但我只是在这里创建一个评论。逻辑(如果)涉及 postRepository 但我觉得这里涉及的主要概念是 Comment 模型。这是否也意味着我需要一个新的存储库时我需要另一个存储库?我同意这违反了 SRP。即使是这里的简单 if 语句也违反了它。

标签: php laravel dependency-injection repository-pattern


【解决方案1】:

据我所知,存储库通常在任何地方都使用,但其他存储库除外。或者好吧,我从来没有真正想过一个你需要这样的东西的例子。

您所描述的可能是一个服务类,它可能有一些通过构造函数注入的存储库。

哦,我读到的关于存储库的最后一件事给了我一个有趣的想法,即只读存储库只查询数据库以获取结果,但它们不保存/创建/更新/删除内容。由于复杂性,这些方法保留在模型中。

这是我读过的关于此的帖子: http://adamwathan.me/2015/02/14/active-repository-is-an-antipattern/

哦,单元测试存储库是指集成测试吗?因为为已经测试过的东西编写单元测试可能不是最好的主意。 (谈论雄辩)

更新: 我刚刚看到您的更新,您似乎需要 Comment 模型,而不是整个存储库。并且存储库应该尽可能短,通常是一个没有任何 if 的 return 语句,但我从来没有真正遇到过这样的情况,所以我不确定检查东西是否是最好的主意。

也许在其他地方做 if 检查,如果通过就运行查询?

此外,对于某些查询,使用关系可能会帮助您更多。

【讨论】:

  • 感谢您的回答。我说的是复杂查询的单元测试,我实际上是从编写单元测试开始并让它们通过。我希望能够模拟我将传入的模型来描述应该调用/完成的流程以及结果应该是什么。
  • 好吧,有些人说模拟东西有点矫枉过正,但我​​从来没有真正这样做过,只有存储库集成测试可以为您提供查询结果。
  • 如果我在其他地方进行 if 检查,那将在我的控制器中,我认为那会很混乱。这就是为什么我想到了选项(3),我需要使用那些低级存储库的另一个级别,但我不知道我是否正确。
  • 为什么不是服务类?这通常是存储库和控制器之间的一个类,它执行多种操作,例如将内容保存到数据库、将内容记录到数据库、向用户发送邮件、更新某种统计信息等。无论场景是什么,你可能有不仅仅是将项目保存到数据库。但是,是的,这真的取决于项目。
【解决方案2】:

简单的答案是,尽管您可以将存储库类 DI 导入到其他存储库类中,但您不能像您在那里编码的那样使用循环 DI。为您解析 DI 的 laravel 容器类将调用每个获取 DI 的类的构造函数。所以你需要做的是整理你的依赖层次结构,例如将一些常见的功能向下移动一个依赖层。

【讨论】:

  • 是的,我完全同意这一点。这就是我在选项(3)中的想法。但是你怎么称呼这个使用这两个存储库的更高层呢?
  • 这确实是您的应用程序的设计问题。您当然可以依赖注入任何更高层,这只是您如何设计它的问题。因此,例如,您可能想要创建一个名为 BlogRepository 的存储库,该存储库将 PostRepository 和 CommentRepository 都拉入(将这两个较低的类都放入 BlogRepository)。然后只需将您的 BlogRepository DI 到您的控制器中。您的控制器需要的功能应该由 BlogRepository 覆盖,如有必要,它应该能够将任务向下委派给 Post 或 Comment 存储库。
猜你喜欢
  • 2016-04-26
  • 1970-01-01
  • 1970-01-01
  • 2012-01-27
  • 1970-01-01
  • 2021-11-02
  • 2020-11-03
  • 2023-03-21
  • 2020-03-05
相关资源
最近更新 更多