狠狠撸
Submit Search
Play2 scalaを2年やって学んた?こと
?
Download as PPTX, PDF
?
4 likes
?
3,750 views
dcubeio
Follow
Play2 Scalaを2年やって学んた?こと
Read less
Read more
1 of 61
Download now
Download to read offline
More Related Content
Play2 scalaを2年やって学んた?こと
1.
Play2 Scalaを2年やっ て学んだこと
2.
D3 創業以来、高い技術力と戦略的なUI/UXを武器に、世の中に価値あるサービスを 生み出しているビズリーチ。 サービスの数が増えるにつれ、技術の幅が広がったため、そのスキルやノウハウ を社内のみならず、 世の中のエンジニアやデザイナーとも共有すべく、 私たちは「D3(ディーキューブ)※」というプロジェクトチームを立ち上げま した。 D3では、たくさんのイベントや勉強会を開催し、 世のエンジニア?デザイナーと共に、さらなる高みを目指します。 ※D3=DESIGNER & DEVELOPER
DIVISION
3.
自己紹介 ? 名前:伊川弘之樹 ? 職業:プログラマー ?
株式会社ビズリーチ インキュベーションカンパニー スタンバイアプリ事業部 マネージャー ? 趣味:子供と散歩、タイガースの応援
4.
長男 2才
5.
次男 2ヶ月
6.
今日話すこと ? スタンバイの紹介 ? 全体の設計 ?
Slick3 ? scalaz ? scalikeJDBC
7.
スタンバイ ? https://jp.stanby.com ? 400万件以上の求人をまとめて探せる日本最大級の 求人検索エンジン。 ?
企業公式サイトや求人サイトの ありとあらゆる求 人情報が探せます。
8.
スタンバイ?カンパニー ? https://stanby.co/ ? スタンバイ?カンパニーは、誰でもかんたんに無料 で求人を作成できるサービスです。 ?
作成した求人はスタンバイに掲載されます。 ? また、応募者とチャットでやりとりをしたり、動画 で面接を行うこともできます。
9.
スタンバイアプリ ? 掲載求人数は400万件以上で日本最大級。 ? 1000以上の求人?転職サイトや企業サイトを横断検索 ?
正社員からアルバイト?パートまでのあらゆる働き方やこだわり条件で仕事を探す ことができます。 ? 自分のプロフィールを登録しておくと、お店や企業から仕事のお誘いが届きます。 ? 興味があれば、そのままお店?企業の方とチャットでやりとりをしていただき、不 明点などを気軽に聞くことができます。 ? そこで働いてみたいと思ったら、そのままチャットで面接の日程調整をしたり、ス タンバイアプリのビデオ通話機能を使えば、自宅にいながらスマホで面接もできま す。
10.
カンパニーアプリ ? たった3分 無料で求人掲載 ?
働きたいひとにスカウトを送信 ? 応募が来たらお知らせ
11.
使っている技術(Client) ? Web ? HTML,CSS,JavaScript ?
React,jQuery,Angular,TypeScript ? Android ? Java ? iOS ? Swift3
12.
使っている技術(AWS) ? Scala(2.11.x) ? PlayFramework(2.3,2.5) ?
MySQL(RDS) ? Redis,Memcache(ElastiCache) ? S3,SQS,SNS,Kinesis…
13.
使っている技術(Firebase) ? RealTimeDatabase ? BigQuery ?
ストレージ ? Remote Config
14.
アーキテクチャ (マイクロサービスの図) それぞれがRDSやCache、S3などを持っています。 他にも、バッチサーバーや、 管理画面用のサーバーや、 ログ集計用のサーバーがあります
15.
スタンバイ ? ElasticSeachから求人情報を検索する ? クライアントはブラウザ(PC,Mobile)、Android、iOS ?
ブラウザは別サーバー ? ユーザーの情報を管理する ? 履歴書、検索履歴、閲覧履歴 ? スカウトされるための情報 ? 求人広告を配信して稼いでいます。 ? Yahoo!しごと検索にもAPI提供しています
16.
スタンバイ カンパニー ? 企業やお店が求人を作成したら、ElasticSearchに インデックスします ?
求職者から応募があったときに応募情報を管理しま す。 ? スカウトします
17.
クローラー ? 求人サイトや、企業HPの求人ページをクローリン グして、ElasticSearchにインデックスします。 ? よりよい検索結果をユーザーに提供できるように、 求人の内容を学習しているようです。
18.
サーバーサイド ? 基本的にはPlay2 Scala ?
ORM:Slick3 or ScalikeJDBC ? DBMigration:DBFlute or Flyway
19.
サーバーアプリケーションの パッケージ構成 ? app ? controllers ?
models ? アプリケーション内でつかうモデル(ユーザー、求人、応募、閲覧履歴、エラー) ? repositories ? 永続化 ? DBや他のAPI、AWSのサービスとつなぐ役割 ? services ? コントローラーとレポジトリをつなぐ ? DBトランザクションの管理(リポジトリをまたいだトランザクションの為と、repositoryのテストがし易いため) ? utils ? views ? twirl用のhtmlや、レスポンスがjsonの場合はcase classやwritesを置く
20.
? 下の層が上の層に依存しないように。 ? repository層が受け取るのはできるだけプリミテ ィブな型 ?
上の層は下の層を知らなくてもいいように。 ? repository層が返すのはエンティティ
21.
controllers ? 機能毎にパッケージを分ける ? リクエストのバリデーション ?
メイン処理の呼び出し ? レスポンスの組み立て controllers/ players/ PlayerController param/ Request view/ Response jobs/ applications/
22.
DBFlute ? http://dbflute.seasar.org/ ? EclipseでER図を作って、DDLや、Alter文を生成し 、DBをマイグレーションする ?
手顺が难しく、覚えられないのでやめた。
23.
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"
24.
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 | +------------------+
25.
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
26.
Slick3 ? Play2.4からSlick3になった。 ? MonadicでReactiveになった。 ?
db.run() して DBIO を Future にする
27.
使い方 ? マッピングを自動生成する 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
28.
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 } }
29.
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) } }
30.
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)) } } }
31.
他の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) } }
32.
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) } }) } } }
33.
(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 ) } }
34.
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)) } } } }
35.
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 ) ) } }
36.
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)) } } } }
37.
並列 ? 先に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))) } } }
38.
エラーを扱いたい ? 外部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) }
39.
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 ) ) }
40.
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)))) ) } } }
41.
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)) ) } } }
42.
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(_)) }
43.
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))) } } }
44.
FunctionalSyntaxHelper ? 全部importするとcompileが重くなるので、必要なパッケージだけ importしたtraitを用意しておいてそれだけを使うようにする ? scalazを便利に使えるようにする
45.
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) } }
46.
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))) } } }
47.
EitherT ? EitherTのTはTransformerのT ? トランスフォーマーは2つのモナドを組み合わせて 別のモナドをつくるためのもの ?
FutureとEitherから別のモナドを作っている ? なので、forできれいに書けた
48.
/, -/, /- ?
/: Either ? -/: Left ? /-: Right
49.
もう少し ? 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./-
50.
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))) } } }
51.
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") }
52.
Slick3のつらいところ ? DBIOとか、EitherTとか難しい。 ? 無駄に非同期処理になって難易度が高い ?
発行されるSQLがよくわからない(Slick3でマシなっ たけど) ? SQLの組み立て方も難しい ? ScalikeJDBCでええやん
53.
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
54.
自動生成 ? 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
55.
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 }
56.
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) }
57.
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() } }
58.
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 } }
59.
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))) } } } }
60.
ScalikeJDBC ? SQLがわりと直感的に書ける ? bindとかいらない ?
無駄に非同期処理じゃないので、DBIOとか考えなくていい ? DBIOとかbindがあるかとか考えなくていいので、レビューし やすい ? DBについてあまり考えなくて良くなるので、アプリケーショ ンの設計や、ビジネスロジックのことを考えられるようになる
61.
ありがとうございました
Download