SlideShare ist ein Scribd-Unternehmen logo
1 von 61
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についてあまり考えなくて良くなるので、アプリケーショ
ンの設計や、ビジネスロジックのことを考えられるようになる
ありがとうございました

Weitere ähnliche Inhalte

Was ist angesagt?

ZabbixのAPIを使って運用を楽しくする話
ZabbixのAPIを使って運用を楽しくする話ZabbixのAPIを使って運用を楽しくする話
ZabbixのAPIを使って運用を楽しくする話Masahito Zembutsu
 
ゼロ幅スペースという悪夢
ゼロ幅スペースという悪夢ゼロ幅スペースという悪夢
ゼロ幅スペースという悪夢swamp Sawa
 
ブレソルでテラバイト級データのALTERを短時間で終わらせる
ブレソルでテラバイト級データのALTERを短時間で終わらせるブレソルでテラバイト級データのALTERを短時間で終わらせる
ブレソルでテラバイト級データのALTERを短時間で終わらせるKLab Inc. / Tech
 
[D12] NonStop SQLって何? by Susumu Yamamoto
[D12] NonStop SQLって何? by Susumu Yamamoto[D12] NonStop SQLって何? by Susumu Yamamoto
[D12] NonStop SQLって何? by Susumu YamamotoInsight Technology, Inc.
 
Cassandra における SSD の活用
Cassandra における SSD の活用Cassandra における SSD の活用
Cassandra における SSD の活用Yuji Ito
 
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話Koichiro Matsuoka
 
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところY Watanabe
 
Apache Kafka 0.11 の Exactly Once Semantics
Apache Kafka 0.11 の Exactly Once SemanticsApache Kafka 0.11 の Exactly Once Semantics
Apache Kafka 0.11 の Exactly Once SemanticsYoshiyasu SAEKI
 
忙しい人の5分で分かるMesos入門 - Mesos って何だ?
忙しい人の5分で分かるMesos入門 - Mesos って何だ?忙しい人の5分で分かるMesos入門 - Mesos って何だ?
忙しい人の5分で分かるMesos入門 - Mesos って何だ?Masahito Zembutsu
 
DeNAのサーバー"コード"レスアーキテクチャ
DeNAのサーバー"コード"レスアーキテクチャDeNAのサーバー"コード"レスアーキテクチャ
DeNAのサーバー"コード"レスアーキテクチャHaruto Otake
 
ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門増田 亨
 
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことBIGLOBE Inc.
 
ビジネスルールの複雑さに立ち向かう
ビジネスルールの複雑さに立ち向かうビジネスルールの複雑さに立ち向かう
ビジネスルールの複雑さに立ち向かう増田 亨
 
Azure Data Box Family Overview and Microsoft Intelligent Edge Strategy
Azure Data Box Family Overview and Microsoft Intelligent Edge StrategyAzure Data Box Family Overview and Microsoft Intelligent Edge Strategy
Azure Data Box Family Overview and Microsoft Intelligent Edge StrategyTakeshi Fukuhara
 

Was ist angesagt? (20)

ZabbixのAPIを使って運用を楽しくする話
ZabbixのAPIを使って運用を楽しくする話ZabbixのAPIを使って運用を楽しくする話
ZabbixのAPIを使って運用を楽しくする話
 
ゼロ幅スペースという悪夢
ゼロ幅スペースという悪夢ゼロ幅スペースという悪夢
ゼロ幅スペースという悪夢
 
ブレソルでテラバイト級データのALTERを短時間で終わらせる
ブレソルでテラバイト級データのALTERを短時間で終わらせるブレソルでテラバイト級データのALTERを短時間で終わらせる
ブレソルでテラバイト級データのALTERを短時間で終わらせる
 
ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開
 
[D12] NonStop SQLって何? by Susumu Yamamoto
[D12] NonStop SQLって何? by Susumu Yamamoto[D12] NonStop SQLって何? by Susumu Yamamoto
[D12] NonStop SQLって何? by Susumu Yamamoto
 
Cassandra における SSD の活用
Cassandra における SSD の活用Cassandra における SSD の活用
Cassandra における SSD の活用
 
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
 
Rdra2.0 redmine
Rdra2.0 redmineRdra2.0 redmine
Rdra2.0 redmine
 
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
 
