Suche senden
Hochladen
Play2 scalaを2年やって学んだこと
•
Als PPTX, PDF herunterladen
•
4 gefällt mir
•
3,727 views
dcubeio
Folgen
Play2 Scalaを2年やって学んだこと
Weniger lesen
Mehr lesen
Software
Melden
Teilen
Melden
Teilen
1 von 61
Jetzt herunterladen
Empfohlen
2023年はTiDBの時代!
2023年はTiDBの時代!
Tomotaka6
New Features of DotNet 6 Blazor WASM
New Features of DotNet 6 Blazor WASM
Shotaro Suzuki
3週連続DDDその1 ドメイン駆動設計の基本を理解する
3週連続DDDその1 ドメイン駆動設計の基本を理解する
増田 亨
RHEL on Azure、初めの一歩
RHEL on Azure、初めの一歩
Ryo Fujita
モノリスからマイクロサービスへの移行 ~ストラングラーパターンの検証~(Spring Fest 2020講演資料)
モノリスからマイクロサービスへの移行 ~ストラングラーパターンの検証~(Spring Fest 2020講演資料)
NTT DATA Technology & Innovation
概念モデリング再入門 + DDD
概念モデリング再入門 + DDD
Hiroshima JUG
コンテナ環境でJavaイメージを小さくする方法!
コンテナ環境でJavaイメージを小さくする方法!
オラクルエンジニア通信
WebAssemblyのWeb以外のことぜんぶ話す
WebAssemblyのWeb以外のことぜんぶ話す
Takaya Saeki
Empfohlen
2023年はTiDBの時代!
2023年はTiDBの時代!
Tomotaka6
New Features of DotNet 6 Blazor WASM
New Features of DotNet 6 Blazor WASM
Shotaro Suzuki
3週連続DDDその1 ドメイン駆動設計の基本を理解する
3週連続DDDその1 ドメイン駆動設計の基本を理解する
増田 亨
RHEL on Azure、初めの一歩
RHEL on Azure、初めの一歩
Ryo Fujita
モノリスからマイクロサービスへの移行 ~ストラングラーパターンの検証~(Spring Fest 2020講演資料)
モノリスからマイクロサービスへの移行 ~ストラングラーパターンの検証~(Spring Fest 2020講演資料)
NTT DATA Technology & Innovation
概念モデリング再入門 + DDD
概念モデリング再入門 + DDD
Hiroshima JUG
コンテナ環境でJavaイメージを小さくする方法!
コンテナ環境でJavaイメージを小さくする方法!
オラクルエンジニア通信
WebAssemblyのWeb以外のことぜんぶ話す
WebAssemblyのWeb以外のことぜんぶ話す
Takaya Saeki
ZabbixのAPIを使って運用を楽しくする話
ZabbixのAPIを使って運用を楽しくする話
Masahito Zembutsu
ゼロ幅スペースという悪夢
ゼロ幅スペースという悪夢
swamp Sawa
ブレソルでテラバイト級データのALTERを短時間で終わらせる
ブレソルでテラバイト級データのALTERを短時間で終わらせる
KLab Inc. / Tech
ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開
Yahoo!デベロッパーネットワーク
[D12] NonStop SQLって何? by Susumu Yamamoto
[D12] NonStop SQLって何? by Susumu Yamamoto
Insight Technology, Inc.
Cassandra における SSD の活用
Cassandra における SSD の活用
Yuji Ito
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
Koichiro Matsuoka
Rdra2.0 redmine
Rdra2.0 redmine
Zenji Kanzaki
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
Y Watanabe
世界最強のソフトウェアアーキテクト
世界最強のソフトウェアアーキテクト
Yahoo!デベロッパーネットワーク
Apache Kafka 0.11 の Exactly Once Semantics
Apache Kafka 0.11 の Exactly Once Semantics
Yoshiyasu SAEKI
忙しい人の5分で分かるMesos入門 - Mesos って何だ?
忙しい人の5分で分かるMesos入門 - Mesos って何だ?
Masahito Zembutsu
DeNAのサーバー"コード"レスアーキテクチャ
DeNAのサーバー"コード"レスアーキテクチャ
Haruto Otake
ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門
増田 亨
AWS Database Migration Service ご紹介
AWS Database Migration Service ご紹介
Amazon Web Services Japan
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したこと
BIGLOBE Inc.
ServiceとRepository
ServiceとRepository
シオリ ショウノ
ビジネスルールの複雑さに立ち向かう
ビジネスルールの複雑さに立ち向かう
増田 亨
【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 Strategy
Takeshi Fukuhara
Teclab3
Teclab3
Eikichi Yamaguchi
Djangoフレームワークの紹介
Djangoフレームワークの紹介
Shinya Okano
Weitere ähnliche Inhalte
Was ist angesagt?
ZabbixのAPIを使って運用を楽しくする話
ZabbixのAPIを使って運用を楽しくする話
Masahito Zembutsu
ゼロ幅スペースという悪夢
ゼロ幅スペースという悪夢
swamp Sawa
ブレソルでテラバイト級データのALTERを短時間で終わらせる
ブレソルでテラバイト級データのALTERを短時間で終わらせる
KLab Inc. / Tech
ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開
Yahoo!デベロッパーネットワーク
[D12] NonStop SQLって何? by Susumu Yamamoto
[D12] NonStop SQLって何? by Susumu Yamamoto
Insight Technology, Inc.
Cassandra における SSD の活用
Cassandra における SSD の活用
Yuji Ito
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
Koichiro Matsuoka
Rdra2.0 redmine
Rdra2.0 redmine
Zenji Kanzaki
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
Y Watanabe
世界最強のソフトウェアアーキテクト
世界最強のソフトウェアアーキテクト
Yahoo!デベロッパーネットワーク
Apache Kafka 0.11 の Exactly Once Semantics
Apache Kafka 0.11 の Exactly Once Semantics
Yoshiyasu SAEKI
忙しい人の5分で分かるMesos入門 - Mesos って何だ?
忙しい人の5分で分かるMesos入門 - Mesos って何だ?
Masahito Zembutsu
DeNAのサーバー"コード"レスアーキテクチャ
DeNAのサーバー"コード"レスアーキテクチャ
Haruto Otake
ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門
増田 亨
AWS Database Migration Service ご紹介
AWS Database Migration Service ご紹介
Amazon Web Services Japan
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したこと
BIGLOBE Inc.
ServiceとRepository
ServiceとRepository
シオリ ショウノ
ビジネスルールの複雑さに立ち向かう
ビジネスルールの複雑さに立ち向かう
増田 亨
【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 Strategy
Takeshi Fukuhara
Was ist angesagt?
(20)
ZabbixのAPIを使って運用を楽しくする話
ZabbixのAPIを使って運用を楽しくする話
ゼロ幅スペースという悪夢
ゼロ幅スペースという悪夢
ブレソルでテラバイト級データのALTERを短時間で終わらせる
ブレソルでテラバイト級データのALTERを短時間で終わらせる
ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開
[D12] NonStop SQLって何? by Susumu Yamamoto
[D12] NonStop SQLって何? by Susumu Yamamoto
Cassandra における SSD の活用
Cassandra における SSD の活用
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
Rdra2.0 redmine
Rdra2.0 redmine
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
世界最強のソフトウェアアーキテクト
世界最強のソフトウェアアーキテクト
Apache Kafka 0.11 の Exactly Once Semantics
Apache Kafka 0.11 の Exactly Once Semantics
忙しい人の5分で分かるMesos入門 - Mesos って何だ?
忙しい人の5分で分かるMesos入門 - Mesos って何だ?
DeNAのサーバー"コード"レスアーキテクチャ
DeNAのサーバー"コード"レスアーキテクチャ
ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門
AWS Database Migration Service ご紹介
AWS Database Migration Service ご紹介
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したこと
ServiceとRepository
ServiceとRepository
ビジネスルールの複雑さに立ち向かう
ビジネスルールの複雑さに立ち向かう
【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 Strategy
Ähnlich wie Play2 scalaを2年やって学んだこと
Teclab3
Teclab3
Eikichi Yamaguchi
Djangoフレームワークの紹介
Djangoフレームワークの紹介
Shinya Okano
Play meetup-2-dev-best-practices
Play meetup-2-dev-best-practices
k4200
Realmの暗号化とAndroid System
Realmの暗号化とAndroid System
Keiji Ariyama
JavaScript/CSS 2015 Autumn
JavaScript/CSS 2015 Autumn
Koji Ishimoto
CodeIgniterによるPhwittr
CodeIgniterによるPhwittr
kenjis
ピクサー USD 入門 新たなコンテンツパイプラインを構築する
ピクサー USD 入門 新たなコンテンツパイプラインを構築する
Takahito Tejima
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...
Shotaro Suzuki
Next2Dで始めるゲーム開発 - Game Development Starting with Next2D
Next2Dで始めるゲーム開発 - Game Development Starting with Next2D
Toshiyuki Ienaga
Azure IoT Edge で Custom Vision
Azure IoT Edge で Custom Vision
Yoshitaka Seo
Grafana Dashboards as Code
Grafana Dashboards as Code
Takuhiro Yoshida
初めての Data api
初めての Data api
Yuji Takayama
TypeScript と Visual Studio Code
TypeScript と Visual Studio Code
Akira Inoue
AngularJSとD3.jsによるインタラクティブデータビジュアライゼーション
AngularJSとD3.jsによるインタラクティブデータビジュアライゼーション
Yosuke Onoue
ScalaMatsuri 2016
ScalaMatsuri 2016
Yoshitaka Fujii
初めてのPadrino
初めてのPadrino
Takeshi Yabe
IDEを目指す開発者コンソール
IDEを目指す開発者コンソール
minoaw
d3jsハンズオン @E2D3ハッカソン
d3jsハンズオン @E2D3ハッカソン
圭輔 大曽根
復習も兼ねて!C#6.0-7.0
復習も兼ねて!C#6.0-7.0
Yuta Matsumura
Ähnlich wie Play2 scalaを2年やって学んだこと
(20)
Teclab3
Teclab3
Djangoフレームワークの紹介
Djangoフレームワークの紹介
Play meetup-2-dev-best-practices
Play meetup-2-dev-best-practices
Realmの暗号化とAndroid System
Realmの暗号化とAndroid System
JavaScript/CSS 2015 Autumn
JavaScript/CSS 2015 Autumn
CodeIgniterによるPhwittr
CodeIgniterによるPhwittr
ピクサー USD 入門 新たなコンテンツパイプラインを構築する
ピクサー USD 入門 新たなコンテンツパイプラインを構築する
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...
Next2Dで始めるゲーム開発 - Game Development Starting with Next2D
Next2Dで始めるゲーム開発 - Game Development Starting with Next2D
Azure IoT Edge で Custom Vision
Azure IoT Edge で Custom Vision
Grafana Dashboards as Code
Grafana Dashboards as Code
初めての Data api
初めての Data api
TypeScript と Visual Studio Code
TypeScript と Visual Studio Code
AngularJSとD3.jsによるインタラクティブデータビジュアライゼーション
AngularJSとD3.jsによるインタラクティブデータビジュアライゼーション
ScalaMatsuri 2016
ScalaMatsuri 2016
初めてのPadrino
初めてのPadrino
IDEを目指す開発者コンソール
IDEを目指す開発者コンソール
d3jsハンズオン @E2D3ハッカソン
d3jsハンズオン @E2D3ハッカソン
復習も兼ねて!C#6.0-7.0
復習も兼ねて!C#6.0-7.0
Mehr von dcubeio
AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」
AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」
dcubeio
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
dcubeio
ビットコインとブロックチェーンを初めからていねいに(超基礎編)
ビットコインとブロックチェーンを初めからていねいに(超基礎編)
dcubeio
20171206 d3 health_tech発表資料
20171206 d3 health_tech発表資料
dcubeio
Go初心者がGoでコマンドラインツールの作成に挑戦した話
Go初心者がGoでコマンドラインツールの作成に挑戦した話
dcubeio
初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)
初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)
dcubeio
BizReach x Marketo連携
BizReach x Marketo連携
dcubeio
Kinesis Firehoseを使ってみた
Kinesis Firehoseを使ってみた
dcubeio
Apiドキュメンテーションツールを使いこなす【api blueprint編】
Apiドキュメンテーションツールを使いこなす【api blueprint編】
dcubeio
春の脆弱性祭り 2017/06/13
春の脆弱性祭り 2017/06/13
dcubeio
DynamoDBを導入した話
DynamoDBを導入した話
dcubeio
すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー!
すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー!
dcubeio
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
dcubeio
Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜
Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜
dcubeio
【freee】プロダクトマネージャーの仕事と魅力
【freee】プロダクトマネージャーの仕事と魅力
dcubeio
【ビズリーチ】プロダクトマネージャーの仕事と魅力
【ビズリーチ】プロダクトマネージャーの仕事と魅力
dcubeio
Python × Herokuで作る 雑談slack bot
Python × Herokuで作る 雑談slack bot
dcubeio
HR Tech x 機械学習 導入事例紹介
HR Tech x 機械学習 導入事例紹介
dcubeio
Scalaマクロ入門 bizr20170217
Scalaマクロ入門 bizr20170217
dcubeio
機械学習を支えるX86 64の拡張命令セットを読む会 20170212
機械学習を支えるX86 64の拡張命令セットを読む会 20170212
dcubeio
Mehr von dcubeio
(20)
AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」
AWS Summit Tokyo 2019登壇資料「DevOpsの劇的改善!古いアーキテクチャから王道のマネージドサービスを活用しフルリプレイス! 」
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
ビットコインとブロックチェーンを初めからていねいに(超基礎編)
ビットコインとブロックチェーンを初めからていねいに(超基礎編)
20171206 d3 health_tech発表資料
20171206 d3 health_tech発表資料
Go初心者がGoでコマンドラインツールの作成に挑戦した話
Go初心者がGoでコマンドラインツールの作成に挑戦した話
初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)
初めての Raspberry pi 〜プラレールをunityの世界の中で走らせよう〜 (1)
BizReach x Marketo連携
BizReach x Marketo連携
Kinesis Firehoseを使ってみた
Kinesis Firehoseを使ってみた
Apiドキュメンテーションツールを使いこなす【api blueprint編】
Apiドキュメンテーションツールを使いこなす【api blueprint編】
春の脆弱性祭り 2017/06/13
春の脆弱性祭り 2017/06/13
DynamoDBを導入した話
DynamoDBを導入した話
すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー!
すごーい!APIドキュメントを更新するだけでAPIが自動テストできちゃう!たのしー!
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
20170329 D3 DBAが夜間メンテをしなくなった日 発表資料
Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜
Bitcoin x Slack でマイクロペイメントを実現! 〜生活の必要上割り勘botを作るまで〜
【freee】プロダクトマネージャーの仕事と魅力
【freee】プロダクトマネージャーの仕事と魅力
【ビズリーチ】プロダクトマネージャーの仕事と魅力
【ビズリーチ】プロダクトマネージャーの仕事と魅力
Python × Herokuで作る 雑談slack bot
Python × Herokuで作る 雑談slack bot
HR Tech x 機械学習 導入事例紹介
HR Tech x 機械学習 導入事例紹介
Scalaマクロ入門 bizr20170217
Scalaマクロ入門 bizr20170217
機械学習を支えるX86 64の拡張命令セットを読む会 20170212
機械学習を支えるX86 64の拡張命令セットを読む会 20170212
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.
ありがとうございました
Jetzt herunterladen