More Related Content
PDF
PDF
PostgreSQL16でのロールに関する変更点(第41回PostgreSQLアンカンファレンス@オンライン 発表資料) PDF
PDF
これからのJDK 何を選ぶ?どう選ぶ? (v1.2) in 熊本 PPTX
iostat await svctm の 見かた、考え方 PDF
Apache Kafka 0.11 の Exactly Once Semantics PDF
PPTX
What's hot
PDF
PDF
PPTX
今こそ知りたいSpring Batch(Spring Fest 2020講演資料) PPTX
PDF
ゲームアーキテクチャパターン (Aurora Serverless / DynamoDB) PPTX
Apache Sparkの基本と最新バージョン3.2のアップデート(Open Source Conference 2021 Online/Fukuoka ... PDF
What's new in Spring Batch 5 PDF
PPTX
PDF
怖くないSpring Bootのオートコンフィグレーション PDF
PDF
PDF
LogbackからLog4j 2への移行によるアプリケーションのスループット改善 ( JJUG CCC 2021 Fall ) PPTX
え、まって。その並列分散処理、Kafkaのしくみでもできるの? Apache Kafkaの機能を利用した大規模ストリームデータの並列分散処理 PDF
PPTX
PDF
ネットワーク ゲームにおけるTCPとUDPの使い分け PDF
PDF
PDF
Similar to Play2 scalaを2年やって学んだこと
PDF
PDF
めんどくさくない Scala #kwkni_scala PDF
PDF
PPT
PDF
PPTX
PDF
PDF
Object-Functional Analysis and Design : 次世代モデリングパラダイムへの道標 PDF
PDF
テスト 【クラウドアプリケーションのためのオブジェクト指向分析設計講座 第33回】 PDF
PDF
PDF
PDF
PDF
Object-Funcational Analysis and design PDF
Kink: invokedynamic on a prototype-based language PDF
PDF
PDF
More from dcubeio
PDF
Apiドキュメンテーションツールを使いこなす【api blueprint編】 PPTX
PDF
Go初心者がGoでコマンドラインツールの作成に挑戦した話 PDF
PDF
すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー! PDF
初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1) PDF
PDF
ビットコインとブロックチェーンを初めからていねいに(超基礎編) PDF
PPT
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料 PDF
Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜 PDF
Python × Herokuで作る 雑談slack bot PDF
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料 PDF
機械学習を支えるX86 64の拡張命令セットを読む会 20170212 PPTX
PDF
【ビズリーチ】プロダクトマネージャーの仕事と魅力 PPTX
PDF
20171206 d3 health_tech発表資料 PPTX
PDF
AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」 Play2 scalaを2年やって学んだこと
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
サーバーアプリケーションの
パッケージ構成
• app
• controllers
•models
• アプリケーション内でつかうモデル(ユーザー、求人、応募、閲覧履歴、エラー)
• repositories
• 永続化
• DBや他のAPI、AWSのサービスとつなぐ役割
• services
• コントローラーとレポジトリをつなぐ
• DBトランザクションの管理(リポジトリをまたいだトランザクションの為と、repositoryのテストがし易いため)
• utils
• views
• twirl用のhtmlや、レスポンスがjsonの場合はcase classやwritesを置く
- 20.
- 21.
- 22.
- 23.
- 24.
- 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.
- 27.
使い方
• マッピングを自動生成する
package repositories.slick
importslick.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型で返す
packageservices
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
importcom.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
importcom.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
classOutsideApi {
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
classPlayerService @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がエラーを返してきたらどうする?
• エラークラス
packagemodels
/**
* 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
classPlayerService @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.
- 45.
package utils
import models.Errors
importscala.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
importscalaz.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.
- 48.
- 49.
もう少し
• importを減らす努力
trait FunctionalSyntaxHelperextends 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()(
valrepo: 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文を減らせる
traitFunctionalSyntaxHelper 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.
- 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._
caseclass 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)(implicitsession: 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])(implicitsession: 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.
- 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.
- 61.