📝

Repositoryパターンのメモ

に公開

背景

前回Data Mapperパターンについて学んだ。
https://zenn.dev/yousaku/articles/fa04f60349e243

Data Mapperパターンのためにデータの取得方法を考えた際、「Repositoryパターンについて改めて学びたいな」と感じたのでメモを残す。

事前知識

この記事を読むにあたって知っておくと良いものを列挙しておきます。

  • Active Recordパターン
  • Data Mapperパターン
  • Unit of Work(記事内で軽く補足します)
  • ドメイン駆動設計でいう「集約」

Repositoryパターン

毎度のごとくMartin Fowler氏の記事です。
https://martinfowler.com/eaaCatalog/repository.html

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection.

ドメインやデータマッピングの仲介を行うもの。

Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer.

リポジトリはデータ永続などの操作をカプセル化する。永続化層をよりオブジェクト指向らしく提供できる

個人的Repository パターンの気になるところ

責務の分離ができる

Data Mapperパターン同様で特定の処理を行うだけのクラスに分割できます。Repositoryパターンであればデータストアへの操作処理だけを持つクラスになれます。
後述しますが、設計を工夫すれば利用するデータストアを特定技術に縛らなくても良くなります

選択肢の幅が増える(実装の柔軟性)

interfaceに依存させることでデータストア先を気にする必要はなくなります。
サンプルコードを書いてみます。Userというドメインオブジェクトを操作するRepositoryを作成します。データストアはRDB, NoSQL, InMemoryです。

UserRepositoryInterface.php
<?php

namespace App\Domain\User;

// Interfaceを用意
interface UserRepositoryInterface
{
    public function findById(UserId $userId): User
    public function insert(User $user): void
    public function update(User $user): void
    public function delete(UserId $userId): void
}

Interfaceを使ってデータストア別にRepositoryを実装する。

RdbUserRepository.php
<?php

namespace App\Repository\User;

use App\Domain\User\User;
use App\Domain\User\UserId;
use App\Domain\User\UserRepositoryInterface;

class RdbUserRepository implements UserRepositoryInterface
{
    public function findById(UserId $userId): User
    {
        // RDBでの処理
    }

    public function insert(User $user): void
    {
        // RDBでの処理
    }

    public function update(User $user): void
    {
        // RDBでの処理
    }

    public function delete(UserId $userId): void
    {
        // RDBでの処理
    }
}
NoSqlUserRepository.php
<?php

namespace App\Repository\User;

use App\Domain\User\User;
use App\Domain\User\UserId;
use App\Domain\User\UserRepositoryInterface;

class NoSqlUserRepository implements UserRepositoryInterface
{
    public function findById(UserId $userId): User
    {
        // NoSQLでの処理
    }

    public function insert(User $user): void
    {
        // NoSQLでの処理
    }

    public function update(User $user): void
    {
        // NoSQLでの処理
    }

    public function delete(UserId $userId): void
    {
        // NoSQLでの処理
    }
}
InMemoryUserRepository.php
<?php

namespace App\Repository\User;

use App\Domain\User\User;
use App\Domain\User\UserId;
use App\Domain\User\UserRepositoryInterface;

class InMemoryUserRepository implements UserRepositoryInterface
{
    public function findById(UserId $userId): User
    {
        // InMemoryでの処理
    }

    public function insert(User $user): void
    {
        // InMemoryでの処理
    }

    public function update(User $user): void
    {
        // InMemoryでの処理
    }

    public function delete(UserId $userId): void
    {
        // InMemoryでの処理
    }
}

こんなふうにRepository層を設ければデータストアが変わっても新しいクラスを作ることで対応できます。namespaceを工夫することでファイル構成をすっきりさせることも可能です。
Active Recordパターンでどのデータストアとやり取りをしているか伝えるためにメソッド名の工夫やコードコメントを書くことになります。そして時間が経てば経つほどModel層が肥大します。

別にUnit of Workパターンとセットではない

Unit of Workというのがあります。別でまとめるかも知れないですが今回は下記資料から抜粋します。
https://learn.microsoft.com/ja-jp/archive/msdn-magazine/2009/june/the-unit-of-work-pattern-and-persistence-ignorance

Martin Fowler によると、Unit of Work パターンでは「ビジネス トランザクションによって影響を受けるオブジェクトのリストを保持し、変更の書き込みと同時実行の問題の解決を調整します」。

簡単にいうと「Databaseなどへデータストアへの書き込みを保持してまとめて行おう」というところです。全部貯めて一括実行なので 全て更新される or 全て更新されない の二択になります。途中で失敗した時のロールバックする処理がActive Recordパターンでのトランザクション制御と異なります。

Repositryはデータの永続化などを一つの層に切り出そうという話です。
データの保存方法をUnit of Workで行おうというものではないのでRepositoryパターンを利用するからUnit of Workも使わないとというのは違います。Unit of Workを使った方が良いというのであれば併用すれば良いです。

もう少し踏み込むために

「Repositoryパターンってこういうことか!じゃあどんどん使おう!!」となることを否定しませんがもう少し深掘りたい。いろんなケースがあって考慮すべきことは多い。

Repositoryをどこに配置するのか

https://github.com/little-hands/ddd-q-and-a/issues/151

本当にRepositoryだけにロジックを集めて良いのか

ReadとWriteを一緒の場所に書くべきか、それともreadとwrite、それぞれの責務を持つもの別々に作成するべきか。

アンチパターン集

下記記事が非常に勉強になります。
Repositoryパターンを学び、とりあえずの気持ちで実践投入してしまわないよう目を通しておくと良さそうです。定期的に見返したい内容になってます。
https://qiita.com/mikesorae/items/ff8192fb9cf106262dbf
https://www.docswell.com/s/katzumi/ZJL8GX-clean-architecture-anti-pattern

調べてみて

別のことについても深掘る必要がありそう

Active Recordパターン、Data Mapperパターンの時同様で追加で調べたほうが良いことがたくさんある。

  • Unit of Work
  • 集約
  • CQS

おわり

次こそ実装するぞ!Data MapperパターンとRepositoryパターンやっていくぞ!

参考文献

Data Mapperについてまとめた自分の過去メモ
https://zenn.dev/yousaku/articles/fa04f60349e243

Repository
https://martinfowler.com/eaaCatalog/repository.html

インフラストラクチャの永続レイヤーの設計
https://learn.microsoft.com/ja-jp/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design

Unit of Work
https://learn.microsoft.com/ja-jp/archive/msdn-magazine/2009/june/the-unit-of-work-pattern-and-persistence-ignorance

little-hands/ddd-q-and-a/issues/151
https://github.com/little-hands/ddd-q-and-a/issues/151

Repositoryパターンのアンチパターン
https://qiita.com/mikesorae/items/ff8192fb9cf106262dbf

クリーアーキテクチャでのアンチパターン
https://www.docswell.com/s/katzumi/ZJL8GX-clean-architecture-anti-pattern

それ... リポジトリじゃなくね?~実案件から学ぶアンチパターン~/Learn repository pattern from real projects
https://speakerdeck.com/hayabusabusa/learn-repository-pattern-from-real-projects

Discussion