世界最強のソフトウェアアーキテクト
世界最強のソフトウェアアーキテクト世界最強のソフトウェアアーキテクト
世界最強のソフトウェアアーキテクト
 
Apache Kafka 0.11 の Exactly Once Semantics
Apache Kafka 0.11 の Exactly Once SemanticsApache Kafka 0.11 の Exactly Once Semantics
Apache Kafka 0.11 の Exactly Once Semantics
 
忙しい人の5分で分かるMesos入門 - Mesos って何だ?
忙しい人の5分で分かるMesos入門 - Mesos って何だ?忙しい人の5分で分かるMesos入門 - Mesos って何だ?
忙しい人の5分で分かるMesos入門 - Mesos って何だ?
 
DeNAのサーバー"コード"レスアーキテクチャ
DeNAのサーバー"コード"レスアーキテクチャDeNAのサーバー"コード"レスアーキテクチャ
DeNAのサーバー"コード"レスアーキテクチャ
 
ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門
 
AWS Database Migration Service ご紹介
AWS Database Migration Service ご紹介AWS Database Migration Service ご紹介
AWS Database Migration Service ご紹介
 
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したこと
 
ServiceとRepository
ServiceとRepositoryServiceとRepository
ServiceとRepository
 
ビジネスルールの複雑さに立ち向かう
ビジネスルールの複雑さに立ち向かうビジネスルールの複雑さに立ち向かう
ビジネスルールの複雑さに立ち向かう
 
【BS4】時は来たれり。今こそ .NET 6 へ移行する時。
【BS4】時は来たれり。今こそ .NET 6 へ移行する時。 【BS4】時は来たれり。今こそ .NET 6 へ移行する時。
【BS4】時は来たれり。今こそ .NET 6 へ移行する時。
 
Azure Data Box Family Overview and Microsoft Intelligent Edge Strategy
Azure Data Box Family Overview and Microsoft Intelligent Edge StrategyAzure Data Box Family Overview and Microsoft Intelligent Edge Strategy
Azure Data Box Family Overview and Microsoft Intelligent Edge Strategy
 

Ähnlich wie Play2 scalaを2年やって学んだこと

Djangoフレームワークの紹介
Djangoフレームワークの紹介Djangoフレームワークの紹介
Djangoフレームワークの紹介Shinya Okano
 
Play meetup-2-dev-best-practices
Play meetup-2-dev-best-practicesPlay meetup-2-dev-best-practices
Play meetup-2-dev-best-practicesk4200
 
Realmの暗号化とAndroid System
Realmの暗号化とAndroid SystemRealmの暗号化とAndroid System
Realmの暗号化とAndroid SystemKeiji Ariyama
 
JavaScript/CSS 2015 Autumn
JavaScript/CSS 2015 AutumnJavaScript/CSS 2015 Autumn
JavaScript/CSS 2015 AutumnKoji Ishimoto
 
CodeIgniterによるPhwittr
CodeIgniterによるPhwittrCodeIgniterによるPhwittr
CodeIgniterによるPhwittrkenjis
 
ピクサー USD 入門 新たなコンテンツパイプラインを構築する
ピクサー USD 入門 新たなコンテンツパイプラインを構築するピクサー USD 入門 新たなコンテンツパイプラインを構築する
ピクサー USD 入門 新たなコンテンツパイプラインを構築するTakahito Tejima
 
Preview: 世界中のゲーム分析をしてきたPlayFabが大進化!一緒に裏側の最新データ探索の仕組みを覗いてみよう
Preview: 世界中のゲーム分析をしてきたPlayFabが大進化!一緒に裏側の最新データ探索の仕組みを覗いてみようPreview: 世界中のゲーム分析をしてきたPlayFabが大進化!一緒に裏側の最新データ探索の仕組みを覗いてみよう
Preview: 世界中のゲーム分析をしてきたPlayFabが大進化!一緒に裏側の最新データ探索の仕組みを覗いてみようDaisuke Masubuchi
 
Let's build a simple app with .net 6 asp.net core web api, react, and elasti...
Let's build a simple app with  .net 6 asp.net core web api, react, and elasti...Let's build a simple app with  .net 6 asp.net core web api, react, and elasti...
Let's build a simple app with .net 6 asp.net core web api, react, and elasti...Shotaro Suzuki
 
