Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JavaからAkkaハンズオン

257 views

Published on

JJUG CCC Fall 2018 Akkaハンズオンの資料です。
Javaで小さなWebアプリケーションを作成します。

Published in: Technology
  • Be the first to comment

  • Be the first to like this

JavaからAkkaハンズオン

  1. 1. Copyright © 2018 TIS Inc. All rights reserved. JavaからAkkaハンズオン JJUG Fall 2018 前出祐吾 @yugolf JavaからAkkaハンズオンの準備⼿順 https://qiita.com/yugolf/items/701ba01ef283c868119a
  2. 2. Copyright © 2018 TIS Inc. All rights reserved. TIS株式会社  前出 祐吾 @yugolf 最近の研究テーマ  オープン環境で⾼可⽤システムどうやって構築する? 翻訳した本 連載  ThinkIT:メニーコア時代のパラダイム リアクティブシステムを知ろう 2 ⾃⼰紹介 エンジニア積極採⽤中
  3. 3. Copyright © 2018 TIS Inc. All rights reserved. 3 本⽇のアジェンダ 1.Akkaとは 2.Akkaで実装してみる ‣ゴール Akkaを使った実装の取っ掛かりを掴む • Akkaでどう実装するのか? • その後どう学んでいけばよいか?
  4. 4. Copyright © 2018 TIS Inc. All rights reserved. 4 教材 ✦here
  5. 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の探求 • クラウドへ
  6. 6. Copyright © 2018 TIS Inc. All rights reserved. Akkaとは 6 第1章 Akkaの紹介
  7. 7. Copyright © 2018 TIS Inc. All rights reserved. 7 Akkaとは 並⾏・分散システムを構築するためのツールキット • スケールアップ(並⾏)やスケールアウ ト(分散)の実現によるアプリケーショ ンの複雑化を抑⽌ • JavaとScalaのAPIを提供
  8. 8. Copyright © 2018 TIS Inc. All rights reserved. 8 単⼀のプログラミングモデルでスケールアップ・スケールアウト アクターモデルはメッセージの送受信を抽象化することにより、 実装とスレッドの数やサーバーの台数に対する結合度を下げる シングルスレッドで実⾏ 複数サーバーで実⾏ 逐次処理 並⾏処理 分散処理 アクターモデル マルチコアで実⾏ 位置透過性
  9. 9. Copyright © 2018 TIS Inc. All rights reserved. アクターモデル 9 第2章 最⼩のAkkaアプリケーション
  10. 10. Copyright © 2018 TIS Inc. All rights reserved. 10 アクターモデルとは 並⾏的に受信するメッセージに対する以下のふるまいを備える • アクターを作る • アクターにメッセージを送信する • メッセージを受信したときの動作を指定する トラディショナルモデルとの違い トラディショナルモデル アクターモデル 逐次実⾏が基本で部分的に並⾏処理を実装 本質的に並⾏
  11. 11. Copyright © 2018 TIS Inc. All rights reserved. 11 本質的に並⾏? お客さん チケット販売員 Buy Buy Buy メールボックス Akkaを使うことでアクターのプログラミングに集中できる • コンポーネント間のやり取りはメッセージで⾏う • 関数の応答を待つ必要はなく並⾏に処理される • アクターはメールボックスを持ち到着順に処理する
  12. 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. 13. Copyright © 2018 TIS Inc. All rights reserved. Akkaのアクター 13 第2章 最⼩のAkkaアプリケーション
  14. 14. Copyright © 2018 TIS Inc. All rights reserved. 14 トラディショナルスタイルとAkkaを4つのケースで⽐較 Akkaとは/概要/従来型との違い/アクターのプログ ラミングモデル/Akkaのアクター 1)データを保存して耐久性のあるものにしたい 2)インタラクティブな機能を実装したい 3)サービスを分離して疎結合にしたい 4)システム全体の障害を避けたい
  15. 15. Copyright © 2018 TIS Inc. All rights reserved. 15 1) データを耐久性のあるものにしたい インメモリーに状態を持ち、ロック制御なしに書き換える 1万円引落し 1万円引落し 残⾼ 1万円 ATM モバイル 1万円引落し 1万円引落し ロック ATM モバイル ⼝座 残⾼ 1万円 トラディショナル Akka ボトルネック
  16. 16. Copyright © 2018 TIS Inc. All rights reserved. 16 1) データを耐久性のあるものにしたい 状態をイベントとして永続化しておき(Akka Persistence)、 これらを再⽣することで状態を復元できる • 1万円引落 • 1万円引落 • 3万円預⼊ 「スケールアウトしやすいようステートレスに」、 から「ステートフルでスケーラブルに」 イベントの永続化 イベントの再⽣ 1万円引落し 1万円引落し ATM モバイル ⼝座 残⾼ 1万円 イベントソーシング
  17. 17. Copyright © 2018 TIS Inc. All rights reserved. 17 2) インタラクティブな機能を作りたい メッセージパッシングなので基本プッシュ ポーリング @Bob “こんにちは!” Alice Bob プッシュ Alice Bob チャットサーバー トラディショナル Akka
  18. 18. Copyright © 2018 TIS Inc. All rights reserved. チャットサーバー メンションサーバー - 時間的疎結合:メッセージの応答を待たない - 機能的疎結合:送信後どうなったか知らない - 位置的疎結合:別サーバーにいてもいい 18 3) サービスを分離して疎結合にしたい メッセージパッシングなので元々⾮同期で疎結合 • 密結合は複雑度をあげる • 変更容易=技術的負債になりづらい、ほどよい疎結合 「メンション」がメッセー ジを受信したときのふる まいを定義するだけ 「メンション」のふるまい が変わっても「会話」は影 響を受けない チャット エンキュー デキュー 会話 メンションMeメンション トラディショナル Akka
  19. 19. Copyright © 2018 TIS Inc. All rights reserved. 19 4) システム全体の障害を避けたい アクターでエラーが発⽣したらスーパーバイザーへ コーヒ1本 故障!! システムをダウンさせないよう、 すべての障害を想定しCatch ⾃販機でコーヒーを買う try { vendingMachine.buy() } catch () { // ⾃販機の故障など // 障害が発⽣したときの処理 } 障害制御はスーパーバイザーに任せる Dave ⾃販機 スーパーバイザー トラディショナル Akka - 障害制御においても疎結合 (⾃動販売機の故障はコーヒーを買った⼈では なく⾃動販売機の管理者が対処)
  20. 20. Copyright © 2018 TIS Inc. All rights reserved. 20 スーパーバイザーヒエラルキー https://doc.akka.io/docs/akka/2.5/general/supervision.html Akkaは適切な単位で障害制御を⾏うため、スーパーバイザーヒエラルキーを持つ
  21. 21. Copyright © 2018 TIS Inc. All rights reserved. 21 1)データを耐久性のあるものにしたい DB  インメモリーで状態を持ちイベントを永続化 2)インタラクティブな機能を実装したい ポーリング  メッセージパッシング 3)サービスを分離して疎結合にしたい キュー  アクター間は疎結合 4)システム全体の障害を避けたい 全障害シナリオをキャッチ  コンポーネント間で影響なし トラディショナルスタイルとAkkaを4つのケースで⽐較してみた
  22. 22. Copyright © 2018 TIS Inc. All rights reserved. 最小のAkkaアプリケーション 22 •クローンとビルドとインターフェイスのテスト •アプリケーションでのActorの探求 •クラウドへ
  23. 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. 24. Copyright © 2018 TIS Inc. All rights reserved. 24 プロジェクトの構成 プロジェクトの構造 リソース ソースコード テストコード ビルド成果物 Mavenのビルドファイル https://www.jetbrains.com/idea/download/
  25. 25. Copyright © 2018 TIS Inc. All rights reserved. 25 構築するアプリケーション • イベント⼀覧の参照 • チケットの購⼊ • イベントの作成 • イベントのキャンセル チケット販売サービス GoTicks.com イベント管理者 お客さん
  26. 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. 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. 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. 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. 30. Copyright © 2018 TIS Inc. All rights reserved. 30 >イベント⼀覧の取得 >「JJUG」を2枚購⼊ >イベント⼀覧の取得 >「JJUG」を8枚購⼊ >「JJUG」を2枚購⼊ >チケット10枚の「JJUG」イベントを作成 >チケット20枚の「ScalaMatsuri」イベントを作成 >「ScalaMatsuri」をキャンセル >イベント⼀覧の取得 チケット販売サービスを使ってみる イベント管理者 お客さん
  31. 31. Copyright © 2018 TIS Inc. All rights reserved. 最小のAkkaアプリケーション 31 •クローンとビルドとインターフェイスのテスト •アプリケーションでのActorの探求 •クラウドへ
  32. 32. Copyright © 2018 TIS Inc. All rights reserved. 32 ハンズオン⽤のブランチを取得 ワークショップ⽤ブランチの取得 $ git checkout handson
  33. 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. 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. 35. Copyright © 2018 TIS Inc. All rights reserved. Exercise 0. アクターシステムの生成 35 演習0.アクターシステムの⽣成
  36. 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. 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. 38. Copyright © 2018 TIS Inc. All rights reserved. Exercise 1. イベントの作成 38 演習1.イベントの作成
  39. 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. 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. 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. 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. 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. 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. 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. 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. 47. Copyright © 2018 TIS Inc. All rights reserved. Exercise 2. チケットの購入 演習2.チケットの購⼊
  48. 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. 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. 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. 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. 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. 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. 54. Copyright © 2018 TIS Inc. All rights reserved. 54 Exercise2のポイント • メッセージの転送 child.get().forward(new TicketSeller.Buy(getTickets.getTickets()), getContext()); ※⾃⾝ではなく、⾃⾝へメッセージを送ってきた⼈に直接返信してほしいときは forwardを使う
  55. 55. Copyright © 2018 TIS Inc. All rights reserved. Exercise 3. イベント一覧の取得 55 演習3.イベント⼀覧の取得
  56. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 66. Copyright © 2018 TIS Inc. All rights reserved. Exercise 4. イベントのキャンセル 66 演習4.イベントのキャンセル
  67. 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. 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. 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. 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. 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. 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. 73. Copyright © 2018 TIS Inc. All rights reserved. 73 Exercise4のポイント 応答が必要なメッセージ、⾃⾝へのメッセージ送信 アクターはPoisonPillメッセージを受信すると停⽌する self().tell(PoisonPill.getInstance(), self()); ※PoisonPillはActorを殺すための毒薬
  74. 74. Copyright © 2018 TIS Inc. All rights reserved. Exercise 5. APIに仕立てる(解説) 74
  75. 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. 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. 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. 78. Copyright © 2018 TIS Inc. All rights reserved. 最小のAkkaアプリケーション 78 •クローンとビルドとインターフェイスのテスト •アプリケーションでのActorの探求 •クラウドへ
  79. 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. 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配下のプロジェクトをプッシュする
  81. 81. Copyright © 2018 TIS Inc. All rights reserved. さいごに 81
  82. 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. 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
  84. 84. THANK YOU We are hiring!! いっしょにチャレンジしませんか?

×