はじめに
ScalaのWebアプリケーションを作る上で最強のRepositoryパターンを考察していきます。
間違えていることはぜひご指摘いただけると幸いです。
本記事で触れるような実装はだいたい以下のリポジトリで公開しています。
https://github.com/YuitoSato/user-manager
また、①があると②もあるということで連載形式で記事を公開していきます
2018/4/29現在の連載記事一覧です↓
①の記事 ~howとwhatの分離~
②の記事 ~トランザクションモナド~
③の記事 ~ScalikeJDBCによる実装~
④の記事 ~テストコード~
対象者
- Scalaがだいたい書ける
- ScalaでWebアプリ作れる
- モナ○が少しわかる
- Repositoryパターンに興味がある
Repositoryパターンとは(復習)
Repositoryパターンはビジネスロジックとデータアクセスロジックを分離するためのパターンを指します。
詳しくはこちら
ビジネスロジックとデータアクセスロジックを分離することで、ビジネスロジックのテストが簡単になったり、可読性が向上します。
環境
- Scala 2.12
- Google Guice 4.1.0
- ScalikeJDBC 3.2.1
- Scalatest 3.0.1
howとwhatの分離
では最強とはなんなのか?
筆者が考える最強とはhowとwhatが分離されているRepositoryです。
どう実現するかよりも何をするかのほうが大事です。
どうやってユーザーの情報を取得するかよりも、どのユーザーの情報を取得するかのほうが圧倒的に大事です。
そのため、どう実現するかは隠蔽します。
具体的なデータの取得方法や更新方法は、アプリケーション層や、DDDでいうところのドメイン層から見えないようにするべきなのです。
アプリケーション層やドメイン層には何をしているのかのwhatの部分だけ見せ、純粋なビジネスロジックに集中できるようにします。
具体的にどうするのかというとインターフェースを作成して、内部の具体的なアクセスロジックは隠蔽します。
実装はDIによって管理します。
trait UserRepository {
// howは隠蔽し、whatだけ公開する
def find(id: Long): Option[User]
}
なぜhowを隠蔽するのか
上の例にある通り、ここでいう隠蔽とはtraitによるインターフェースを用意し実装クラスをDIすることで、アプリケーション層やドメイン層からhowを見えなくするということを指します。
DIを行いhowを隠蔽する利点は、
①テストをしやすくする
②howを差し替え可能にする
一つ目はテストがしやすくなるという利点です。というかこれが主だと思います。
howを隠蔽しないと基本的にテストがしにくくなります。
SlickのDBIOのモックを作るのは骨が折れますし、だれもFutureのインスタンスをawaitしたくないです。
インターフェースであれば、モックを注入することは容易になり、アプリケーション層やドメイン層のテスタビリティが向上します。
二つ目はhowが差し替え可能になるという利点です。OR Mapperを差し替えることはあまりないかもですが、疎結合になることで差し替えコストが向上します。
今までMySqlだったけど、NoSqlで一部差し替えたいというケースでも対応できます。
Repositoryパターンにおけるhow
Repositoryパターンにおけるhowは3つあります。
それは ①どのデータベースから、②どのツールを使って、③どうスレッドを管理するかです。
一つ目はデータベースです。
データベースとは「検索や蓄積が容易にできるよう整理された情報の集まり」を指します。
RDBだけではないです。MySQL、NoSQL、Memcachedなどなどいろいろなものを指します。
二つ目はツールです。
ここでは特にO/Rマッパーを指します。
MySQLならScalikejdbc、Hibernateなど、Memcachedならshadeなどです。
三つ目はスレッド管理です。
ここではノンブロッキングかブロッキングかを指します。
これはORマッパーによるところが多いですが、こちらのスレッド管理も隠蔽していきます。
howが隠蔽できていないパターン
class UserRepository {
// SlickのDBIOを返しているのでSlickというORマッパーが隠蔽できていない
// Slickを使用しているので MySQLやノンブロッキングというhowが隠蔽できていない
def find(id: Long): DBIO[Option[User]] = {
中略
}
// ScalikejdbcのDBSessionが隠蔽できていない
def find(id: Long)(dbSession: DBSession): Option[User] = {
中略
}
}
最終形態
先に本記事の最終形態をお見せします。
ここまでの話からhowが隠蔽できているRepositoryなら良いということになりますね。
trait UserRepository {
def find(id: Long): Option[User]
}
上に挙げた例の通り本当はこうしたいですね。
しかしOR Mapperによっては、返す値が変わったり、引数に特別なものを取らないといけないケースが出てきます。
上の例のようなインターフェースを公開するだけだと難しそうです。
そのため
trait Transaction[A]
trait UserRepository {
def find(id: Long): Transaction[Option[User]]
}
返り値にも抽象化した型であるTransactionクラスを用意します。
これで自由度が上がりました。
SlickやShadeを使う際はこのUserRepositoryとTransactionクラスを実装していけばできそうですね。
次の②の記事では、もう少しこのTransactionクラスの性質を掘り下げていきます。
では!