Next2Dで始めるゲーム開発 - Game Development Starting with Next2D
Next2Dで始めるゲーム開発  - Game Development Starting with Next2DNext2Dで始めるゲーム開発  - Game Development Starting with Next2D
Next2Dで始めるゲーム開発 - Game Development Starting with Next2DToshiyuki Ienaga
 
Azure IoT Edge で Custom Vision
Azure IoT Edge で Custom VisionAzure IoT Edge で Custom Vision
Azure IoT Edge で Custom VisionYoshitaka Seo
 
Grafana Dashboards as Code
Grafana Dashboards as CodeGrafana Dashboards as Code
Grafana Dashboards as CodeTakuhiro Yoshida
 
初めての Data api
初めての Data api初めての Data api
初めての Data apiYuji Takayama
 
TypeScript と Visual Studio Code
TypeScript と Visual Studio CodeTypeScript と Visual Studio Code
TypeScript と Visual Studio CodeAkira Inoue
 
AngularJSとD3.jsによるインタラクティブデータビジュアライゼーション
AngularJSとD3.jsによるインタラクティブデータビジュアライゼーションAngularJSとD3.jsによるインタラクティブデータビジュアライゼーション
AngularJSとD3.jsによるインタラクティブデータビジュアライゼーションYosuke Onoue
 
初めてのPadrino
初めてのPadrino初めてのPadrino
初めてのPadrinoTakeshi Yabe
 
IDEを目指す開発者コンソール
IDEを目指す開発者コンソールIDEを目指す開発者コンソール
IDEを目指す開発者コンソールminoaw
 
d3jsハンズオン @E2D3ハッカソン
d3jsハンズオン @E2D3ハッカソンd3jsハンズオン @E2D3ハッカソン
d3jsハンズオン @E2D3ハッカソン圭輔 大曽根
 
復習も兼ねて!C#6.0-7.0
復習も兼ねて!C#6.0-7.0復習も兼ねて!C#6.0-7.0
復習も兼ねて!C#6.0-7.0Yuta Matsumura
 

Ähnlich wie Play2 scalaを2年やって学んだこと (20)

Teclab3
Teclab3Teclab3
Teclab3
 
Djangoフレームワークの紹介
Djangoフレームワークの紹介Djangoフレームワークの紹介
Djangoフレームワークの紹介
 
Play meetup-2-dev-best-practices
Play meetup-2-dev-best-practicesPlay meetup-2-dev-best-practices
Play meetup-2-dev-best-practices
 
Realmの暗号化とAndroid System
Realmの暗号化とAndroid SystemRealmの暗号化とAndroid System
Realmの暗号化とAndroid System
 
JavaScript/CSS 2015 Autumn
JavaScript/CSS 2015 AutumnJavaScript/CSS 2015 Autumn
JavaScript/CSS 2015 Autumn
 
CodeIgniterによるPhwittr
CodeIgniterによるPhwittrCodeIgniterによるPhwittr
CodeIgniterによるPhwittr
 
ピクサー USD 入門 新たなコンテンツパイプラインを構築する
ピクサー USD 入門 新たなコンテンツパイプラインを構築するピクサー USD 入門 新たなコンテンツパイプラインを構築する
ピクサー USD 入門 新たなコンテンツパイプラインを構築する
 
Preview: 世界中のゲーム分析をしてきたPlayFabが大進化!一緒に裏側の最新データ探索の仕組みを覗いてみよう
Preview: 世界中のゲーム分析をしてきたPlayFabが大進化!一緒に裏側の最新データ探索の仕組みを覗いてみようPreview: 世界中のゲーム分析をしてきたPlayFabが大進化!一緒に裏側の最新データ探索の仕組みを覗いてみよう
Preview: 世界中のゲーム分析をしてきたPlayFabが大進化!一緒に裏側の最新データ探索の仕組みを覗いてみよう
 
Let's build a simple app with .net 6 asp.net core web api, react, and elasti...
Let's build a simple app with  .net 6 asp.net core web api, react, and elasti...Let's build a simple app with  .net 6 asp.net core web api, react, and elasti...
Let's build a simple app with .net 6 asp.net core web api, react, and elasti...
 
