始めに
前回に引き続き、今回はレポジトリ部分を作成します。
構成
- IntelliJ 2018
- Scala 2.12.5
- sbt 1.1.4
- Akka 2.5.11
- AkkaHttp 10.1.0
- Slick 3.2.3
- Scalaz 7.2.19
成果物
レポジトリ
早速、書いていきます。まずはsrc/main/resources/application.confを作成します。Slickに読ませるデータベース設定です。
application.conf
todo-slick-db {
dataSourceClass = "slick.jdbc.DriverDataSource"
connectionPool = disabled
properties = {
driver = "org.h2.Driver"
url = "jdbc:h2:mem:todo-api-todo;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;MODE=MySQL;INIT=runscript from 'src/main/resources/create-todo.sql'"
}
}
合わせて、テーブル定義。
create-todo.sql
CREATE TABLE IF NOT EXISTS todo(
id INT NOT NULL AUTO_INCREMENT
, body VARCHAR(255)
, PRIMARY KEY (id));
ルートパッケージ配下にrepositoryパッケージを追加します。テーブルは前回のHelloSlickとほとんど一緒です。
Model.scala
package todo.api.repository
import slick.jdbc.H2Profile.api._
object Model {
final case class Todo(id: Int, body: String)
class TodoTable(tag: Tag) extends Table[Todo](tag, "todo") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def body = column[String]("body")
override def * = (id, body) <> (Todo.tupled, Todo.unapply)
}
}
続いてレポジトリ自体を定義しますが、簡易のDIを構成するのでインターフェースと実装は分離します。
また、レポジトリの各メソッドは単純なFutureを返さず、FutureにScalazのEitherを包んで返すことにします。
まずはインターフェースから。
TodoRepository.scala
package todo.api.repository
import scala.concurrent.Future
import scalaz.\/
trait TodoRepository {
import Model._
def findAll(): Future[\/[Throwable, Seq[Todo]]]
def findById(id: Int): Future[\/[Throwable, Option[Todo]]]
def create(data: Todo): Future[\/[Throwable, Int]]
def update(data: Todo): Future[\/[Throwable, Int]]
def delete(id: Int): Future[\/[Throwable, Int]]
}
続けて実装です。今回はslickのfor構文も使ってみます。
TodoRepositoryImpl.scala
package todo.api.repository
import scala.concurrent.Future
import scala.util.control.NonFatal
import scalaz.\/
import scalaz.syntax.ToEitherOps
import slick.dbio.DBIOAction
import slick.jdbc.H2Profile.api._
// Databaseインスタンスをコンストラクタインジェクション
class TodoRepositoryImpl(db: Database) extends TodoRepository with ToEitherOps {
import Model._
lazy val todos = TableQuery[TodoTable]
// Future[R]ではなくFuture[\/[Throwable, R]]を返すようにしたラッパー
private def run[R](a: DBIOAction[R, NoStream, Nothing]): Future[\/[Throwable, R]] =
db.run(a).map(_.right[Throwable]).recover {
case NonFatal(ex) =>
ex.left[R]
}
override def findAll(): Future[\/[Throwable, Seq[Todo]]] = run(todos.result)
override def findById(id: Int): Future[\/[Throwable, Option[Todo]]] = {
val q = for {
r <- todos if r.id === id
} yield r
run(q.result.headOption)
}
override def create(data: Todo): Future[\/[Throwable, Int]] =
run(todos returning todos.map(_.id) += data)
override def update(data: Todo): Future[\/[Throwable, Int]] = {
val q = for {
r <- todos if r.id === data.id
} yield r.body
run(q.update(data.body))
}
override def delete(id: Int): Future[\/[Throwable, Int]] = {
val q = for {
r <- todos if r.id === id
} yield r
run(q.delete)
}
}
以上で、レポジトリは完了です。
終わりに
次回は今回作成したレポジトリを扱うアクターを定義します。レポジトリの戻り値をScalazのEitherにした意味がきっと出てくるはず・・・。