More Related Content
Similar to JavaからAkkaハンズオン (20)
JavaからAkkaハンズオン
- 1. Copyright © 2018 TIS Inc. All rights reserved.
JavaからAkkaハンズオン
JJUG Fall 2018
前出祐吾 @yugolf
JavaからAkkaハンズオンの準備⼿順
https://qiita.com/yugolf/items/701ba01ef283c868119a
- 2. Copyright © 2018 TIS Inc. All rights reserved.
TIS株式会社
前出 祐吾 @yugolf
最近の研究テーマ
オープン環境で⾼可⽤システムどうやって構築する?
翻訳した本
連載
ThinkIT:メニーコア時代のパラダイム リアクティブシステムを知ろう
2
⾃⼰紹介
エンジニア積極採⽤中
- 3. Copyright © 2018 TIS Inc. All rights reserved. 3
本⽇のアジェンダ
1.Akkaとは
2.Akkaで実装してみる
‣ゴール
Akkaを使った実装の取っ掛かりを掴む
• Akkaでどう実装するのか?
• その後どう学んでいけばよいか?
- 5. Copyright © 2018 TIS Inc. All rights reserved. 5
⽬次
https://www.shoeisha.co.jp/book/detail/9784798153278
第1章 Akkaの紹介
第2章 最⼩のAkkaアプリケーション
第3章 アクターによるテスト駆動開発
第4章 耐障害性
第5章 Future
第6章 Akkaによるはじめての分散アプリケーション
第7章 設定とロギングとデプロイ
第8章 アクターの構造パターン
第9章 メッセージのルーティング
第10章 メッセージチャネル
第11章 有限状態マシンとエージェント
第12章 ストリーミング
第13章 システム統合
第14章 クラスタリング
第15章 アクターの永続化
第16章 パフォーマンスTips
第17章 Akkaのこれから
付録 AkkaをJavaから使う
TODAY
• 第2章 最⼩のAkkaアプリケーション
• クローンとビルドとインターフェイスのテスト
• アプリケーションでのActorの探求
• クラウドへ
- 7. Copyright © 2018 TIS Inc. All rights reserved. 7
Akkaとは
並⾏・分散システムを構築するためのツールキット
• スケールアップ(並⾏)やスケールアウ
ト(分散)の実現によるアプリケーショ
ンの複雑化を抑⽌
• JavaとScalaのAPIを提供
- 8. Copyright © 2018 TIS Inc. All rights reserved. 8
単⼀のプログラミングモデルでスケールアップ・スケールアウト
アクターモデルはメッセージの送受信を抽象化することにより、
実装とスレッドの数やサーバーの台数に対する結合度を下げる
シングルスレッドで実⾏ 複数サーバーで実⾏
逐次処理 並⾏処理 分散処理
アクターモデル
マルチコアで実⾏
位置透過性
- 10. Copyright © 2018 TIS Inc. All rights reserved. 10
アクターモデルとは
並⾏的に受信するメッセージに対する以下のふるまいを備える
• アクターを作る
• アクターにメッセージを送信する
• メッセージを受信したときの動作を指定する
トラディショナルモデルとの違い
トラディショナルモデル
アクターモデル
逐次実⾏が基本で部分的に並⾏処理を実装
本質的に並⾏
- 11. Copyright © 2018 TIS Inc. All rights reserved. 11
本質的に並⾏?
お客さん
チケット販売員
Buy
Buy
Buy
メールボックス
Akkaを使うことでアクターのプログラミングに集中できる
• コンポーネント間のやり取りはメッセージで⾏う
• 関数の応答を待つ必要はなく並⾏に処理される
• アクターはメールボックスを持ち到着順に処理する
- 12. Copyright © 2018 TIS Inc. All rights reserved. 12
もしあなたがErlangの世界のアクターだった場合
あなたは孤独な⼈間で、窓もない暗い部屋に座っていて、メール
ボックスにメッセージが届くのを待っている状態です。
そしてメッセージを受け取ると、それに対して特定の⽅法で反応し
ます:メッセージを受け取るときにお⾦を払い、誕⽣⽇カードには
「ありがとう(“Thank you”)」という⽂字で返事をし、理解出来な
い⽂字は無視するという具合です。
「すごいErlangゆかいに学ぼう!」より
https://www.ymotongpoo.com/works/lyse-ja/ja/01_introduction.html
- 13. Copyright © 2018 TIS Inc. All rights reserved.
Akkaのアクター
13
第2章 最⼩のAkkaアプリケーション
- 14. Copyright © 2018 TIS Inc. All rights reserved. 14
トラディショナルスタイルとAkkaを4つのケースで⽐較
Akkaとは/概要/従来型との違い/アクターのプログ
ラミングモデル/Akkaのアクター
1)データを保存して耐久性のあるものにしたい
2)インタラクティブな機能を実装したい
3)サービスを分離して疎結合にしたい
4)システム全体の障害を避けたい
- 15. Copyright © 2018 TIS Inc. All rights reserved. 15
1) データを耐久性のあるものにしたい
インメモリーに状態を持ち、ロック制御なしに書き換える
1万円引落し
1万円引落し
残⾼
1万円
ATM
モバイル
1万円引落し
1万円引落し
ロック ATM
モバイル
⼝座
残⾼
1万円
トラディショナル Akka
ボトルネック
- 16. Copyright © 2018 TIS Inc. All rights reserved. 16
1) データを耐久性のあるものにしたい
状態をイベントとして永続化しておき(Akka Persistence)、
これらを再⽣することで状態を復元できる
• 1万円引落
• 1万円引落
• 3万円預⼊
「スケールアウトしやすいようステートレスに」、
から「ステートフルでスケーラブルに」
イベントの永続化
イベントの再⽣
1万円引落し
1万円引落し
ATM
モバイル
⼝座
残⾼
1万円
イベントソーシング
- 17. Copyright © 2018 TIS Inc. All rights reserved. 17
2) インタラクティブな機能を作りたい
メッセージパッシングなので基本プッシュ
ポーリング
@Bob “こんにちは!”
Alice Bob
プッシュ
Alice Bob
チャットサーバー
トラディショナル Akka
- 18. Copyright © 2018 TIS Inc. All rights reserved.
チャットサーバー メンションサーバー
- 時間的疎結合:メッセージの応答を待たない
- 機能的疎結合:送信後どうなったか知らない
- 位置的疎結合:別サーバーにいてもいい
18
3) サービスを分離して疎結合にしたい
メッセージパッシングなので元々⾮同期で疎結合
• 密結合は複雑度をあげる
• 変更容易=技術的負債になりづらい、ほどよい疎結合
「メンション」がメッセー
ジを受信したときのふる
まいを定義するだけ
「メンション」のふるまい
が変わっても「会話」は影
響を受けない
チャット
エンキュー
デキュー
会話
メンションMeメンション
トラディショナル Akka
- 19. Copyright © 2018 TIS Inc. All rights reserved. 19
4) システム全体の障害を避けたい
アクターでエラーが発⽣したらスーパーバイザーへ
コーヒ1本
故障!!
システムをダウンさせないよう、
すべての障害を想定しCatch
⾃販機でコーヒーを買う
try {
vendingMachine.buy()
} catch () {
// ⾃販機の故障など
// 障害が発⽣したときの処理
}
障害制御はスーパーバイザーに任せる
Dave
⾃販機
スーパーバイザー
トラディショナル Akka
- 障害制御においても疎結合
(⾃動販売機の故障はコーヒーを買った⼈では
なく⾃動販売機の管理者が対処)
- 20. Copyright © 2018 TIS Inc. All rights reserved. 20
スーパーバイザーヒエラルキー
https://doc.akka.io/docs/akka/2.5/general/supervision.html
Akkaは適切な単位で障害制御を⾏うため、スーパーバイザーヒエラルキーを持つ
- 21. Copyright © 2018 TIS Inc. All rights reserved. 21
1)データを耐久性のあるものにしたい
DB インメモリーで状態を持ちイベントを永続化
2)インタラクティブな機能を実装したい
ポーリング メッセージパッシング
3)サービスを分離して疎結合にしたい
キュー アクター間は疎結合
4)システム全体の障害を避けたい
全障害シナリオをキャッチ コンポーネント間で影響なし
トラディショナルスタイルとAkkaを4つのケースで⽐較してみた
- 22. Copyright © 2018 TIS Inc. All rights reserved.
最小のAkkaアプリケーション
22
•クローンとビルドとインターフェイスのテスト
•アプリケーションでのActorの探求
•クラウドへ
- 23. Copyright © 2018 TIS Inc. All rights reserved. 23
クローンとビルド
• github.com からサンプルプロジェクトをクローン
$git clone https://github.com/yugolf/akka-in-action-java.git
$cd akka-in-action-java/chapter-up-and-running/
$mvn compile exec:exec
https://github.com/yugolf/akka-in-action-java/tree/master/chapter-up-and-running
• ビルドとアプリケーションの起動
Slf4jLogger: Slf4jLogger started
Main: start actor system: go-ticks
Main: Server online at http://0.0.0.0:5000
Main: Press RETURN to stop...
※Mavenがインストールされていない場合はmvnwを使⽤してください。
例) ./mvnw compile exec:exec
https://github.com/takari/maven-wrapper
※事前準備でクローン済の⼈はgit pull で最新を取得してください。
- 24. Copyright © 2018 TIS Inc. All rights reserved. 24
プロジェクトの構成
プロジェクトの構造
リソース
ソースコード
テストコード
ビルド成果物 Mavenのビルドファイル
https://www.jetbrains.com/idea/download/
- 25. Copyright © 2018 TIS Inc. All rights reserved. 25
構築するアプリケーション
• イベント⼀覧の参照
• チケットの購⼊
• イベントの作成
• イベントのキャンセル
チケット販売サービス
GoTicks.com
イベント管理者
お客さん
- 26. Copyright © 2018 TIS Inc. All rights reserved. 26
Web API確認のための準備
まずは、動かしてみましょう!
HTTPリクエストのツールをインストールしてアプリを起動
• HTTPリクエスト送信ツールのインストール
• HTTPie または Advanced REST client
- CLI: HTTPie (https://httpie.org/)
- GUI: Advanced REST client(Chrome Extensions)
https://chrome.google.com/webstore/detail/advanced-rest-
client/hgmloofddffdnphfgcellkdfbfbjeloo
$brew install httpie
- 27. Copyright © 2018 TIS Inc. All rights reserved. 27
HTTPieでリクエスト送信
• CLI: HTTPie
$http POST localhost:5000/events/JJUG/ tickets:=3
HTTP/1.1 201 Created
Content-Length: 27
Content-Type: application/json
Date: Sun, 02 Dec 2018 06:42:52 GMT
Server: GoTicks.com REST API
{
"name": "JJUG",
"tickets": 3
}
- 28. Copyright © 2018 TIS Inc. All rights reserved. 28
REST clientでリクエスト送信
• GUI: Advanced REST client(Chrome Extensions)
(1) (2)
(3)
(4)
(8)
http://localhost:5000/events/JJUG
(5)
(6)
(7)
{"tickets":3}
- 29. Copyright © 2018 TIS Inc. All rights reserved. 29
APIのエンドポイント
HTTPieコマンド
• イベント作成
• チケット購⼊
• イベント⼀覧取得
• イベント取得
• イベントキャンセル
$http POST localhost:5000/events/JJUG/ tickets:=3
$http GET localhost:5000/events
$http POST localhost:5000/events/JJUG/tickets tickets:=2
$http DELETE localhost:5000/events/JJUG/
機能 HTTPメソッド パス JSON
イベント作成 POST /events/<イベント名>/ {"tickets":<枚数>}
チケット購⼊ POST /events/<イベント名>/tickets/ {"tickets":<枚数>}
イベント⼀覧 GET /events/
イベント取得 GET /events/<イベント名>/
イベントキャンセル DELETE /events/<イベント名>/
$http GET localhost:5000/events/JJUG/
- 30. Copyright © 2018 TIS Inc. All rights reserved. 30
>イベント⼀覧の取得
>「JJUG」を2枚購⼊
>イベント⼀覧の取得
>「JJUG」を8枚購⼊
>「JJUG」を2枚購⼊
>チケット10枚の「JJUG」イベントを作成
>チケット20枚の「ScalaMatsuri」イベントを作成
>「ScalaMatsuri」をキャンセル
>イベント⼀覧の取得
チケット販売サービスを使ってみる
イベント管理者
お客さん
- 31. Copyright © 2018 TIS Inc. All rights reserved.
最小のAkkaアプリケーション
31
•クローンとビルドとインターフェイスのテスト
•アプリケーションでのActorの探求
•クラウドへ
- 32. Copyright © 2018 TIS Inc. All rights reserved. 32
ハンズオン⽤のブランチを取得
ワークショップ⽤ブランチの取得
$ git checkout handson
- 33. Copyright © 2018 TIS Inc. All rights reserved.
ActorSystem
“go-ticks”
33
クラス構成
Main
HTTP Server
run
new
create
create
create
Actor
BoxOffice
Actor
TicketSeller
HTTP Route
RestApi
- 34. Copyright © 2018 TIS Inc. All rights reserved.
Interface
ITicketSeller
Interface
IBoxOffice
34
アクターの関係
JJUG
Scala
Matsuri
boxOffice
create
Add/Buy/Cancel/…
Actor
BoxOffice
Actor
TicketSeller
- 35. Copyright © 2018 TIS Inc. All rights reserved.
Exercise 0. アクターシステムの生成
35
演習0.アクターシステムの⽣成
- 36. Copyright © 2018 TIS Inc. All rights reserved.
ActorSystem
“go-ticks”
36
アクターシステムの⽣成
アクターシステムの⽣成
Main
HTTP Server
HTTP Route
RestApi
Actor
BoxOffice
Actor
TicketSeller
run
new
create
create
create
- 37. Copyright © 2018 TIS Inc. All rights reserved. 37
「go-ticks」 ActorSystemを⽣成する
0.0.[Main] ActorSystemの⽣成
アプリケーションの実⾏
final ActorSystem system = ActorSystem.create("go-ticks");
$ mvn compile exec:exec
...
Slf4jLogger: Slf4jLogger started
Main: start actor system: go-ticks
Main: Server online at http://0.0.0.0:5000
Main: Press RETURN to stop...
※Mavenがインストールされていない場合はmvnwを使⽤してください。
例) ./mvnw compile exec:exec
https://github.com/takari/maven-wrapper
- 38. Copyright © 2018 TIS Inc. All rights reserved.
Exercise 1. イベントの作成
38
演習1.イベントの作成
- 39. Copyright © 2018 TIS Inc. All rights reserved. 39
HTTP Route
RestApi
Actor
BoxOffice
Actor
TicketSeller
CreateEvent
create
EventCreated
EventExists
POST
/events/JJUG
Created 201
BadRequest 400
Add
イベント作成時のメッセージの流れ
- 40. Copyright © 2018 TIS Inc. All rights reserved. 40
イベント作成時のメッセージの流れ:実装箇所
実装するところ
Actorの⽣成とメッセージ送信(Add/ EventCreated)
HTTP Route
RestApi
Actor
BoxOffice
Actor
TicketSeller
CreateEvent
create
AddEventCreated
EventExists
POST
/events/JJUG
Created 201
BadRequest 400
- 41. Copyright © 2018 TIS Inc. All rights reserved. 41
実装: アクターの⽣成・定義とメッセージ送信
演習1:TicketSellerアクターの⽣成、Addメッセージ
の送信、EventCreatedメッセージの返信
• TicketSellerアクターの⽣成
1.1.[TicketSeller] アクターのファクトリーメソッドを定義
1.2.[BoxOffice ] TicketSellerアクターの⽣成
• BoxOfficeアクターからTicketSellerアクターへAddメッセージを送信
1.3.[TicketSeller] メッセージプロトコルの定義
1.4.[TicketSeller] メッセージ受信時のふるまい定義
1.5.[BoxOffice ] Addメッセージの送信
• BoxOfficeからRestApiへEventCreatedメッセージを返信
1.6.[BoxOffice ] EventCreatedメッセージの返信
- 42. Copyright © 2018 TIS Inc. All rights reserved. 42
テストによる実⾏結果の確認
RestApiTest#testCreateEventの実⾏
$mvn test -Dtest=RestApiTest#testCreateEvent
...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.goticks.RestApiTest
Slf4jLogger: Slf4jLogger started
Slf4jLogger: Slf4jLogger started
RestApi: ---------- POST /events/RHCP/ {"tickets":3} ----------
BoxOffice: 📩 IBoxOffice.CreateEvent[name=RHCP,tickets=3]
TicketSeller: 📩 ITicketSeller.Add[tickets=[ITicketSeller.Ticket[id=1],
ITicketSeller.Ticket[id=2], ITicketSeller.Ticket[id=3]]]
RestApi: 📩 IBoxOffice.EventCreated[event=IBoxOffice.Event[name=RHCP,tickets=3]]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.634 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
...
※Windowsだと「📩 」が「?」で表⽰されます。
- 43. Copyright © 2018 TIS Inc. All rights reserved. 43
実装1/3 TicketSellerアクターの⽣成
1.1.[TicketSeller] アクターのファクトリーメソッドを定義
1.2.[BoxOffice ] TicketSellerアクターの⽣成
public static Props props(String event) {
return Props.create(TicketSeller.class, () -> new TicketSeller(event));
}
public class BoxOffice extends AbstractActor {
...
private ActorRef createTicketSeller(String name) {
return getContext().actorOf(TicketSeller.props(name), name);
}
BoxOffice TicketSeller
create
- 44. Copyright © 2018 TIS Inc. All rights reserved. 44
実装2/3 BoxOfficeからTicketSellerにAddメッセージを送信
1.3.[TicketSeller] メッセージプロトコルの定義
1.4.[TicketSeller] メッセージ受信時のふるまい定義
interface ITicketSeller {
class Add extends AbstractMessage {
private final List<Ticket> tickets;
public Add(List<Ticket> tickets) {
this.tickets = Collections.unmodifiableList(new ArrayList<>(tickets));
}
public List<Ticket> getTickets() {
return tickets;
}
}
private final List<Ticket> tickets = new ArrayList<>();
private void add(Add add) {
log.debug(msg, add);
tickets.addAll(add.getTickets());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(Add.class, this::add)
.build();
}
BoxOffice TicketSeller
Add
- 45. Copyright © 2018 TIS Inc. All rights reserved. 45
実装3/3 BoxOfficeからTicketSellerにAddメッセージを送信
1.5.[BoxOffice ] Addメッセージの送信
1.6.[BoxOffice ] EventCreatedメッセージの返信
private void createEvent(CreateEvent createEvent) {
log.debug(msg, createEvent);
Optional<ActorRef> child = getContext().findChild(createEvent.getName());
if (child.isPresent()) {
getContext().sender().tell(new EventExists(), self());
} else {
ActorRef ticketSeller = createTicketSeller(createEvent.getName());
List<TicketSeller.Ticket> newTickets =
IntStream.rangeClosed(1, createEvent.getTickets())
.mapToObj(ticketId -> (new TicketSeller.Ticket(ticketId)))
.collect(Collectors.toList());
ticketSeller.tell(new TicketSeller.Add(newTickets), getSelf());
getContext().sender().tell(new EventCreated(new Event(createEvent.getName(),
createEvent.getTickets())), getSelf());
}
}
BoxOffice TicketSeller
Add
RestApi BoxOffice
EventCreated
- 46. Copyright © 2018 TIS Inc. All rights reserved. 46
Exercise1のポイント
• Actorの⽣成
• メッセージの送信
• メッセージ受信時のふるまい
• メッセージプロトコルの定義
getContext().actorOf(TicketSeller.props(name), name);
eventTickets.tell(new TicketSeller.Add(newTickets), getSelf());
public static class Add{
private final List<Ticket> tickets;
public Add(List<Ticket> tickets) {
this.tickets = Collections.unmodifiableList(new ArrayList<>(tickets));
}
public List<Ticket> getTickets() {
return tickets;
}
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(Add.class, this::add)
.build();
public static Props props(String event) {
return Props.create(TicketSeller.class, () -> new TicketSeller(event));
}
- 47. Copyright © 2018 TIS Inc. All rights reserved.
Exercise 2. チケットの購入
演習2.チケットの購⼊
- 48. Copyright © 2018 TIS Inc. All rights reserved. 48
チケット購⼊時のメッセージの流れ
チケットを購⼊するフロー
GetTicketsPOST
/events/JJUG/tickets
Created 201
NotFound 404
HTTP Route
RestApi
Actor
BoxOffice
Actor
TicketSeller
Buy
Tickets
- 49. Copyright © 2018 TIS Inc. All rights reserved. 49
GetTickets
Buy
Tickets
POST
/events/JJUG/tickets
Created 201
NotFound 404
HTTP Route
RestApi
Actor
BoxOffice
Actor
TicketSeller
実装するところ
メッセージ送信(Buy/ Tickets)
チケット購⼊時のメッセージの流れ:実装箇所
- 50. Copyright © 2018 TIS Inc. All rights reserved. 50
実装: Buyメッセージの送信とTicketsメッセージの返信
• BoxOfficeからTicketSellerへBuyメッセージを送信
2.1.[TicketSeller] メッセージプロトコルの定義
2.2.[TicketSeller] メッセージ受信時のふるまい
- 送信元へTicketsメッセージの返信
2.3.[BoxOffice ] Buyメッセージの転送
※TicketSellerからのメッセージの返信先を⾃⾝(BoxOffice)ではなく、
RestApi(TicketSellerいメッセージを送った⼈)にしたい
2.4.[BoxOffice ] 空のTicketsメッセージを返信
- 51. Copyright © 2018 TIS Inc. All rights reserved. 51
チケット購⼊の動作確認
$mvn test -Dtest=RestApiTest#testBuy
...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.goticks.RestApiTest
Slf4jLogger: Slf4jLogger started
Slf4jLogger: Slf4jLogger started
RestApi: ---------- POST /events/RHCP/ {"tickets":3} ----------
BoxOffice: 📩 IBoxOffice.CreateEvent[name=RHCP,tickets=3]
TicketSeller: 📩 ITicketSeller.Add[tickets=[ITicketSeller.Ticket[id=1],
ITicketSeller.Ticket[id=2], ITicketSeller.Ticket[id=3]]]
RestApi: 📩 IBoxOffice.EventCreated[event=IBoxOffice.Event[name=RHCP,tickets=3]]
RestApi: ---------- POST /events/RHCP/tickets/ {"tickets":2} ----------
BoxOffice: 📩 IBoxOffice.GetTickets[event=RHCP,tickets=2]
TicketSeller: 📩 ITicketSeller.Buy[tickets=2]
RestApi: 📩 ITicketSeller.Tickets[event=RHCP,entries=[ITicketSeller.Ticket[id=1],
ITicketSeller.Ticket[id=2]]]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.577 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
...
テストによる実⾏結果の確認
RestApiTest#testBuyの実⾏
- 52. Copyright © 2018 TIS Inc. All rights reserved. 52
実装1/2 BoxOfficeからTicketSellerへBuyメッセージを送信
2.1.[TicketSeller] メッセージプロトコルの定義
2.2.[TicketSeller] メッセージ受信時のふるまい
- 送信元へTicketsメッセージの返信
interface ITicketSeller {
class Buy extends AbstractMessage {
private final int tickets;
public Buy(int tickets) {
this.tickets = tickets;
}
public int getTickets() {
return tickets;
}
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(Buy.class, this::buy)
.build();
BoxOffice TicketSeller
Buy
BoxOffice
Tickets
private final List<Ticket> tickets = new ArrayList<>();
private void buy(Buy buy){
log.debug(msg, buy);
if (tickets.size() >= buy.getTickets()) {
List<Ticket> entries = tickets.subList(0, buy.getTickets());
getContext().sender().tell(new Tickets(event, entries), getSelf());
entries.clear();
} else {
getContext().sender().tell(new Tickets(event), getSelf());
}
}
- 53. Copyright © 2018 TIS Inc. All rights reserved. 53
実装2/2 BoxOfficeからTicketSellerへBuyメッセージを送信
2.3.[BoxOffice ] Buyメッセージの転送
2.4.[BoxOffice ] 空のTicketsメッセージを返信
if (child.isPresent())
child.get().forward(new TicketSeller.Buy(getTickets.getTickets()), getContext());
else
getContext().sender().tell(new TicketSeller.Tickets(getTickets.getEvent()), getSelf());
BoxOffice TicketSeller
Buy
BoxOffice
Tickets
- 54. Copyright © 2018 TIS Inc. All rights reserved. 54
Exercise2のポイント
• メッセージの転送
child.get().forward(new TicketSeller.Buy(getTickets.getTickets()), getContext());
※⾃⾝ではなく、⾃⾝へメッセージを送ってきた⼈に直接返信してほしいときは
forwardを使う
- 55. Copyright © 2018 TIS Inc. All rights reserved.
Exercise 3. イベント一覧の取得
55
演習3.イベント⼀覧の取得
- 56. Copyright © 2018 TIS Inc. All rights reserved. 56
イベント取得時のメッセージの流れ
イベント⼀覧を取得するフロー
GetEventGET
/events/JJUG
OK 200 Events
HTTP Route
RestApi
Actor
BoxOffice
Actor
TicketSeller
empty
GetEvent
Event
- 57. Copyright © 2018 TIS Inc. All rights reserved. 57
イベント⼀覧取得時のメッセージの流れ
イベント⼀覧を取得するフロー
GetEventsGET
/events/
OK 200 Events
HTTP Route
RestApi
Actor
BoxOffice
Actor
TicketSeller
GetEvent
empty
GetEvent
Event
Events
- 58. Copyright © 2018 TIS Inc. All rights reserved. 58
GetEvents
GetEvent
Event
GET
/events/
OK 200 Events
GetEvent
empty
Events
HTTP Route
RestApi
Actor
BoxOffice
Actor
TicketSeller
イベント⼀覧取得時のメッセージの流れ:実装箇所
- 59. Copyright © 2018 TIS Inc. All rights reserved. 59
実装: GetEventメッセージの送信とEventsメッセージの返信
• TicketSellerにGetEventメッセージを送信する(準備)
3.1.[TicketSeller] メッセージプロトコルの定義
3.2.[TicketSeller] メッセージ受信時のふるまい
- Eventメッセージの返信
• BoxOfficeから⾃⾝にGetEventメッセージを送信
3.3.[BoxOffice ] ⾃分宛てにGetEventメッセージを送信
• BoxOfficeにGetEventメッセージ受信時のふるまいを定義
3.4.[BoxOffice ] ⼦アクターが存在する場合:
- ⼦アクター(TicketSeller)へGetEventメッセージを転送
3.5.[BoxOffice ] ⼦アクターが存在しない場合:
- Optional.empty()メッセージを返信
• BoxOfficeからEventsメッセージの返信
3.6.[BoxOffice ] Eventsメッセージを送信元へ返信
- 60. Copyright © 2018 TIS Inc. All rights reserved. 60
テストによる実⾏結果の確認
チケット⼀覧取得の動作確認
$mvn test -Dtest=RestApiTest#testGetEvents
...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.goticks.RestApiTest
Slf4jLogger: Slf4jLogger started
Slf4jLogger: Slf4jLogger started
RestApi: ---------- POST /events/RHCP/ {"tickets":3} ----------
BoxOffice: 📩 IBoxOffice.CreateEvent[name=RHCP,tickets=3]
TicketSeller: 📩 ITicketSeller.Add[tickets=[ITicketSeller.Ticket[id=1],
ITicketSeller.Ticket[id=2], ITicketSeller.Ticket[id=3]]]
RestApi: 📩 IBoxOffice.EventCreated[event=IBoxOffice.Event[name=RHCP,tickets=3]]
RestApi: ---------- GET /events/ ----------
BoxOffice: 📩 IBoxOffice.GetEvents[]
BoxOffice: 📩 IBoxOffice.GetEvent[name=RHCP]
TicketSeller: 📩 ITicketSeller.GetEvent[]
RestApi: 📩 IBoxOffice.Events[events=[IBoxOffice.Event[name=RHCP,tickets=3]]]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.186 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
RestApiTest#testGetEventsの実⾏
- 61. Copyright © 2018 TIS Inc. All rights reserved. 61
実装1/4 TicketSellerにGetEventメッセージを送信する(準備)
3.1.[TicketSeller] メッセージプロトコルの定義
3.2.[TicketSeller] メッセージ受信時のふるまい
- Eventメッセージの返信
@Override
public Receive createReceive() {
return receiveBuilder()
.match(GetEvent.class, this::getEvent)
.build();
}
class GetEvent extends AbstractMessage {
}
BoxOffice TicketSeller
Event
private void getEvent(GetEvent getEvent) {
log.debug(msg, getEvent);
sender().tell(Optional.of(new BoxOffice.Event(event, tickets.size())), self());
}
GetEvent
- 62. Copyright © 2018 TIS Inc. All rights reserved. 62
実装2/4 BoxOfficeから⾃⾝にGetEventメッセージを送信
GetEventメッセージを⾃⾝(self)に送信する
3.3.[BoxOffice ] GetEventメッセージを⾃分宛てに送信
// 子アクター(TicketSeller)に ask した結果のリストを作成
List<CompletableFuture<Optional<Event>>> children = new ArrayList<>();
getContext().getChildren().forEach(child ->
children.add(ask(getSelf(), new GetEvent(child.path().name()), timeout)
.thenApply(event -> (Optional<Event>) event)
.toCompletableFuture()));
BoxOffice
GetEvent
- 63. Copyright © 2018 TIS Inc. All rights reserved. 63
実装3/4 BoxOfficeにGetEventメッセージ受信時のふるまいを定義
3.4.[BoxOffice ] ⼦アクターが存在する:
- ⼦アクターへGetEventメッセージを転送
3.5.[BoxOffice ] ⼦アクターが存在しない:
- Optional.empty()を返信
private void getEvent(GetEvent getEvent) {
log.debug(msg, getEvent);
Optional<ActorRef> child = getContext().findChild(getEvent.getName());
if (child.isPresent())
child.get().forward(new TicketSeller.GetEvent(), getContext());
else
getContext().sender().tell(Optional.empty(), getSelf());
}
BoxOffice TicketSeller
GetEvent
Optional.empty()
- 64. Copyright © 2018 TIS Inc. All rights reserved. 64
実装4/4 BoxOfficeからEventsメッセージを返信
pipeを使って、処理完了時にFuture内の値(Events)
を送信元に返信する
3.6.[BoxOffice ] Eventsメッセージを送信元へ返信
private void getEvents(GetEvents getEvents) {
// 子アクター(TicketSeller)に ask した結果のリストを作成
List<CompletableFuture<Optional<Event>>> children = new ArrayList<>();
getContext().getChildren().forEach(child ->
children.add(ask(getSelf(), new GetEvent(child.path().name()), timeout)
.thenApply(event -> (Optional<Event>) event)
.toCompletableFuture()));
// List<CompletableFuture<Optional<Event>>> の children を
// CompletionStage<Events> に変換
// Events は List<Event> を持つ
CompletionStage<Events> futureEvents = CompletableFuture
.allOf(children.toArray(new CompletableFuture[0]))
.thenApply(ignored -> {
List<Event> events = children.stream()
.map(CompletableFuture::join)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
return new Events(events);
});
pipe(futureEvents, getContext().dispatcher()).to(sender());
}
RestApi BoxOffice
Events
- 65. Copyright © 2018 TIS Inc. All rights reserved. 65
Exercise3のポイント
応答が必要なメッセージ、⾃⾝へのメッセージ送信
レスポンスが必要な場合のメッセージ送信
⾃分宛てのメッセージ送信
ask(getSelf(), new GetEvent(child.path().name()), timeout)
.thenApply(event -> (Optional<Event>) event);
pipe(futureEvents, getContext().dispatcher()).to(sender());
ask(getSelf(), new GetEvent(child.path().name()), timeout)
※レスポンスがほしいときはaskを使う(使いすぎ注意)
※pipeでCompletableFuture完了時結果を返す
- 66. Copyright © 2018 TIS Inc. All rights reserved.
Exercise 4. イベントのキャンセル
66
演習4.イベントのキャンセル
- 67. Copyright © 2018 TIS Inc. All rights reserved. 67
イベントキャンセルのメッセージの流れ
イベントをキャンセルするフロー
CancelEvent
Cancel
Event
DELETE
/events/JJUG
OK 200
NotFound 404
empty
PoisonPill
HTTP Route
RestApi
Actor
BoxOffice
Actor
TicketSeller
- 68. Copyright © 2018 TIS Inc. All rights reserved. 68
CancelEvent
Cancel
Event
DELETE
/events/JJUG
empty
PoisonPill
HTTP Route
RestApi
Actor
BoxOffice
Actor
TicketSeller
イベントキャンセルのメッセージの流れ:実装箇所
実装するところ
メッセージ送信(Cancel/ Event)、アクターの停⽌
OK 200
NotFound 404
- 69. Copyright © 2018 TIS Inc. All rights reserved. 69
実装: Cancelメッセージの送信とTicketSellerアクターの停⽌
演習4:Cancelメッセージの送信とTicketSellerアク
ターの停⽌
BoxOfficeからTicketSellerへCancelメッセージを送信
4.1.[TicketSeller] メッセージプロトコルの定義
4.2.[TicketSeller] メッセージ受信時のふるまい
Eventメッセージの返信
⾃分宛てにPoisonPillメッセージの送信 ※毒薬を飲んで⾃殺
4.3.[BoxOffice ] ⼦アクターが存在する場合:
- ⼦アクター(TicketSeller)へCancelメッセージを転送
4.4.[BoxOffice ] ⼦アクターが存在しない場合:
- Optional.empty()メッセージを返信
- 70. Copyright © 2018 TIS Inc. All rights reserved. 70
テストによる実⾏結果の確認
$mvn test -Dtest=RestApiTest#testCancel
...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.goticks.RestApiTest
Slf4jLogger: Slf4jLogger started
Slf4jLogger: Slf4jLogger started
RestApi: ---------- POST /events/RHCP/ {"tickets":3} ----------
BoxOffice: 📩 IBoxOffice.CreateEvent[name=RHCP,tickets=3]
TicketSeller: 📩 ITicketSeller.Add[tickets=[ITicketSeller.Ticket[id=1],
ITicketSeller.Ticket[id=2], ITicketSeller.Ticket[id=3]]]
RestApi: 📩 IBoxOffice.EventCreated[event=IBoxOffice.Event[name=RHCP,tickets=3]]
RestApi: ---------- DELETE /events/RHCP/ ----------
BoxOffice: 📩 IBoxOffice.CancelEvent[name=RHCP]
TicketSeller: 📩 ITicketSeller.Cancel[]
RestApi: 📩 Optional[IBoxOffice.Event[name=RHCP,tickets=3]]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.679 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
RestApiTest#testCancelの実⾏
- 71. Copyright © 2018 TIS Inc. All rights reserved. 71
実装1/2 BoxOfficeからTicketSellerへCancelメッセージの送信
4.1.[TicketSeller] メッセージプロトコルの定義
4.2.[TicketSeller] メッセージ受信時のふるまい
- Eventメッセージの返信
- ⾃⾝にPoisonPillメッセージを送信する(PoisonPillを受信するとアクターは停⽌する)
class Cancel extends AbstractMessage {
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(Cancel.class, this::cancel)
.build();
}
private void cancel(Cancel cancel){
log.debug(msg, cancel);
sender().tell(Optional.of(new BoxOffice.Event(event, tickets.size())), self());
self().tell(PoisonPill.getInstance(), self());
}
BoxOffice TicketSeller
Event
Cancel PoisonPill
- 72. Copyright © 2018 TIS Inc. All rights reserved. 72
実装2/2 BoxOfficeからTicketSellerへCancelメッセージの送信
4.3.[BoxOffice ] ⼦アクターが存在する場合:
- ⼦アクター(TicketSeller)へCancelメッセージを転送
4.4.[BoxOffice ] ⼦アクターが存在しない場合:
- Optional.empty()メッセージを返信
private void cancelEvent(CancelEvent cancelEvent) {
log.debug(msg, cancelEvent);
Optional<ActorRef> child = getContext().findChild(cancelEvent.getName());
if (child.isPresent())
child.get().forward(new TicketSeller.Cancel(), getContext());
else
getContext().sender().tell(Optional.empty(), getSelf());
}
BoxOffice TicketSeller
Cancel
Optional.empty()
- 73. Copyright © 2018 TIS Inc. All rights reserved. 73
Exercise4のポイント
応答が必要なメッセージ、⾃⾝へのメッセージ送信
アクターはPoisonPillメッセージを受信すると停⽌する
self().tell(PoisonPill.getInstance(), self());
※PoisonPillはActorを殺すための毒薬
- 75. Copyright © 2018 TIS Inc. All rights reserved. 75
APIのエンドポイント(再掲)
HTTPieコマンド
• イベント作成
• チケット購⼊
• イベント⼀覧取得
• イベント取得
• イベントキャンセル
$http POST localhost:5000/events/JJUG/ tickets:=3
$http GET localhost:5000/events
$http POST localhost:5000/events/JJUG/tickets tickets:=2
$http DELETE localhost:5000/events/JJUG/
機能 HTTPメソッド パス JSON
イベント作成 POST /events/<イベント名>/ {"tickets":<枚数>}
チケット購⼊ POST /events/<イベント名>/tickets/ {"tickets":<枚数>}
イベント⼀覧 GET /events/
イベント取得 GET /events/<イベント名>/
イベントキャンセル DELETE /events/<イベント名>/
$http GET localhost:5000/events/JJUG/
- 76. Copyright © 2018 TIS Inc. All rights reserved. 76
エンドポイントの作成(Akka HTTP)
RestAPI#createRoute
import akka.http.javadsl.server.Route;
public Route createRoute() {
return route(
pathPrefix("events", () -> route(
getEvents(),
pathPrefix(segment(), (String name) -> route(
getEvent(name),
createEvent(name),
cancelEvent(name)
)),
pathPrefix(segment().slash(segment("tickets")), (String event) -> route(
requestTickets(event)
))
))
);
}
• AkkaHTTPが提供するDSLを使ってHTTPリクエストの制御⽅法を定義
- 77. Copyright © 2018 TIS Inc. All rights reserved. 77
RestApi#getEvents
private Route getEvents() {
// [Get all events] GET /events/
return get(() ->
pathEndOrSingleSlash(() -> {
log.debug("---------- GET /events/ ----------");
CompletionStage<Events> events =
ask(boxOfficeActor, new GetEvents(), timeout)
.thenApply((Events.class::cast));
return onSuccess(() -> events, maybeEvent -> {
log.debug(msg, maybeEvent);
return completeOK(maybeEvent, Jackson.marshaller());
});
})
);
}
エンドポイントの作成(Akka HTTP)
- 78. Copyright © 2018 TIS Inc. All rights reserved.
最小のAkkaアプリケーション
78
•クローンとビルドとインターフェイスのテスト
•アプリケーションでのActorの探求
•クラウドへ
- 79. Copyright © 2018 TIS Inc. All rights reserved. 79
クラウドへ1/2
Heroku上にアプリケーションを乗せる
• Mainクラスに標準⼊⼒を待ち受けサーバーをダウンさせる実装があるため
Heroku上で動かすにはこの部分を削除
$cd ..
$heroku login
$heroku create go-ticks
Creating ⬢ go-ticks... done
https://go-ticks.herokuapp.com/ | https://git.heroku.com/go-ticks.git
Heroku CLIのインストール
$brew install heroku
System.in.read();
log.info("presses return...");
binding
.thenCompose(ServerBinding::unbind)
.thenAccept(unbound -> system.terminate());
- 80. Copyright © 2018 TIS Inc. All rights reserved. 80
クラウドへ2/2
Herokuにソースコードをプッシュ
$git subtree push --prefix chapter-up-and-running heroku master
....
remote: -----> Compressing...
remote: Done: 73M
remote: -----> Launching...
remote: Released v3
remote: https://go-ticks.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/go-ticks.git
* [new branch] 0f4dac16e9abbdbb694502f21d314281c6affd66 -> master
$ http POST go-ticks.herokuapp.com/events/JJUG tickets:=250
$ http POST go-ticks.herokuapp.com/events/JJUG/tickets tickets:=4
Heroku上のアプリケーションにアクセス
※chapter-up-and-running配下のプロジェクトをプッシュする
- 82. Copyright © 2018 TIS Inc. All rights reserved. 82
さいごに
やったこと
• Akkaを使って⼩さなWebサービスを構築
残された課題
• アプリケーションを再起動するとデータは消える
• スケールアップもスケールアウトもしない
• テストを実装していない
• 1台のサーバーがダウンするとシステム全体がダウンする
Akkaのエコシステムで解決できるのでぜひチャレンジしてください。
https://akka.io/docs/
Akka
Actors Akka
Http Akka
Cluster Cluster
Sharing
Distributed
Data
Akka
Persistence
Alpakka
- 83. Copyright © 2018 TIS Inc. All rights reserved.
チャレンジのお供に
83
• メニーコア時代のパラダイム リアクティブシステムを知ろう(ThinkIT)
サンプルコードはScalaで書かれ
いますが、付録でJavaのAPIも
紹介しています
本⽇のサンプルアプリケーションのScala版はこちら
https://github.com/akka-ja/akka-in-action/tree/master/chapter-up-and-running