Repositoryの実装について。
レイヤードアーキテクチャとかClean Architectureとかアーキテクチャと言っても色々とあるが、大体のアーキテクチャであっても最近はRepository層、クラスというのは作られる事が多いと思っている。
役割としてはデータの永続化だったりの技術的詳細を担うってのが一般的な説明だが、主にinterfaceとかが絡むあたりで、実装を行うにあたっては観点が色々あるなーと思った。
採用してるアーキテクチャが何であっても、Repositoryは永続化を隠蔽するためのデザインパターンなので、どこかのアーキテクチャ限定で適応される話でもないはず。
- レイヤー化
- 名前の通り技術的詳細を担う層としてディレクトリを切って、責務を明確にする
- レイヤードアーキテクチャの説明ではここの観点がメインで出てくるイメージがある
- プロトコルの抽象化
- よくある説明の例としては、MySqlRepositoryではなく、UserRepositoryとしてInterfaceを噛ませた上で実装し、具体的なDBは利用元からは意識しなくて済むようにする
- DBが変わった時に差し替えが容易になる(らしい)
- 依存関係逆転
- DDDだとDomain、Clean ArchitectureだとApplication、UseCase層かDomainでinterfaceを切る実装
- どちらにしてもよりシステムの本質的な層でinterfaceを切ることでアーキテクチャ内の依存の方向性を整理する
- DDDだと名前の通りドメインドリブンなわけだが、そのままRepositoryクラスをドメイン層で利用すると、外部の技術的な詳細にドメイン層が依存してしまう
- なのでドメイン層でinterfaceを定義して、Repositoryではあくまでそのinterfaceの具体クラスを提供することとする
個人的にはプロトコルの抽象化だけはあまり重要視しなくて良いかなと思っている。
まずはシンプルにDBの変更を見据えた上で抽象化を当初から行うのはかなりハードルが高い。
MySqlからCassandraにDBを変えても同じI/Fを維持できるように最初から作れる気がしない。
それにDBの寿命は大抵アプリケーションより長いので単純にこれを行っておいて生きる場面がいつになるのか想像がつかない。
近しい経験として、10年以上前に開発されたアプリケーションのDB刷新をした事がある。
コードを読んでみてDB周りを抽象化しようとしてる様子は確認できたが、流石にRepositoryの実装だけ差し替えればOKなんて事にはならなかったし、大抵そんなものだと思う。
APIに関しても同様で、上手く抽象化できればそれで良いんだろうが、そこまで頑張ってもコストに見合うのかがわからない。
シンプルにレイヤーだけ分けておけばそれで十分な事がほとんどではないかなと。
DDDやClean Architectureをやろうとすると、依存関係逆転の観点でinterfaceを切ったりするからそれを行うであれば同じと言ってしまえば同じではってのはある。
がプロトコルの抽象化まで意識させるとI/F考えるのに手間取る人は多いと思うし、この観点は無理に意識しない方がコスパを考えるとベターなのかなと思っている。
なので自分はあくまでも依存関係逆転のためのinterfaceだと割り切り、プロトコルの抽象化はそこまで意識しないで実装することが多い。
依存関係逆転に関しては、単純に自分はオニオンやクリーンアーキテクチャをベースにしたアーキテクチャにする事が多いのでドメインやユースケースでインフラストラクチャ層などに対してInterfaceを切る事が多い。
アプリケーションの本質的な部分で、外部のAPIやライブラリへの依存をなくすために、利用元でInterfaceを切るってのはチーム間で認識も合わせやすいのと、プロトコルの抽象化をそこまで重要視しなければ実装で詰まる事もほぼないし、何より依存関係の一方向化、アプリケーションの本質的な部分とは異なる技術的な詳細にドメインなどで依存させないようにするってのをチーム全体に意識してもらうにはそこはもうルールにしてしまいたいかなと。
この辺りで言うと、最近はArchunitが気に入っていて、これを使ってドメインやユースケースでインフラストラクチャ層に対してInterfaceを切る事を強制するルールを入れたりしている。コツコツと良さげなルールをどう書くか色々試してるので、これはこれでどこかのタイミングでまとめたい。
後レイヤー分割はほぼマストで行ってる。先ほど述べたようにオニオンとクリーンとかを意識すると、必然的にレイヤーが分離される。
まぁRepositoryパターンの是非は色々と語られていると思うが、とはいえRepository層を作る事に慣れている開発者が少なくとも自分の周りにはほとんどだし、余程規模が小さいプロダクト以外ではレイヤー分割やRepositoryパターンはとりあえず利用しておくかなといった感じ。
E2EやIntegration testでほぼ全ての処理を担保してたシステムの開発に関わって以来、test pyramidへの執着がだいぶ強くなったので、アプリケーション層、ユースケースクラスのユニットテストでフローを担保するためにも嬉しい気がするが、これはテスタブルになっていればそれで良いし、規模によってはIntegrationやE2Eで全て担保するってのでもまぁ良いかなとは思うが、今の所そのような形で開発した事がないのであまりイメージがついていない。
参考
多分この記事を読むより、以下のスライドを見る方が遥かに得るものが多い
Repositoryによる抽象化の理想と現実