Repositoryパターンを維持しながらN+1問題を起こさないようにする方法論について by yamotuki

PHP Conference Japan 2021
採択
2021/10/03 14:20〜
Track3
Regular session (25 mins)
Architecture

Repositoryパターンを維持しながらN+1問題を起こさないようにする方法論について

yamotuki yamotuki yamotuki

速度は求めたい。ユーザのためである。
設計方針は崩したくない。開発者のためである。
「速度も求めつつ、既存の設計方針を守り、ユーザに価値を届ける。そんな方法が欲しい。」

こちらはそんな思いから生まれたテクニックを紹介するセッションです。

Domain Repository は Domain Entity もしくはそのEntityのリストのみを返して良いという制約があるとする。
なんらかのリストをユーザに見せる時、アプリケーション層で複数の種類のEntityを取りまとめてData Transfer Object(以下
DTO)として情報を固めて渡したいということがよくある。
愚直にやるのであればアプリケーション層で最初のEntityを取り(①)、さらに他のEntityのIDから他のEntityを取得する(②)という形であろう。
仮に、最初のEntityが1個、次に取りたいEntityが1個、そういう場合は全く問題はないのだ。

しかし、最初に取得するのがEntityのリスト(①)の場合に困ってしまうのだ。このリストを元にループを回して、次に取りたいEntityを逐次取得(②)しDTO を組み上げるとする。そうすると①のリストの長さの分だけ、②のEntityを取得するためのRepositoryを叩くことになる。

理想的にはDomain Repositoryの裏に隠れているインフラの層は隠蔽されており、知ったことではないのだ。しかし、実際のところ多くのケースで裏でDBから取得しているであろう。いわゆるN+1問題だ。

以下のような方法論で問題は緩和されたり、避けられるかもしれない。

  • キャッシュをする
    • ②の Repository の get() などにキャッシュを差し込む。N回キャッシュ問い合わせがあることは変わらない
  • 一覧にページネーションを追加する
    • Nを減らすアイディア。問題の緩和にはなるが、ビジネス要件によっては難しい。
  • Command Query Separation を使う
    • Query で Repository を通さないで DTO を直接取得してしまう方法。一見根本解決ように見えるが、すでに Repository で get 系を書いていると取得処理が重複管理になり、管理不能になりがち

これらの方法には一長一短ある。
私は、Repositoryを使った設計パターンは崩したくない。けど速度は欲しい。そう考えた。

第4の選択肢としてHash Map Attachment法を提唱する。名前は独自命名だ。
Repository から取得したEntityから Query Service で DTO に変換する時にN+1問題とO(N^2)に陥らないようにするテクニックである。

Discord Channel: #track3-7-a-repository-pattern