以前、2つの記事を書き、「よし、これを活かして実案件で設計するぞ」と意気揚々にやってみたのですが、実際に社内のバックエンドエンジニアにしっかりレビューをもらって学んだことがあったのでそのまとめになります。
TL;DR
- 過度な抽象化は生産性を下げるリスクがある
- Laravelを使用するならActiveRecordパターンを最大限に活用した方が効率はいい
順に説明します。
過度な抽象化を避ける
↑これが私が今回学んだことであり、この記事で一番伝えたいことです。
まず、今回下記の記事を参考にしました。
実際に自分が考えていた「LaravelにDDDの思想をがっつり導入する」は中々のアンチパターンだと気づきました。(もちろん要件や仕様、規模に応じて変わると思うのでケースバイケースだとは思いますが)
今回のプロジェクトでは、PoCであり、実際のAPIもせいぜい5,6本と小規模開発でした。フレームワークはLaravelを使用し、開発期間が2週間程度でとにかくスケジュールがタイトでした。
そんな中、DDD思想のアーキテクチャを考えて、実際にレビューをもらった時に一番に言われたことは
「Repository層いらなくない?」でした。
Repositoryパターンについて
そもそもRepositoryパターンが目指したい世界は永続化層の抽象化です。
もっと具体的にいうと、データの保存先がファイルなのか、RDBなのかNoSQLなのか、はたまた外部APIをコールするのか、そんなことはドメイン層(ビジネスロジックを含む)は知るよしもないのです。
ドメイン層からしてみれば、
「どこにデータを取得・保存するかは知らないけど、このメソッドを呼ぶから決まったレスポンスでこのデータを返してね」ということだけを知っていればよく、永続化層の話はドメイン層の責務ではないのです。なので、永続化層のモックを用意して単体テストもできるということですね。
ActiveRecordパターンについて
しかし、ここでLaravelを使用する場合、必ずしもこのRepositoryパターンを採用することがいいことだとは限りません。
Laravel(他にはRails)には、ActiveRecordパターンというものが組み込まれています。ActiveRecordパターンとは
データベースレコードとオブジェクトを1対1で対応させ、データベース操作(CRUD操作)をオブジェクト指向的に扱えるようにするためのデザインパターンです。
LaravelではEloquent ORMがこれに該当します。
Laravelで実装する時に実際にデータベースから値を取得してくるときにUser::find(1);
みたいに書きますよね?これです。
テーブルに対応するModelを作成して、リレーションを定義すると、ORMで簡単にDB操作ができるよってことです。
このActiveRecordとRepositoryを踏まえて、本題の「Repository層いらなくない?」に戻ります。
Laravelの良さを削ってまで、「永続化層の抽象化」にこだわる必要があるのか?
いよいよ本題です。結論は、見出しにもあるように
過度な抽象化を避ける = Repository層はいらない
になったのですが、その経緯と理由を説明します。
まず、Repository層を作るアーキテクチャの場合、ORMによるデータアクセスをするとしても、Repositoryクラスが返却するレスポンスをEloquent Modelに依存しないようにするためにEntityクラス(もしくはOutputクラス)のようなものを作成する必要になります。ModelをそのままRepositoryで返してしまうと、Repository層が、
「データストアはリレーショナルデータベースでEloquent Modelが返ってくる」
という依存関係が発生してしまうからです。永続化層がファイルサーバーに変わった時にはModel返却できないのでこれはクリーンアーキテクチャやDDDにおけるRepository層の役割を果たしていません。なので常にEntityクラスに変換してやりとりをする必要が出てきます。
もう一つ、データベースって本当に変わることあるのかです。これはこちらの記事にもあったのですが、
「アプリケーションのコードはそのままで RDBMS を入れ替えたい」なんてことはほとんど発生しないそうです。
これはレビューをもらった人、社内のベテランエンジニアにも聞いたのですが、経験的にもほとんどないらしいです。
結局、そこを抽象化するよりもActiveRecordパターンで簡単に実装できるんだから、そのLaravelの強力な武器を使った方が生産性良いよねってことになりました。
テストどうするのさ。
Repository層を分けずに、ドメイン層でデータアクセスをする場合にUnitテストできなくない?って思った方もいるかもしれません。そもそも抽象化のメリットにこの「テストの容易さ」も含まれますよね。今回は
Unitテストは無しで機能(Feature)テストをする
ことで解決しました。ただ、あくまでドメイン層の話なので、他のUnitテストは問題なくできると思います。
まとめ
結局は、その時の要件次第なのですが、伝えたいことは「必ずしもDDDやクリーンアーキテクチャを導入することが正しい」訳ではないということです。確かに書籍や記事には"これがベストプラクティスだ"みたいに書かれていますが、実際のアプリケーションに組み込んだ時に、
- それがオーバースペックになっていないか
- 抽象化しすぎて生産性が落ちていないか
ぐらいはその都度考える必要があると思います。今回は自分が学んだことをとりあえずプロダクトに適応してみて、「これが世の中のベストプラクティスだから合っている」と思ってしまったのが間違いでした。本質を理解して、実際にどこまで・どの部分を採用するかを考えることが開発生産性をあげる上で大切なことだと気づきました。
これから設計をする方、勉強している方の参考になれば幸いです。
参考