play2 scalaを2年やって学んだこと

61
Play2 Scala2年やっ て学んだこと

Upload: dcubeio

Post on 21-Jan-2018

839 views

Category:

Software


0 download

TRANSCRIPT

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_version\G

*************************** 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.GetResultimport 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.Singletonimport repositories.slick.Tables._import slick.jdbc.MySQLProfile.api._

/*** Created by hiroyuki.ikawa on 2017/05/09.*/

@Singletonclass Players {

def getAllPlayers: DBIO[Seq[Player]] = {Players.result

}

}

Service

• レポジトリからデータを取得して、コントローラーに返す

• Future型で返す

package services

import com.google.inject.{Inject, Singleton}import repositories.slick.Playersimport repositories.slick.Tables.Playerimport slick.jdbc.MySQLProfile.api._

import scala.concurrent.Future

/*** Created by hiroyuki.ikawa on 2017/05/09.*/

@Singletonclass 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.*/

@Singletonclass 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.Singletonimport play.api.libs.ws.WS

import scala.concurrent.Future

/*** Created by hiroyuki.ikawa on 2017/05/09.*/

@Singletonclass OutsideApi {

def battingAverage(name: String): Future[Double] = {

// WSで他のAPIサービスからデータを取ってくる// WS.url("https://api.outside.internal/batting-average/:name")

// 今回はモックで適当に。Future.successful(0.321)

}

}

Service

• ネストが深くなってくる、、

@Singletonclass 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)

@Singletonclass 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式を使う• ネストがなくなって読みやすくなった

• 並列で実行できないか?

@Singletonclass 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が追加された

@Singletonclass 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の罠

• 全員のデータをいっぺんに取ってくる

@Singletonclass 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を実行する

@Singletonclass 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 <- playersFutureaverages <- 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で返す

@Singletonclass 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 <- playersFutureaveragesEither <- 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で返すようになったらどうする?

@Singletonclass 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式の中はスッキリしたけど、、、

@Singletonclass 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 <- playersFutureEitheraverages <- averagesFuture

} yield {// データを生成players.map(player => (player, averages(player.name)))

}}

}

FunctionalSyntaxHelper

• 全部importするとcompileが重くなるので、必要なパッケージだけimportしたtraitを用意しておいてそれだけを使うようにする

• scalazを便利に使えるようにする

package utils

import models.Errors

import scala.concurrent.Futureimport 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.EitherTimport scalaz.Scalaz._

@Singletonclass 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").toEitherTfor {

players <- playersFutureaverages <- 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も減らせる

@Singletonclass 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").toEitherTfor {

players <- playersFutureaverages <- averagesFuture

} yield {// データを生成players.map(player => (player, averages(player.name)))

}}

}

scalazおまけ

• ToBooleanOps

• if文を減らせる

trait FunctionalSyntaxHelper extends ToEitherOpswith ToBooleanOpswith 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 settingsgenerator.packageName=repositories.scalikejdbc.jdbc# generator.lineBreak: LF/CRLFgenerator.lineBreak=LF# generator.template: interpolation/queryDslgenerator.template=queryDsl# generator.testTemplate: specs2unit/specs2acceptance/ScalaTestFlatSpecgenerator.testTemplate=# File Encodinggenerator.encoding=UTF-8# When you're using Scala 2.11 or higher, you can use case classes for 22+ columns tablesgenerator.caseClassOnly=true# Set AutoSession for implicit DBSession parameter's default valuegenerator.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.Playersimport 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

}

}

servicepackage services

import com.google.inject.{Inject, Singleton}import models.Errorsimport repositories.api.OutsideApiimport repositories.scalikejdbc.PlayerRepositoryimport repositories.scalikejdbc.jdbc.Playersimport utils.FunctionalSyntaxHelper

import scala.concurrent.{ExecutionContext, Future}

/*** Created by hiroyuki.ikawa on 2017/05/09.*/

@Singletonclass 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についてあまり考えなくて良くなるので、アプリケーションの設計や、ビジネスロジックのことを考えられるようになる

ありがとうございました