狠狠撸

狠狠撸Share a Scribd company logo
Play2 Scalaを2年やっ
て学んだこと
D3
創業以来、高い技術力と戦略的なUI/UXを武器に、世の中に価値あるサービスを
生み出しているビズリーチ。
サービスの数が増えるにつれ、技術の幅が広がったため、そのスキルやノウハウ
を社内のみならず、
世の中のエンジニアやデザイナーとも共有すべく、
私たちは「D3(ディーキューブ)※」というプロジェクトチームを立ち上げま
した。
D3では、たくさんのイベントや勉強会を開催し、
世のエンジニア?デザイナーと共に、さらなる高みを目指します。
※D3=DESIGNER & DEVELOPER DIVISION
自己紹介
? 名前:伊川弘之樹
? 職業:プログラマー
? 株式会社ビズリーチ インキュベーションカンパニー スタンバイアプリ事業部 マネージャー
? 趣味:子供と散歩、タイガースの応援
長男
2才
次男
2ヶ月
今日話すこと
? スタンバイの紹介
? 全体の設計
? Slick3
? scalaz
? scalikeJDBC
スタンバイ
? https://jp.stanby.com
? 400万件以上の求人をまとめて探せる日本最大級の
求人検索エンジン。
? 企業公式サイトや求人サイトの ありとあらゆる求
人情報が探せます。
スタンバイ?カンパニー
? https://stanby.co/
? スタンバイ?カンパニーは、誰でもかんたんに無料
で求人を作成できるサービスです。
? 作成した求人はスタンバイに掲載されます。
? また、応募者とチャットでやりとりをしたり、動画
で面接を行うこともできます。
スタンバイアプリ
? 掲載求人数は400万件以上で日本最大級。
? 1000以上の求人?転職サイトや企業サイトを横断検索
? 正社員からアルバイト?パートまでのあらゆる働き方やこだわり条件で仕事を探す
ことができます。
? 自分のプロフィールを登録しておくと、お店や企業から仕事のお誘いが届きます。
? 興味があれば、そのままお店?企業の方とチャットでやりとりをしていただき、不
明点などを気軽に聞くことができます。
? そこで働いてみたいと思ったら、そのままチャットで面接の日程調整をしたり、ス
タンバイアプリのビデオ通話機能を使えば、自宅にいながらスマホで面接もできま
す。
カンパニーアプリ
? たった3分 無料で求人掲載
? 働きたいひとにスカウトを送信
? 応募が来たらお知らせ
使っている技術(Client)
? Web
? HTML,CSS,JavaScript
? React,jQuery,Angular,TypeScript
? Android
? Java
? iOS
? Swift3
使っている技術(AWS)
? Scala(2.11.x)
? PlayFramework(2.3,2.5)
? MySQL(RDS)
? Redis,Memcache(ElastiCache)
? S3,SQS,SNS,Kinesis…
使っている技術(Firebase)
? RealTimeDatabase
? BigQuery
? ストレージ
? Remote Config
アーキテクチャ
(マイクロサービスの図)
それぞれがRDSやCache、S3などを持っています。
他にも、バッチサーバーや、
管理画面用のサーバーや、
ログ集計用のサーバーがあります
スタンバイ
? ElasticSeachから求人情報を検索する
? クライアントはブラウザ(PC,Mobile)、Android、iOS
? ブラウザは別サーバー
? ユーザーの情報を管理する
? 履歴書、検索履歴、閲覧履歴
? スカウトされるための情報
? 求人広告を配信して稼いでいます。
? Yahoo!しごと検索にもAPI提供しています
スタンバイ カンパニー
? 企業やお店が求人を作成したら、ElasticSearchに
インデックスします
? 求職者から応募があったときに応募情報を管理しま
す。
? スカウトします
クローラー
? 求人サイトや、企業HPの求人ページをクローリン
グして、ElasticSearchにインデックスします。
? よりよい検索結果をユーザーに提供できるように、
求人の内容を学習しているようです。
サーバーサイド
? 基本的にはPlay2 Scala
? ORM:Slick3 or ScalikeJDBC
? DBMigration:DBFlute or Flyway
サーバーアプリケーションの
パッケージ構成
? app
? controllers
? models
? アプリケーション内でつかうモデル(ユーザー、求人、応募、閲覧履歴、エラー)
? repositories
? 永続化
? DBや他のAPI、AWSのサービスとつなぐ役割
? services
? コントローラーとレポジトリをつなぐ
? DBトランザクションの管理(リポジトリをまたいだトランザクションの為と、repositoryのテストがし易いため)
? utils
? views
? twirl用のhtmlや、レスポンスがjsonの場合はcase classやwritesを置く
? 下の層が上の層に依存しないように。
? repository層が受け取るのはできるだけプリミテ
ィブな型
? 上の層は下の層を知らなくてもいいように。
? repository層が返すのはエンティティ
controllers
? 機能毎にパッケージを分ける
? リクエストのバリデーション
? メイン処理の呼び出し
? レスポンスの組み立て
controllers/
players/
PlayerController
param/
Request
view/
Response
jobs/
applications/
DBFlute
? http://dbflute.seasar.org/
? EclipseでER図を作って、DDLや、Alter文を生成し
、DBをマイグレーションする
? 手顺が难しく、覚えられないのでやめた。
Flyway
? project/plugins.sbt
? build.sbt
resolvers += "Flyway" at "https://flywaydb.org/repo"
addSbtPlugin("org.flywaydb" % "flyway-sbt" % "4.2.0")
flywayUrl := "jdbc:mysql://localhost:3306/tigers"
flywayUser := "root"
Flyway
? src/main/resources/db/migration/V1__Create_players_table.sql
? $ sbt flywayMigrate
CREATE TABLE players(
id INT NOT NULL PRIMARY KEY,
name VARCHAR(64) NOT NULL,
defence_position VARCHAR(64)
);
mysql> show tables;
+------------------+
| Tables_in_tigers |
+------------------+
| players |
| schema_version |
+------------------+
Flyway
mysql> desc players;
+------------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(64) | NO | | NULL | |
| defence_position | varchar(64) | YES | | NULL | |
+------------------+-------------+------+-----+---------+-------+
mysql> select * from schema_versionG
** 1. row **
installed_rank: 1
version: 1
description: Create players table
type: SQL
script: V1__Create_players_table.sql
checksum: -2124815084
installed_by: root
installed_on: 2017-05-07 21:35:01
execution_time: 43
success: 1
Slick3
? Play2.4からSlick3になった。
? MonadicでReactiveになった。
? db.run() して DBIO を Future にする
使い方
? マッピングを自動生成する
package repositories.slick
import slick.jdbc.GetResult
import slick.jdbc.MySQLProfile.api._
/**
* Created by hiroyuki.ikawa on 2017/05/09.
*/
trait Tables{
case class Player(id: Int, name: String, defencePosition: Option[String] = None)
implicit val getPlayerResult = GetResult { r =>
Player(r.<<, r.<<, r.<<)
}
class Players(tag: Tag) extends Table[Player](tag, "players") {
def id = column[Int]("id", O.PrimaryKey)
def name = column[String]("name")
def defencePosition = column[Option[String]]("defence_position")
def * = (id, name, defencePosition) <> (Player.tupled, Player.unapply)
}
object Players extends TableQuery(new Players(_))
}
object Tables extends Tables
Repository
? DBから取得する
? DBIO型で返す
? テストやりやすくする
? トランザクションの制御はservice層でする
package repositories.slick
import com.google.inject.Singleton
import repositories.slick.Tables._
import slick.jdbc.MySQLProfile.api._
/**
* Created by hiroyuki.ikawa on 2017/05/09.
*/
@Singleton
class Players {
def getAllPlayers: DBIO[Seq[Player]] = {
Players.result
}
}
Service
? レポジトリからデータを取得して、コントローラー
に返す
? Future型で返す
package services
import com.google.inject.{Inject, Singleton}
import repositories.slick.Players
import repositories.slick.Tables.Player
import slick.jdbc.MySQLProfile.api._
import scala.concurrent.Future
/**
* Created by hiroyuki.ikawa on 2017/05/09.
*/
@Singleton
class PlayerService @Inject()(
val repo: Players
) {
val db = Database.forConfig("db.default")
def getPlayers: Future[Seq[Player]] = {
db.run(repo.getAllPlayers)
}
}
Controller
? データを取得してViewを表示
package controllers
import com.google.inject.{Inject, Singleton}
import play.api.mvc.{Action, Controller}
import services.PlayerService
import scala.concurrent.ExecutionContext
/**
* Created by hiroyuki.ikawa on 2017/05/09.
*/
@Singleton
class PlayersController @Inject()(
val service: PlayerService
)(implicit val ed: ExecutionContext) extends Controller {
def list = Action.async {
service.getPlayers.map { players =>
Ok(views.html.player.list(players))
}
}
}
他のAPIとの連携
? 他のAPIサーバーからデータを取得する
package repositories.api
import com.google.inject.Singleton
import play.api.libs.ws.WS
import scala.concurrent.Future
/**
* Created by hiroyuki.ikawa on 2017/05/09.
*/
@Singleton
class OutsideApi {
def battingAverage(name: String): Future[Double] = {
// WSで他のAPIサービスからデータを取ってくる
// WS.url("https://api.outside.internal/batting-average/:name")
// 今回はモックで適当に。
Future.successful(0.321)
}
}
Service
? ネストが深くなってくる、、
@Singleton
class PlayerService @Inject()(
val repo: Players,
val api: OutsideApi
)(implicit val ec: ExecutionContext) {
val db = Database.forConfig("db.default")
def getPlayers: Future[Seq[(Player, Double)]] = {
// DBから一覧を取得
db.run(repo.getAllPlayers).flatMap { players =>
// 選手毎に、
Future.sequence(players.map { player =>
// データを取得して、
api.battingAverage(player.name).map { average =>
// レスポンスするデータを作る
(player, average)
}
})
}
}
}
(APIを修正しておく)
? 複数取得できるAPIを用意する
? もしくは、repositoryで吸収する(Future.sequence)
@Singleton
class OutsideApi {
def battingAverage(name: String): Future[Double] = {
// WSで他のAPIサービスからデータを取ってくる
// WS.url("https://api.outside.internal/batting-average/:name")
// 今回はモックで適当に。
Future.successful(0.321)
}
// 複数の選手の打率を返す
def battingAverages(names: Seq[String]): Future[Map[String, Double]] = {
Future.successful(
names.map { name =>
name -> 0.321
}.toMap
)
}
}
for式を使う
? ネストがなくなって読みやすくなった
? 並列で実行できないか?
@Singleton
class PlayerService @Inject()(
val repo: Players,
val api: OutsideApi
)(implicit val ec: ExecutionContext) {
val db = Database.forConfig("db.default")
def getPlayers: Future[Seq[(Player, Double)]] = {
for {
// DBから取得
players <- db.run(repo.getAllPlayers)
// APIから取得
averages <- api.battingAverages(players.map(_.name))
} yield {
// データを生成
players.map { player =>
(player, averages(player.name))
}
}
}
}
APIが追加される
? チーム全員の打率を返すAPIが追加された
@Singleton
class OutsideApi {
// 複数の選手の打率を返す
def battingAverages(names: Seq[String]): Future[Map[String, Double]] = {
Future.successful(
names.map { name =>
name -> 0.321
}.toMap
)
}
// チームの全選手の打率を返す
def battingAveragesByTeam(name: String): Future[Map[String, Double]] = {
Future.successful(
Map(
"鳥谷" -> 0.321
)
)
}
}
forの罠
? 全員のデータをいっぺんに取ってくる
@Singleton
class PlayerService @Inject()(
val repo: Players,
val api: OutsideApi
)(implicit val ec: ExecutionContext) {
val db = Database.forConfig("db.default")
def getPlayers: Future[Seq[(Player, Double)]] = {
for {
// DBから取得
players <- db.run(repo.getAllPlayers)
// APIから取得
averages <- api.battingAveragesByTeam("tigers")
} yield {
// データを生成
players.map { player =>
(player, averages(player.name))
}
}
}
}
並列
? 先にFutureを実行する
@Singleton
class PlayerService @Inject()(
val repo: Players,
val api: OutsideApi
)(implicit val ec: ExecutionContext) {
val db = Database.forConfig("db.default")
def getPlayers: Future[Seq[(Player, Double)]] = {
// DBから取得
val playersFuture = db.run(repo.getAllPlayers)
// APIから取得
val averagesFuture = api.battingAveragesByTeam("tigers")
for {
players <- playersFuture
averages <- averagesFuture
} yield {
// データを生成
players.map(player => (player, averages(player.name)))
}
}
}
エラーを扱いたい
? 外部APIがエラーを返してきたらどうする?
? エラークラス
package models
/**
* Created by hiroyuki.ikawa on 2017/05/09.
*/
trait Errors {
override def toString: String = this match {
case e: InternalServerError => e.msg
}
}
final case class InternalServerError(msg: String) extends Errors
object Errors {
def internalServerError(msg: String) = InternalServerError(msg)
}
Either
? repository
? APIがエラーだったとき、Eitherで返すようにする
// チームの全選手の打率を返す
def battingAveragesByTeam(name: String): Future[Either[Errors, Map[String, Double]]] = {
Future(
Map(
"鳥谷" -> 0.321
)
).map(Right(_))
}
// チームの全選手の打率を返す
def battingAveragesByTeam(name: String): Future[Map[String, Double]] = {
Future.successful(
Map(
"鳥谷" -> 0.321
)
)
}
Service
? エラーをハンドリングして、LeftかRightで返す
@Singleton
class PlayerService @Inject()(
val repo: Players,
val api: OutsideApi
)(implicit val ec: ExecutionContext) {
val db = Database.forConfig("db.default")
def getPlayers: Future[Either[Errors, Seq[(Player, Double)]]] = {
// DBから取得
val playersFuture = db.run(repo.getAllPlayers)
// APIから取得
val averagesFuture = api.battingAveragesByTeam("tigers")
for {
players <- playersFuture
averagesEither <- averagesFuture
} yield {
// データを生成
averagesEither.fold(
error => Left(Errors.internalServerError("api error")),
averages => Right(players.map(player => (player, averages(player.name))))
)
}
}
}
Controller
? 適切なエラーを返せるようになる
? Future[Either[Errors, T]]とか面倒
? DBがEitherで返すようになったらどうする?
@Singleton
class PlayersController @Inject()(
val service: PlayerService
)(implicit val ed: ExecutionContext) extends Controller
{
def list = Action.async {
service.getPlayers.map { result =>
result.fold(
e => InternalServerError(e.toString),
result => Ok(views.html.player.list(result))
)
}
}
}
scalaz.EitherT
? repository
? /,/-
// チームの全選手の打率を返す
def battingAveragesByTeam(name: String): Future[/[Errors, Map[String, Double]]] = {
Future(
Map(
"鳥谷" -> 0.321
)
).map(/-(_))
}
// チームの全選手の打率を返す
def battingAveragesByTeam(name: String): Future[Either[Errors, Map[String, Double]]] = {
Future(
Map(
"鳥谷" -> 0.321
)
).map(Right(_))
}
service
? EitherT
? for式の中はスッキリしたけど、、、
@Singleton
class PlayerService @Inject()(
val repo: Players,
val api: OutsideApi
)(implicit val ec: ExecutionContext) {
val db = Database.forConfig("db.default")
def getPlayers: EitherT[Future, Errors, Seq[(Player, Double)]] = {
// DBから取得
val playersFuture: Future[/[Errors, Seq[Player]]] = db.run(repo.getAllPlayers).map(/-(_))
val playersFutureEither: EitherT[Future, Errors, Seq[Player]] = EitherT.eitherT(playersFuture)
// APIから取得
val averagesFuture: EitherT[Future, Errors, Map[String, Double]] =
EitherT.eitherT(api.battingAveragesByTeam("tigers"))
for {
players <- playersFutureEither
averages <- averagesFuture
} yield {
// データを生成
players.map(player => (player, averages(player.name)))
}
}
}
FunctionalSyntaxHelper
? 全部importするとcompileが重くなるので、必要なパッケージだけ
importしたtraitを用意しておいてそれだけを使うようにする
? scalazを便利に使えるようにする
package utils
import models.Errors
import scala.concurrent.Future
import scalaz.{Applicative, EitherT, /, /-}
import scalaz.syntax.ToEitherOps
/**
* Created by hiroyuki.ikawa on 2017/05/09.
*/
trait FunctionalSyntaxHelper extends ToEitherOps {
implicit class ToEitherT[A](a: A) {
/**
* A を EitherT[F, Errors, A] に変換する
*/
def toEitherT[F[_]](implicit F: Applicative[F]): EitherT[F, Errors, A] = {
val either: /[Errors, A] = /-(a)
EitherT(F.point(either))
}
}
implicit class RichEither[A, B](either: A / B) {
/**
* A / B を EitherT[F, A, B] に変換する
*/
def toEitherT[F[_]](implicit F: Applicative[F]): EitherT[F, A, B] = {
EitherT(F.point(either))
}
}
implicit class RichEitherFuture[A, B](eitherF: Future[A / B]) {
/**
* Future[/[A, B]] を EitherT[[Future A, B]] に変換する
*/
def toEitherT: EitherT[Future, A, B] = EitherT[Future, A, B](eitherF)
}
}
EitherT[Future,Errors,T]
? きれいになりました。
import scalaz.EitherT
import scalaz.Scalaz._
@Singleton
class PlayerService @Inject()(
val repo: Players,
val api: OutsideApi
) (implicit val ec: ExecutionContext)
extends FunctionalSyntaxHelper {
val db = Database.forConfig("db.default")
def getPlayers: EitherT[Future, Errors, Seq[(Player, Double)]] = {
// DBから取得
val playersFuture = db.run(repo.getAllPlayers).map(_.right[Errors]).toEitherT
// APIから取得
val averagesFuture = api.battingAveragesByTeam("tigers").toEitherT
for {
players <- playersFuture
averages <- averagesFuture
} yield {
// データを生成
players.map(player => (player, averages(player.name)))
}
}
}
EitherT
? EitherTのTはTransformerのT
? トランスフォーマーは2つのモナドを組み合わせて
別のモナドをつくるためのもの
? FutureとEitherから別のモナドを作っている
? なので、forできれいに書けた
/, -/, /-
? /: Either
? -/: Left
? /-: Right
もう少し
? importを減らす努力
trait FunctionalSyntaxHelper extends ToEitherOps with FutureInstances {
// import を省略するためのショートカット
type /[+A, +B] = scalaz./[A, B]
type -/[+A] = scalaz.-/[A]
type /-[+B] = scalaz./-[B]
type EitherT[F[_], A, B] = scalaz.EitherT[F, A, B]
val / : scalaz./.type = scalaz./
val -/ : scalaz.-/.type = scalaz.-/
val /- : scalaz./-.type = scalaz./-
importも減らせる
@Singleton
class PlayerService @Inject()(
val repo: Players,
val api: OutsideApi
) (implicit val ec: ExecutionContext)
extends FunctionalSyntaxHelper {
val db = Database.forConfig("db.default")
def getPlayers: EitherT[Future, Errors, Seq[(Player, Double)]] = {
// DBから取得
val playersFuture = db.run(repo.getAllPlayers).map(_.right[Errors]).toEitherT
// APIから取得
val averagesFuture = api.battingAveragesByTeam("tigers").toEitherT
for {
players <- playersFuture
averages <- averagesFuture
} yield {
// データを生成
players.map(player => (player, averages(player.name)))
}
}
}
scalazおまけ
? ToBooleanOps
? if文を減らせる
trait FunctionalSyntaxHelper extends ToEitherOps
with ToBooleanOps
with FutureInstances {
def useIf(is: Boolean): /[Errors, Boolean] = {
if (is) {
true.right
} else {
Errors.internalServerError("false").left
}
}
def useEither(is: Boolean): /[Errors, Boolean] = {
is either true or Errors.internalServerError("false")
}
Slick3のつらいところ
? DBIOとか、EitherTとか難しい。
? 無駄に非同期処理になって難易度が高い
? 発行されるSQLがよくわからない(Slick3でマシなっ
たけど)
? SQLの組み立て方も難しい
? ScalikeJDBCでええやん
scalikeJDBCの使い方
sbt
? project/plugins.sbt
? build.sbt
libraryDependencies += "mysql" % "mysql-connector-java" % "5.1.26"
addSbtPlugin("org.scalikejdbc" %% "scalikejdbc-mapper-generator" % "2.5.0")
libraryDependencies ++= Seq(
jdbc, cache, ws,
"com.typesafe.play" % "play-slick_2.11" % "2.1.0",
"com.typesafe.slick" % "slick_2.11" % "3.2.0",
"mysql" % "mysql-connector-java" % "6.0.6",
"org.scalaz" %% "scalaz-core" % "7.2.12",
// Scalikejdbc
"org.scalikejdbc" %% "scalikejdbc" % "2.5.0",
"org.scalikejdbc" %% "scalikejdbc-config" % "2.5.0",
"org.scalikejdbc" %% "scalikejdbc-play-dbapi-adapter" % "2.5.1",
specs2 % Test )
scalikejdbcSettings
自動生成
? project/scalikejdbc.properties
? $ sbt “scalikejdbcGen players”
# ---
# jdbc settings
jdbc.driver="com.mysql.jdbc.Driver"
jdbc.url="jdbc:mysql://localhost:3306/tigers"
jdbc.username="root"
jdbc.password=""
# ---
# source code generator settings
generator.packageName=repositories.scalikejdbc.jdbc
# generator.lineBreak: LF/CRLF
generator.lineBreak=LF
# generator.template: interpolation/queryDsl
generator.template=queryDsl
# generator.testTemplate: specs2unit/specs2acceptance/ScalaTestFlatSpec
generator.testTemplate=
# File Encoding
generator.encoding=UTF-8
# When you're using Scala 2.11 or higher, you can use case classes for 22+ columns tables
generator.caseClassOnly=true
# Set AutoSession for implicit DBSession parameter's default value
generator.defaultAutoSession=false
# Use autoConstruct macro (default: false)
generator.autoConstruct=false
# joda-time (org.joda.time.DateTime) or JSR-310 (java.time.ZonedDateTime java.time.OffsetDateTime)
generator.dateTimeClass=org.joda.time.DateTime
package repositories.scalikejdbc.jdbc
import scalikejdbc._
case class Players(
id: Int,
name: String,
defencePosition: Option[String] = None) {
def save()(implicit session: DBSession): Players = Players.save(this)(session)
def destroy()(implicit session: DBSession): Int = Players.destroy(this)(session)
}
object Players extends SQLSyntaxSupport[Players] {
override val tableName = "players"
override val columns = Seq("id", "name", "defence_position")
def apply(p: SyntaxProvider[Players])(rs: WrappedResultSet): Players = apply(p.resultName)(rs)
def apply(p: ResultName[Players])(rs: WrappedResultSet): Players = new Players(
id = rs.get(p.id),
name = rs.get(p.name),
defencePosition = rs.get(p.defencePosition)
)
val p = Players.syntax("p")
override val autoSession = AutoSession
def find(id: Int)(implicit session: DBSession): Option[Players] = {
withSQL {
select.from(Players as p).where.eq(p.id, id)
}.map(Players(p.resultName)).single.apply()
}
def findAll()(implicit session: DBSession): List[Players] = {
withSQL(select.from(Players as p)).map(Players(p.resultName)).list.apply()
}
def countAll()(implicit session: DBSession): Long = {
withSQL(select(sqls.count).from(Players as p)).map(rs => rs.long(1)).single.apply().get
}
def findBy(where: SQLSyntax)(implicit session: DBSession): Option[Players] = {
withSQL {
select.from(Players as p).where.append(where)
}.map(Players(p.resultName)).single.apply()
}
def findAllBy(where: SQLSyntax)(implicit session: DBSession): List[Players] = {
withSQL {
select.from(Players as p).where.append(where)
}.map(Players(p.resultName)).list.apply()
}
def countBy(where: SQLSyntax)(implicit session: DBSession): Long = {
withSQL {
select(sqls.count).from(Players as p).where.append(where)
}.map(_.long(1)).single.apply().get
}
def create(
id: Int,
name: String,
defencePosition: Option[String] = None)(implicit session: DBSession): Players = {
withSQL {
insert.into(Players).namedValues(
column.id -> id,
column.name -> name,
column.defencePosition -> defencePosition
)
}.update.apply()
Players(
id = id,
name = name,
defencePosition = defencePosition)
}
def batchInsert(entities: Seq[Players])(implicit session: DBSession): List[Int] = {
val params: Seq[Seq[(Symbol, Any)]] = entities.map(entity =>
Seq(
'id -> entity.id,
'name -> entity.name,
'defencePosition -> entity.defencePosition))
SQL("""insert into players(
id,
name,
defence_position
) values (
{id},
{name},
{defencePosition}
)""").batchByName(params: _*).apply[List]()
}
def save(entity: Players)(implicit session: DBSession): Players = {
withSQL {
update(Players).set(
column.id -> entity.id,
column.name -> entity.name,
column.defencePosition -> entity.defencePosition
).where.eq(column.id, entity.id)
}.update.apply()
entity
}
def destroy(entity: Players)(implicit session: DBSession): Int = {
withSQL { delete.from(Players).where.eq(column.id, entity.id) }.update.apply()
}
}
repository
package repositories.scalikejdbc
import repositories.scalikejdbc.jdbc.Players
import scalikejdbc._
/**
* Created by hiroyuki.ikawa on 2017/05/10.
*/
class PlayerRepository {
Class.forName("com.mysql.jdbc.Driver")
ConnectionPool.singleton("jdbc:mysql://localhost:3306/tigers?useSSL=false", "root", "")
implicit val session = AutoSession
def getAllPlayers: Seq[Players] = {
Players.findAll
}
}
service
package services
import com.google.inject.{Inject, Singleton}
import models.Errors
import repositories.api.OutsideApi
import repositories.scalikejdbc.PlayerRepository
import repositories.scalikejdbc.jdbc.Players
import utils.FunctionalSyntaxHelper
import scala.concurrent.{ExecutionContext, Future}
/**
* Created by hiroyuki.ikawa on 2017/05/09.
*/
@Singleton
class PlayerService @Inject()(
val repo: PlayerRepository,
val api: OutsideApi
) (implicit val ec: ExecutionContext)
extends FunctionalSyntaxHelper {
def getPlayers: Future[/[Errors, Seq[(Players, Double)]]] = {
// DBから取得
val players = repo.getAllPlayers
// APIから取得
api.battingAveragesByTeam("tigers").map { averagesEither =>
averagesEither.map { averages =>
players.map(player => (player, averages(player.name)))
}
}
}
}
ScalikeJDBC
? SQLがわりと直感的に書ける
? bindとかいらない
? 無駄に非同期処理じゃないので、DBIOとか考えなくていい
? DBIOとかbindがあるかとか考えなくていいので、レビューし
やすい
? DBについてあまり考えなくて良くなるので、アプリケーショ
ンの設計や、ビジネスロジックのことを考えられるようになる
ありがとうございました

More Related Content

Play2 scalaを2年やって学んた?こと