Slide 1

Slide 1 text

Phoenix.PubSub の紹介と活用を 考える 2021.10.26 fukuoka.ex#47 :Elixir お茶会〜Phoenix を学ぼう編

Slide 2

Slide 2 text

About Me @koga1020_ @koga1020 koga1020.com 👨‍💻 自己紹介 古賀 祥造(koga1020 ) 福岡在住のバックエンドエンジニア(最近はマネジメント寄り) fukuoka.ex 管理人 💡 最近の興味関心 Elixir ・Phoenix を使ったWeb アプリケーション開発 マイクロサービスの実現。実装パターンの学習 ユーザーも開発者もハッピーなプロジェクト・プロダクトマネジメント 美味しいご飯・美味しいお酒 🍶 マイホームで快適に過ごすこと 🏠 キャンプ・アウトドア用品 ⛺

Slide 3

Slide 3 text

アジェンダ PubSub パターンについて Phoenix.PubSub の導入 PubSub を動かしてみる ユースケースを考える

Slide 4

Slide 4 text

PubSub パターンについて

Slide 5

Slide 5 text

PubSub パターン 出版(Publish)- 購読(Subscribe) パターン Publisher は入力チャネルを介してメッセージを送信 Subscriber のためにメッセージを入力チャネルから出力チャネルにメッセージをコピー Subscriber は関心のあるメッセージを受け取る [1] 1. https://docs.microsoft.com/ja-jp/azure/architecture/patterns/publisher-subscriber

Slide 6

Slide 6 text

PubSub パターンの利点 Publisher とSubscriber を切り離して管理、疎結合にできる Publisher は、メッセージを送信するのみ それをどこの誰がSubscribe しているかは関心がない Publisher の処理を小さくできる(応答性の向上) 遅延処理やスケジュールされた処理も可能 詳しくは クラウド設計パターン - パブリッシャーとサブスクライバーのパターン 参照

Slide 7

Slide 7 text

Phoenix.PubSub を使ってPubSub を実装してみる

Slide 8

Slide 8 text

実装イメージ(再掲) 真ん中のブローカーがPhoenix.PubSub が担うイメージ 1. https://docs.microsoft.com/ja-jp/azure/architecture/patterns/publisher-subscriber ↩︎ [1]

Slide 9

Slide 9 text

Phoenix.PubSub の導入 project を作成した段階でsupervision tree に追加されている # Start the PubSub system {Phoenix.PubSub, name: Sample.PubSub}, # lib/sample/application.ex @impl true def start(_type, _args) do children = [ # Start the Ecto repository Sample.Repo, # Start the Telemetry supervisor SampleWeb.Telemetry, # Start the Endpoint (http/https) SampleWeb.Endpoint # Start a worker by calling: Sample.Worker.start_link(arg) # {Sample.Worker, arg} ] # See https://hexdocs.pm/elixir/Supervisor.html # for other strategies and supported options opts = [strategy: :one_for_one, name: Sample.Supervisor] Supervisor.start_link(children, opts) end

Slide 10

Slide 10 text

Subscriber を追加する "user_event" をsubsribe するSubsriber1 を作成 IO.puts "get message: #{inspect(message)} at #{__MODULE__}" # lib/sample/subscriber1.ex defmodule Sample.Subscriber1 do use GenServer def init(_) do Phoenix.PubSub.subscribe(Sample.PubSub, "user_event") {:ok, nil} end def start_link(opts) do GenServer.start_link(__MODULE__, opts) end def handle_info(message, state) do {:noreply, state} end end

Slide 11

Slide 11 text

Subscriber を追加する product_event をsubsribe するSubscriber2 を作成 Phoenix.PubSub.subscribe(Sample.PubSub, "product_event") # lib/sample/subscriber2.ex defmodule Sample.Subscriber2 do use GenServer def init(_) do {:ok, nil} end def start_link(opts) do GenServer.start_link(__MODULE__, opts) end def handle_info(message, state) do IO.puts "get message: #{inspect(message)} at #{__MODULE__}" {:noreply, state} end end

Slide 12

Slide 12 text

application tree に追加する def start(_type, _args) do children = [ # Start the Ecto repository Sample.Repo, # Start the Telemetry supervisor SampleWeb.Telemetry, # Start the PubSub system {Phoenix.PubSub, name: Sample.PubSub}, # Start the Endpoint (http/https) - SampleWeb.Endpoint + SampleWeb.Endpoint, # Start a worker by calling: Sample.Worker.start_link(arg) # {Sample.Worker, arg} + Sample.Subscriber1, + Sample.Subscriber2 ]

Slide 13

Slide 13 text

実行してみる user_event にはSubscriber1 が、product_event にはSubscriber2 がそれぞれ応答していることがわかる $ iex -S mix iex(2)> Phoenix.PubSub.broadcast(Sample.PubSub, "product_event", %{id: 456}) :ok get message: %{id: 456} at Elixir.Sample.Subscriber2 iex(1)> Phoenix.PubSub.broadcast(Sample.PubSub, "user_event", %{id: 123}) :ok get message: %{id: 123} at Elixir.Sample.Subscriber1

Slide 14

Slide 14 text

どんな使い方が考えられる?

Slide 15

Slide 15 text

Webhook e.g. GitHub をイメージしてみる branch が削除されたら、X に通知 issue にコメントがついたら、X とY に通知 …etc こんな感じで実装できそう? # Subsriber iex> PubSub.subscribe(Sample.PubSub, "repo_event:koga1020") # Publisher iex> PubSub.broadcast( ...> Sample.PubSub, ...> "repo_event:koga1020", ...> {:branch, :deleted, %{repo: "koga1020/sample", branch: "feature1", deleted_at: ~U[2021-10-24 14:06:23.666118Z]}} ...> )

Slide 16

Slide 16 text

CallBack のように使う ActiveRecord でいう after_update っぽく使う 「ユーザーデータが更新された」というイベントを配信する 保存後に追加で何を行うかはPublisher は関与しない def update_user(%User{} = user, attrs) do user |> User.changeset(attrs) |> Repo.update() |> tap( {:ok, user} -> Phoenix.PubSub.broadcast(Sample.PubSub, "user:#{user.id}", {:user_update}, user) error -> error end) end ` `

Slide 17

Slide 17 text

パターンの勘所 あるドメインイベントが発生したときに、そのイベントのデータを利用する コンシューマー(=Subscriber) が多い場合に有用 Channels のユースケース=PubSub のユースケースと解釈しても良さげ hexdocs の例: Chat rooms and APIs for messaging apps Breaking news, like "a goal was scored" or "an earthquake is coming" Tracking trains, trucks, or race participants on a map Events in multiplayer games Monitoring sensors and controlling lights Notifying a browser that a page’s CSS or JavaScript has changed (this is handy in development) [1] 1. https://hexdocs.pm/phoenix/channels.html

Slide 18

Slide 18 text

PubSub パターンが不適切な場合 https://docs.microsoft.com/ja-jp/azure/architecture/patterns/publisher-subscriber より 作成側アプリケーションの大きく異なる情報を必要とするコンシューマーが、数人だけしかいない場合。 アプリケーションが、ほぼリアルタイムの、コンシューマーとの対話を必要とする場合。

Slide 19

Slide 19 text

まとめ Phoenix.PubSub を使ったPubSub パターンの実装について紹介 プロジェクトを作成しただけでこの環境が整っているのは非常に便利 設計の勘所は必要そうだが、うまく使えると大きな武器になるかも?