Next2Dで始めるゲーム開発 - Game Development Starting with Next2D
Next2Dで始めるゲーム開発  - Game Development Starting with Next2DNext2Dで始めるゲーム開発  - Game Development Starting with Next2D
Next2Dで始めるゲーム開発 - Game Development Starting with Next2D
 
Azure IoT Edge で Custom Vision
Azure IoT Edge で Custom VisionAzure IoT Edge で Custom Vision
Azure IoT Edge で Custom Vision
 
Grafana Dashboards as Code
Grafana Dashboards as CodeGrafana Dashboards as Code
Grafana Dashboards as Code
 
初めての Data api
初めての Data api初めての Data api
初めての Data api
 
TypeScript と Visual Studio Code
TypeScript と Visual Studio CodeTypeScript と Visual Studio Code
TypeScript と Visual Studio Code
 
AngularJSとD3.jsによるインタラクティブデータビジュアライゼーション
AngularJSとD3.jsによるインタラクティブデータビジュアライゼーションAngularJSとD3.jsによるインタラクティブデータビジュアライゼーション
AngularJSとD3.jsによるインタラクティブデータビジュアライゼーション
 
ScalaMatsuri 2016
ScalaMatsuri 2016ScalaMatsuri 2016
ScalaMatsuri 2016
 
初めてのPadrino
初めてのPadrino初めてのPadrino
初めてのPadrino
 
IDEを目指す開発者コンソール
IDEを目指す開発者コンソールIDEを目指す開発者コンソール
IDEを目指す開発者コンソール
 
d3jsハンズオン @E2D3ハッカソン
d3jsハンズオン @E2D3ハッカソンd3jsハンズオン @E2D3ハッカソン
d3jsハンズオン @E2D3ハッカソン
 
復習も兼ねて!C#6.0-7.0
復習も兼ねて!C#6.0-7.0復習も兼ねて!C#6.0-7.0
復習も兼ねて!C#6.0-7.0
 

Mehr von dcubeio

AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」
AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」
AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」dcubeio
 
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料dcubeio
 
ビットコインとブロックチェーンを初めからていねいに(超基礎編)
ビットコインとブロックチェーンを初めからていねいに(超基礎編)ビットコインとブロックチェーンを初めからていねいに(超基礎編)
ビットコインとブロックチェーンを初めからていねいに(超基礎編)dcubeio
 
20171206 d3 health_tech発表資料
20171206 d3 health_tech発表資料20171206 d3 health_tech発表資料
20171206 d3 health_tech発表資料dcubeio
 
Go初心者がGoでコマンドラインツールの作成に挑戦した話
Go初心者がGoでコマンドラインツールの作成に挑戦した話Go初心者がGoでコマンドラインツールの作成に挑戦した話
Go初心者がGoでコマンドラインツールの作成に挑戦した話dcubeio
 
初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)
初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)
初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)dcubeio
 
BizReach x Marketo連携
BizReach x Marketo連携BizReach x Marketo連携
BizReach x Marketo連携dcubeio
 
Kinesis Firehoseを使ってみた
Kinesis Firehoseを使ってみたKinesis Firehoseを使ってみた
Kinesis Firehoseを使ってみたdcubeio
 
Apiドキュメンテーションツールを使いこなす【api blueprint編】
Apiドキュメンテーションツールを使いこなす【api blueprint編】Apiドキュメンテーションツールを使いこなす【api blueprint編】
Apiドキュメンテーションツールを使いこなす【api blueprint編】dcubeio
 
春の脆弱性祭り 2017/06/13
春の脆弱性祭り 2017/06/13春の脆弱性祭り 2017/06/13
春の脆弱性祭り 2017/06/13dcubeio
 
DynamoDBを導入した話
DynamoDBを導入した話DynamoDBを導入した話
DynamoDBを導入した話dcubeio
 
すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー!
すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー! すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー!
すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー! dcubeio
 
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料dcubeio
 
Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜
Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜
Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜dcubeio
 
【freee】プロダクトマネージャーの仕事と魅力
【freee】プロダクトマネージャーの仕事と魅力【freee】プロダクトマネージャーの仕事と魅力
【freee】プロダクトマネージャーの仕事と魅力dcubeio
 
【ビズリーチ】プロダクトマネージャーの仕事と魅力
【ビズリーチ】プロダクトマネージャーの仕事と魅力【ビズリーチ】プロダクトマネージャーの仕事と魅力
【ビズリーチ】プロダクトマネージャーの仕事と魅力dcubeio
 
