【问题标题】:laravel place abstractions in model instead of repositorylaravel 将抽象放在模型而不是存储库中
【发布时间】:2017-10-20 21:10:31
【问题描述】:

我只是想弄清楚是否要使用存储库模式。 我能找到的三个优点:

1. Testability (repositories can be injected into the controller)
2. Abstraction (complex eloquent queries can be refactored into repository functions)
3. Decoupling (enables me to replace the persistence layer)

但是,除了第 3 点之外,我并没有真正清楚地看到它。

1. I can just as well inject the model (or a mock for testing)
2. I can also abstract the queries into functions and place them into the model instead of a repository.

因此,假设我不关心被 Eloquent 绑定,我并没有真正看到使用存储库的意义。我感谢任何改变我想法的论点。

【问题讨论】:

  • 如果您使用 Eloquent,我建议您将所有与数据相关的逻辑放入模型中。如果您这样做,您仍然遵循存储库模式,并且使用存储库包有太多缺点,并且不会给您带来任何真正重要的优势。如果您使用的是查询生成器或原始 SQL 查询,请使用存储库包。
  • Repository Pattern 将帮助您替换整个 ORM 并使用其他一些 OM,例如 Doctrine。如果您通过注入 Eloquent 模型类来创建存储库,那么在将来用其他一些 ORM 替换 eloquent ORM 方面将具有一定的灵活性。但是如果你只是想把 Eloquent 分解成一个额外的抽象,那么它就毫无价值,而只是一个抽象。

标签: laravel eloquent repository-pattern


【解决方案1】:

根据我的观点和经验,您可以使用 Eloquent 作为您的存储库层。

对于第 2 点,您可以创建范围方法以便能够与普通 Eloquent 查询一起使用

$users = User::whereHasACarOfColor('yellow')->where('active, true)->get();
// ...
public function scopeWhereHasACarOfColor($query, $color) 
{
    return $this->whereHas(function ($cars) use ($color) {

        return $cars->where('color', $color);

    });
} 

在过去的几个月里,我在 Laravel 中对存储库层有不同的体验:

  1. 因为想重用代码,所以不得不创建很多类似的方法,getUsersWithYellowCars()getUsersWithYellowCarsOrderedByName()...
  2. 如果你决定不创建这么多具体的方法,你最终会得到一堆带有很多参数的方法

    public function getUsersWithCars($color, $orderBy, $orderDescOrAsc, $paginate...)
    
  3. 有时您需要将模型传递到存储库以与它们进行交互,而不是直接与之交互

    $students = $usersRepository->getStudentsForProfessor($professor, 'name', 'ASC');
    

    而不是

    $students = $professor->getStudents()->orderBy('name')->get();
    
  4. 一段时间后,代码看起来不像预期的那么干净(请查看 3 中的示例。)

另外,想说 Eloquent 已经是一个 ORM,如果您决定从 MySQL 更改为 SQLite,您无需更改任何代码。

【讨论】:

    【解决方案2】:

    让我们一一分解您的福利清单:

    1. 可测试性(存储库可以注入控制器)

    这是真的。稍后在您的问题中,您说您可以轻松地将模型注入您的控制器,那有什么意义呢?关键是,如果您使用接口来定义存储库方法,则可以使用在生产代码中使用的具体存储库来实际查询 eloquent 模型,同时使用实现相同接口的“假”存储库进行测试。我经常这样做,我的“假”实现只使用一个集合来保存我正在测试的模型。这样,您的测试运行得更快,您不必担心控制器测试中的迁移。它还使您不必在单元测试中与数据库进行交互。 我并不是说对数据库进行测试本身就不好,但您应该尽可能避免使用它。使用存储库可以帮助您在不需要与数据库交互的情况下加快测试速度。通过在控制器中对存储库接口进行类型提示,您可以传入用于测试的虚假实现,并在服务提供者中为您的生产代码绑定真实实现。

    1. 抽象(复杂的 eloquent 查询可以重构为存储库函数)

    这取决于你。 Laravel 中存储库模式的问题在于,无论您多么努力,您的模型都与数据库相关联。没有办法解决它。您扩展应用程序并重新使用存储库的机会几乎为零,因此这不一定是坏事。无论如何,业务逻辑都不应该在您的模型存储库中,因此您的应用程序的核心(如果设计得当)仍将从框架中抽象出来。在真正的存储库模式中,您的查询是在存储库中定义的。但是由于 Laravel 的模型与数据库紧密耦合,所以你把这个逻辑放在哪里并不重要。事实上,Laravel 有一些很好的 Eloquent 特性,可能更希望将这个逻辑直接放在你的模型中。存储库在 Laravel 中真正闪耀的地方是我在您的第一点中提出的,能够在您的测试中从数据库中抽象出来。

    1. 解耦(让我能够替换持久层)

    这是存储库模式的要点,也是使第 1 点成为可能的原因。当然,传递 Eloquent 模型意味着模型在哪里,数据库也在哪里。这正是 Active Record 所必须忍受的。


    扩展@Lloope 的答案,另一个好处是在一个地方(您的模型或存储库)定义您的复杂查询,以避免在您的代码库中乱扔这样的代码:

    $professor->getStudents()->orderBy('name')->get();
    

    改为使用这样的东西:

    $students = $usersRepository->getStudentsForProfessor($professor, 'name', 'ASC');
    

    在你的应用程序周围编写 Eloquent 查询的问题在于,随着需求的变化,你必须在应用程序的任何地方更新这些查询。这可能只在你的控制器中出现一次,或者在你的应用程序的几十个不同部分中。存储库(甚至只是您的 Eloquent 模型)上的 getStudentsForProfessor 方法更容易维护。这意味着您的其他对象不需要担心getStudentsForProfessor 的作用或它的功能。他们只知道它将返回给定教授的学生集合。存储库可帮助您在一个地方组织查询逻辑。一旦你定义了这个接口,或契约,你的其他对象就可以依赖它来做它所说的事情。随着查询需求的变化,您可以在一个地方而不是整个应用程序中更新实现。合同还是一样的。这比在任何地方编写 Eloquent 查询具有更大的灵活性。

    【讨论】:

    • 我想通过示例说明的是,使用您的模型作为存储库层,您可以将方法放在它们上并直接使用它,因此在示例中它是拥有该方法的教授getStudents 之后您可以订购它们或分页或其他方式。如果你在存储库上做同样的事情,你应该创建getStudentsForProfessorgetStudentsForProfessorOrderedgetStudentsForProfessorPaginatedgetStudentsForProfessorOrderedAndPaginated,等等
    • 我明白了。那我会编辑我的答案。误会请见谅:)
    猜你喜欢
    • 2021-05-23
    • 2016-03-10
    • 2014-08-13
    • 2020-11-15
    • 1970-01-01
    • 2016-11-21
    • 2020-01-23
    • 1970-01-01
    相关资源
    最近更新 更多