Scalaで最強のRepositoryパターンを実装する ~③ScalikeJDBCによる実装~

# はじめに
本記事は連載形式で執筆しています。ここまでの記事は以下の通り
[①の記事 ~howとwhatの分離~](https://qiita.com/YuitoSato/items/730add80659718c79bd6)
[②の記事 ~トランザクションモナド~](https://qiita.com/YuitoSato/items/d81b76e1757d45afae0f)

# ScalikeJDBCとは
ScalikeJDBCはScalaのO/Rマッパーのひとつです。
タイプセーフでかけたり、ドキュメントがしっかりしていたりので個人的に好きです。

[公式ドキュメントはこちら](http://scalikejdbc.org/)

# 実装方針
まずは前回のおさらいから、


```scala
trait Transaction { self =>

  def map[B](f: A => B): Transaction[B]

  def flatMap[B](f: A => Transaction[B]): Transaction[B]

  def run(implicit runner: TransactionRunner): Result[A] = {
    runner.run(self)
  }

}

trait TransactionRunner {

  def run[A](transaction: Transaction[A]): Result[A]

}

trait UserRepository {

  def create(user: User): Transaction[Unit]

}

class UserService @Inject()(
  userRepository: UserRepository
)(
  implicit runner: TransactionRunner
) {

  def create3Times(user: User): Result[Unit] = {
    (for {
      _ <- userRepository.create(user)
      _ <- userRepository.create(user)
      _ <- userRepository.create(user)
    }).run
  }

}
```

このような形になっていました。
今回ScalaikeJDBCで実装していくのはこの、
- **Transaction**
- **TransactionRunner**
- **UserRepository**
の3つになります。

# Transactionの実装
前提として今回の実装はRDBのO/R Mapperの実装です。
なので、**Transactionをrun時（=トランザクションをコミット時）にエラーを検知したらロールバックが起きる**ように実装したいです。


そのためにはまず、そのO/Rマッパーのトランザクション処理の実装を把握する必要があります。

[scalikejdbc-cookbook](https://github.com/scalikejdbc/scalikejdbc-cookbook/blob/master/ja/04_transaction.md)

知らない人は、公式ドキュメントを読んで学んでおきましょう。

ScalikeJDBCのトランザクションはrun時に同じインスタンスのDBSessionを渡していれば同一のトランザクションとみなされます。

これをどう表現していくのかというと、

```scala

case class ScalikeJDBCTransaction[A](
  execute: DBSession => DomainError \/ A
) extends Transaction[A] {

  override def map[B](f: A => B): ScalikeJDBCTransaction[B] = {
    val exec = (session: DBSession) => execute(session).map(f)

    ScalikeJDBCTransaction(exec)
  }

  override def flatMap[B](f: A => Transaction[B]): Transaction[B] = {
    val exec = (session: DBSession) => execute(session).map(f).flatMap(_.asInstanceOf[ScalikeJDBCTransaction[B]].execute(session))
    ScalikeJDBCTransaction(exec)
  }

}
```

こう表現します。

DBSessionを受け取り、エラーハンドリングした何かしらの型を返す（DBSession => DomainError \/ A）関数を内包したケースクラスとして定義します。

mapやflatMapする場合も、メソッド内で次のScalikeJDBCTransactionで内包すべき関数を作成して、その関数を包むことによって新たなTransactionを生成しています。

最後まで説明しないと、理解しがたい部分もあると思うので先に進みます。

# TransactionRunnerの実装
TransactionRunnerの実装はこうです。最終的に合成したトランザクションモナドをコミットするためのメソッドを持ちます。

```scala
class ScalikeJDBCTransactionRunner extends TransactionRunner {

  override def run[A](transaction: Transaction[A]): Result[A] = {
    SyncResult {
      DB localTx { session =>
        transaction.asInstanceOf[ScalikeJDBCTransaction[A]].execute(session)
      }
    }
  }

}
```

# Transactionコンパニオンオブジェクト
ScalikeJDBCTransactionの生成を簡単にするために、コンパニオンオブジェクトにファクトリメソッドを定義しておきます。

```scala
object ScalikeJDBCTransaction {

  def from[A](execute: DBSession => A): ScalikeJDBCTransaction[A] = {
    val exec = (session: DBSession) => {
      Try {
        execute(session)
      } match {
        case Success(r) => \/-(r)
        // ThrowableをDomainErrorに変換して、Left値に詰めてるだけ。
        case Failure(l) => -\/(DomainError.Unexpected(l))
      }
    }
    ScalikeJDBCTransaction(exec)
  }

}
```

# UserRepositoryの実装
次はUserRepositoryを実装していきます。

```scala
class UserRepositoryScalikeJDBC extends UserRepository {

  override def create(user: User): ScalikeJDBCTransaction[Unit] = {
    ScalikeJDBCTransaction.from { session: DBSession =>
      Users.create(
        // 中略
      )(session)
    }.map(_ => ())
  }

}
```

Users.create ~ のところは以下のDBSessionとユーザー情報を受け取り、ユーザーのレコードを追加するメソッドだと仮定します。
今回は詳しく解説はしません。

# 使用方法

```scala
class UserService @Inject()(
  userRepository: UserRepository
)(
  implicit runner: TransactionRunner
) {

  def create3Times(user: User): Result[Unit] = {
    (for {
      _ <- userRepository.create(user)
      _ <- userRepository.create(user)
      _ <- userRepository.create(user)
    }).run
  }

}
```

DIの設定だけすれば、変わらず上記のような形で作成でき、トランザクションも同一のものとして処理できるはずです。


今回は解説のため、DIやimplicitを使わない場合も置いておきます。

```scala
class UserServiceScalikeJDBC {
  
  val userRepository = new UserRepositoryScalikeJDBC
  val transactionRunner = new ScalikeJDBCTransactionRunner

  def create3Times(user: User): SyncResult[Unit] = {
    (for {
      _ <- userRepository.create(user)
      _ <- userRepository.create(user)
      _ <- userRepository.create(user)
    }).run(transactionRunner)
  }

}

```

いかかでしたでしょうか？

次回からはこのまま**Slick**や**MemchachedクライアントのShade**などの実装の紹介に移りたいとこですが、我慢して
本連載の目的の一つ**テストコード**の書き方について説明していきます。

[④の記事 ~テストコード編~](https://qiita.com/YuitoSato/items/fe73430befe2505813be)

では！