Python × Herokuで作る 雑談slack bot
Python × Herokuで作る 雑談slack botPython × Herokuで作る 雑談slack bot
Python × Herokuで作る 雑談slack botdcubeio
 
HR Tech x 機械学習 導入事例紹介
HR Tech x 機械学習 導入事例紹介HR Tech x 機械学習 導入事例紹介
HR Tech x 機械学習 導入事例紹介dcubeio
 
Scalaマクロ入門 bizr20170217
Scalaマクロ入門 bizr20170217 Scalaマクロ入門 bizr20170217
Scalaマクロ入門 bizr20170217 dcubeio
 
機械学習を支えるX86 64の拡張命令セットを読む会 20170212
機械学習を支えるX86 64の拡張命令セットを読む会 20170212機械学習を支えるX86 64の拡張命令セットを読む会 20170212
機械学習を支えるX86 64の拡張命令セットを読む会 20170212dcubeio
 

Mehr von dcubeio (20)

AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」
AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」
AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」
 
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
 
ビットコインとブロックチェーンを初めからていねいに(超基礎編)
ビットコインとブロックチェーンを初めからていねいに(超基礎編)ビットコインとブロックチェーンを初めからていねいに(超基礎編)
ビットコインとブロックチェーンを初めからていねいに(超基礎編)
 
20171206 d3 health_tech発表資料
20171206 d3 health_tech発表資料20171206 d3 health_tech発表資料
20171206 d3 health_tech発表資料
 
Go初心者がGoでコマンドラインツールの作成に挑戦した話
Go初心者がGoでコマンドラインツールの作成に挑戦した話Go初心者がGoでコマンドラインツールの作成に挑戦した話
Go初心者がGoでコマンドラインツールの作成に挑戦した話
 
初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)
初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)
初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)
 
BizReach x Marketo連携
BizReach x Marketo連携BizReach x Marketo連携
BizReach x Marketo連携
 
Kinesis Firehoseを使ってみた
Kinesis Firehoseを使ってみたKinesis Firehoseを使ってみた
Kinesis Firehoseを使ってみた
 
Apiドキュメンテーションツールを使いこなす【api blueprint編】
Apiドキュメンテーションツールを使いこなす【api blueprint編】Apiドキュメンテーションツールを使いこなす【api blueprint編】
Apiドキュメンテーションツールを使いこなす【api blueprint編】
 
春の脆弱性祭り 2017/06/13
春の脆弱性祭り 2017/06/13春の脆弱性祭り 2017/06/13
春の脆弱性祭り 2017/06/13
 
DynamoDBを導入した話
DynamoDBを導入した話DynamoDBを導入した話
DynamoDBを導入した話
 
すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー!
すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー! すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー!
すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー!
 
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
 
Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜
Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜
Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜
 
【freee】プロダクトマネージャーの仕事と魅力
【freee】プロダクトマネージャーの仕事と魅力【freee】プロダクトマネージャーの仕事と魅力
【freee】プロダクトマネージャーの仕事と魅力
 
【ビズリーチ】プロダクトマネージャーの仕事と魅力
【ビズリーチ】プロダクトマネージャーの仕事と魅力【ビズリーチ】プロダクトマネージャーの仕事と魅力
【ビズリーチ】プロダクトマネージャーの仕事と魅力
 
Python × Herokuで作る 雑談slack bot
Python × Herokuで作る 雑談slack botPython × Herokuで作る 雑談slack bot
Python × Herokuで作る 雑談slack bot
 
HR Tech x 機械学習 導入事例紹介
HR Tech x 機械学習 導入事例紹介HR Tech x 機械学習 導入事例紹介
HR Tech x 機械学習 導入事例紹介
 
Scalaマクロ入門 bizr20170217
Scalaマクロ入門 bizr20170217 Scalaマクロ入門 bizr20170217
Scalaマクロ入門 bizr20170217
 
機械学習を支えるX86 64の拡張命令セットを読む会 20170212
機械学習を支えるX86 64の拡張命令セットを読む会 20170212機械学習を支えるX86 64の拡張命令セットを読む会 20170212
機械学習を支えるX86 64の拡張命令セットを読む会 20170212
 

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