Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Nostr REQ with Rx / Rx で REQ する Nostr
Search
penpenpng
August 04, 2023
Programming
15k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Nostr REQ with Rx / Rx で REQ する Nostr
penpenpng
August 04, 2023
More Decks by penpenpng
See All by penpenpng
How Skylight was created / Techniques for constructing URL Entity.
penpenpng
0
390
Nostr Web Client のための (Service) Worker 活用法
penpenpng
0
490
Other Decks in Programming
See All in Programming
When benchmarks go bad - what I learned from measuring performance wrong
hollycummins
0
190
Jetpack Compose Mechanisms
skydoves
2
610
複雑なUIコンポーネントとの ⻑い付き合い⽅:波及するバグからテスト戦略を経て、安全な再設計へ
mixplace
0
220
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Rails World 2026, Austin, Texas
marcoroth
0
190
動作中のプログラムの中身をリアルタイムに覗く / Realtime Debugger for CSharp with Roslyn
prota
1
1.8k
JAWS-UG 東京支部が始める、JAWS-UG支部コラボ / JAWS-UG lunchtime LT Collaboration
y0hgi
0
200
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
940
自サークルのIdPからログアウトできるようになるまで
taiseiue
1
120
【加筆修正版】Laravel のアプリケーションをどこにデプロイするか #phpcon_ehime
akase244
0
150
プロダクトコードからライブラリの境界を見つける
elmetal
PRO
0
110
setup-vp GitLab対応の裏側
naokihaba
0
150
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
1
350
Featured
See All Featured
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.5k
Git: the NoSQL Database
bkeepers
PRO
433
67k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
570
WCS-LA-2024
lcolladotor
0
860
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
3
3.9k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
42k
Into the Great Unknown - MozCon
thekraken
41
2.8k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
280
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.3k
Building a Scalable Design System with Sketch
lauravandoore
464
34k
The Art of Programming - Codeland 2020
erikaheidi
57
14k
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
3.3k
Transcript
ReactiveX で REQ する Nostr ぽーまん (@penpenpng) Nostr 勉強会 #3
Abstract • 真面目に REQ をハンドルするのはあまりにも面倒くさいので、 便利なライブラリが必要とされている ◦ そもそも REQ ってなんだっけ
◦ 何がそんなに面倒なんだっけ • ReactiveX と REQ の相性はいい ◦ なんで相性がいいの ◦ 面倒事をどう解消できるの • ReactiveX を利用して REQ をハンドルするライブラリを作ってみたよ ◦ 設計どうなってるの ◦ どんないいことがあるの
Abstract • 真面目に REQ をハンドルするのはあまりにも面倒くさいので、 便利なライブラリが必要とされている ◦ そもそも REQ ってなんだっけ?
◦ 何がそんなに面倒なんだっけ? • ReactiveX と REQ の相性はいい ◦ なんで相性がいいの? ◦ 面倒事をどう解消できるの? • ReactiveX を利用して REQ をハンドルするライブラリを作ってみたよ ◦ 設計どうなってるの? ◦ どんないいことがあるの?
Review: NIP-01
【復習】 EVENT / REQ ってなんだっけ EVENT = クライアントが生成し、リレーに送信する情報 (のうち、他のクライアントへの伝達を目的とするもの )
• プロフィール (kind0) • 投稿 (kind1) • リアクション (kind7) • Zap (kind9734, kind9735) REQ = クライアントがリレーに「EVENTが来たら教えてね」って言うやつ (EVENT の購読) クライアント リレー クライアント kind1 来たら教えてや ええで REQ kind1: ぽわ~ なんかきたで ぽわ~言うとるな
["REQ", "powa", { kinds: [1] }] 【復習】 Nostr の REQ
の仕組み クライアント リレー WebSocket ["REQ", "wayo", { kinds: [3] }] ["REQ", "moge", { kinds: [2] }] REQ 他所のクライアント 他所のクライアント 他所のクライアント ["EVENT", "powa", { ... }] ["EVENT", "powa", { ... }] ["EOSE", "powa"] ["EVENT", "powa", { ... }] ... ["EVENT", "moge", { ... }] ["EVENT", "moge", { ... }] ["EOSE", "moge"] ["EVENT", "wayo", { ... }] ["EVENT", "wayo", { ... }] ["EOSE", "wayo"]
Introduction: REQ is complicated
1 リクエスト : 非同期 N レスポンス 一般的な Web API コール
= 1 リクエスト : 非同期 1 レスポンス クライアント HTTPサーバ データちょうだい ちょい待ってな… ほいな Nostr の REQ = 1 リクエスト : 非同期 N レスポンス[1] クライアント リレー データちょうだい ちょい待ってな… ほいな あ、まだある これも これも これも入荷したで async function request(params) {/* ... */} /* は? */ [1]「それは async iterable を返せばいいのでは?」と思ったあなたは鋭いですが、次のスライドで述べる事情等によりここではすっとぼけています。
リクエストは投げっぱなしではない リクエストに対して「追加の非同期な操作」 (=CLOSE) が発生しうる クライアント リレー データちょうだい ちょい待ってな… ほいな あ、まだある
これも これも これも入荷したで もうええわ (CLOSE) • 同じ subId を異なる filter の REQ で上書きする操作も見ようによっては「追加の非同期な操作」 • REQ とは、非同期 N パラメータと非同期 M レスポンスから成る、本質的に時系列的な操作
「多重化した伝送路が多重に存在する」というあまりにもややこしい構造 クライアント リレー WebSocket REQ 多重化した伝送路 クライアント リレー リレー リレー
…が多重に存在する • 定義上の REQ とはこの図中の1本の細い線であるわけだが、 アプリケーションが求める "REQ" とは多重化した3本の細い線である • 当然ながら「追加の非同期な操作」も多重化している
再接続処理という鬼門 • リレーとの WebSocket 通信は案外安定しないので、再接続が頻繁に発生する • ところで、伝送路が失われると、それに紐づいた REQ 仮想伝送路も当然失われる クライアント
リレー リレー リレー • WebSocket 再接続時には、伝送路に紐づく REQ をすべて再構成する必要がある • 再構成 = REQ メッセージの再送 とは限らない ◦ `since: now` を再送したとして、それはアプリケーションの意図通りであろうか?
「リレー設定を変更する」という頭痛の種 クライアント リレー リレー リレー リレー 消します 足します • 接続先リレーを変更したとき、REQ
は「自然に」維持される必要がある • 再接続のときと同様に、自然な再構成とは REQ メッセージの再送そのものではない
サブスクリプション上限という低すぎる天井 ひとつの伝送路に入れることができる REQ の上限は決まっている (大体10前後のことが多い) クライアント リレー WebSocket REQ 3つまでしか入らないよ!
すなわち、REQ の待ち行列を用意しなければならない REQ 待機所 ⋮
なんでこんなに面倒なのか • 「ライブラリがないから」に尽きる ◦ こんなわけわからん通信方式の先例はないので、ライブラリはない ◦ REST API などのありふれた方式にも一定の面倒はあるものだが、 ライブラリがそれらのほとんどを隠蔽する
◦ ライブラリがないので、先述の面倒事をすべてアプリケーションプログラマが 捌かなければならない • 本当にまったくない? ◦ nostr-tools があるといえばあるが… ◦ nostr-fetch はある ▪ 過去のイベントを取得する限りにおいて極めて自然なインターフェースを提供する • REQ Subscription モデルが表現するすべてのユースケースをカバーできるような 便利なインターフェースがほしい
では、求められるインターフェースとは? • 少なくとも 1 リクエスト : 非同期 N レスポンス を表現できることが求められる
• ところで、こちらの表をご覧ください: 同期的と読み替えてもいい 非同期的と読み替えてもいい
では、求められるインターフェースとは? ref. https://rxjs.dev/guide/observable
ReactiveX (Rx)
[2] https://reactivex.io/languages.html ReactiveX (Rx) ってなんだ • ReactiveX (Rx) は Observer
Pattern を生かした API の定義の集合 ◦ 具体的な実装は言語ごとにライブラリとして存在する ▪ JavaScript, Java, C#, Scala, Clojure, C++, Lua, Ruby, Python, Go, Groovy, JRuby, Kotlin, Swift, PHP, Elixir, Dart[2] ◦ ある程度は移植性があると言ってもいい • RxJS は JavaScript における Rx の実装 • 特徴的な点は: ◦ Observable (Subject を示す Rx に特有の言い回し) をストリーム[3]とみなす点 ◦ ストリームを演算するための Operator という概念 ▪ 高次 Observable すら簡単に取り回すことができる • と、言葉で言われてもピンと来ないと思うので次のスライドで例を見ていきます [3] 公式の用語ではない。公式ドキュメントでは observable sequences と表現される概念について、ストリーム的な側面をより強調したい意図がある。
ものごとをストリームとみなす コードの引用: https://rxjs.dev/guide/overview • 「なんか (=クリックイベント) あったらこれ (下線部)を 実行しておいて」という何の変哲もないコード •
ここで「なんか」を可視化する図を書いてみると … ◦ 「なんか」(=丸印) がどこからともなく現れ 下線部に到達したらコールバックが実行される …と思える
ストリームを RxJS で表現する Observer Observable コードの引用: https://rxjs.dev/guide/overview • RxJS を使って同等のコードを書いたものがこちら
• Rx では、この青いストリームを Observable、赤いコンシューマを Observer と呼んでいる • 先のコードとの決定的な違いはコードの 中の `fromEvent()` がストリームそのものであるところ ◦ 先のコードではストリームと 思える概念がコードの外にあっただけ
コード上にストリームを表現できると何が嬉しいのか • ずばり、ストリームを演算できるようになる点がうれしい ◦ f(observable) なるシグネチャを持つ関数 f を考えられるようになる (Operator) ◦
実際には f(g(h(observable))) のような演算が頻発するので、 pipe() による演算を行うことになる
Operator による演算の例 1 - map() map() の Marble diagram https://rxjs.dev/api/index/function/map
Operator による演算の例 2 - mergeMap() mergeMap() の Marble diagram https://rxjs.dev/api/operators/mergeMap
Operator による演算の例 3 - switchAll() switchAll() の Marble diagram https://rxjs.dev/api/operators/switchAll
Operator を「ゲート」とみなす • Operator の作用は今まで見てきたように Marble diagram で表現するのが一般的 • …なのだが、今後のスライドでは画面面積の都合で「ゲート」記法を導入することにする
map(x => x*3) filter(x => x%2 === 0) delay(1000) これは左から整数を入れると、 ◦ 3倍されて、 ◦ 奇数は捨てて、 ◦ 残った整数を 1000 ミリ秒後に排出する ような (あまり意味のない) Observable[4] になる [4] 左からものを入れられるので、正確に言えば Subject となる 整数 整数
Applying ReactiveX to REQ
REQ Subscription をモデリングする • 素朴に考えると REQ Subscription は Subject でモデリングできる
◦ Subject は手動で値を「流す」ことができる Observable リレーに投げる リレーから受け取る REQ EVENT/EOSE リレー • pros ◦ 「非同期 N パラメータ / 非同期 M レスポンス」の構造は既に表現できている • cons ◦ subId をいちいち指定するのが面倒くさい ◦ 適当なタイミングで CLOSE を投げる (= unsubscribe する) のが面倒くさい ▪ アプリケーションロジックは subId の具体値にも CLOSE タイミングのフルコントロールにも (大抵は) 興味はない
素朴 REQ モデルの問題点に関する考察 subId をいちいち指定するのが面倒くさい 意味のある subId の選び方は実のところ 2 通りしかない
• まったく新しい subId を生成する • 過去のいずれかの REQ と同じ subId を使う 適当なタイミングで CLOSE を投げるのが面倒くさい CLOSE を投げたくなるタイミングは大抵が次の 3 通り • EOSE を受け取った • 新しい REQ を発行したので、古い REQ がいらなくなった • 欲しい情報が手に入った ◦ これはパターン化できないので手動でやってもらうのが現実的
subId と CLOSE に関する考察 EOSE 新しい REQ ユニーク値 過去のREQと同じ CLOSE
のタイミング subId • 表を使って整理していく
subId と CLOSE に関する考察 EOSE 新しい REQ ユニーク値 過去のREQと同じ CLOSE
のタイミング subId • 赤マスに注目 ◦ 「過去の REQ と同じ subId を持ち、EOSE が来たときに CLOSE する REQ 」 ◦ ⇔ 「既にある REQ をキャンセルしながら、過去の EVENT を収集する REQ」 ◦ 既にある REQ = 同種の REQ = 同じマスに属する REQ という仮定をおく
subId と CLOSE に関する考察 EOSE 新しい REQ ユニーク値 ◯ 過去のREQと同じ
↑ CLOSE のタイミング subId • 赤マスに注目 ◦ 「過去の REQ と同じ subId を持ち、EOSE が来たときに CLOSE する REQ 」 ◦ ⇔ 「既にある REQ をキャンセルしながら、過去の EVENT を収集する REQ」 ◦ 既にある REQ = 同種の REQ = 同じマスに属する REQ という仮定をおく • 「既にある REQ」もどうせ EOSE が来たら止まる。わざわざキャンセルする必要性は薄い ◦ ⇒ 青マスで代替可能
subId と CLOSE に関する考察 EOSE 新しい REQ ユニーク値 ◯ 過去のREQと同じ
↑ CLOSE のタイミング subId • 赤マスに注目 ◦ 「ユニークな subId を持ち、新しい REQ が発行されたときに CLOSE する REQ 」 ◦ ⇔ 「既にある REQ を邪魔せずに、新しい REQ が発行されたときに CLOSE する REQ」 ◦ 例によって「既にある REQ は同種の REQ」という仮定をおく
subId と CLOSE に関する考察 EOSE 新しい REQ ユニーク値 ◯ 過去のREQと同じ
↑ CLOSE のタイミング subId • 赤マスに注目 ◦ 「ユニークな subId を持ち、新しい REQ が発行されたときに CLOSE する REQ 」 ◦ ⇔ 「既にある REQ を邪魔せずに、新しい REQ が発行されたときに CLOSE する REQ」 ◦ 例によって「既にある REQ は同種の REQ」という仮定をおく • 「既にある REQ を邪魔しない」と「新しい REQ が発行されたときに CLOSE する」は矛盾 ◦ 意味のない組み合わせ
subId と CLOSE に関する考察 EOSE 新しい REQ ユニーク値 いらない 過去のREQと同じ
いらない CLOSE のタイミング subId
subId と CLOSE に関する考察 EOSE 新しい REQ ユニーク値 過去の EVENT
に興味がある いらない 過去のREQと同じ いらない CLOSE のタイミング subId • EOSE のタイミングで CLOSE する ◦ ⇒ 過去の EVENT にしか興味がない
subId と CLOSE に関する考察 EOSE 新しい REQ ユニーク値 過去の EVENT
に興味がある いらない 過去のREQと同じ いらない 未来の EVENT に興味がある CLOSE のタイミング subId • EOSE のタイミングで CLOSE する ◦ ⇒ 過去の EVENT にしか興味がない • CLOSE のタイミングが EOSE に依らない ◦ ⇒ 特定の条件に合致する EVENT にのみ興味があるか、未来の EVENT に興味がある ◦ ⇒ 未来の EVENT にしか興味がない ▪ ……と乱暴にまとめてしまってもいい。なぜなら「特定の条件に合致する EVENT」 が過去にあるか未来にあるかに応じて戦略を選んでもらえばいいから
subId と CLOSE に関する考察 EOSE 新しい REQ ユニーク値 過去の EVENT
に興味がある いらない 過去のREQと同じ いらない 未来の EVENT に興味がある CLOSE のタイミング subId • まとめると、以上の青マスに対応する 2 つの戦略を用意しておけば、 アプリケーションプログラマは subId と CLOSE の管理から解放される • 以後、この 2 つの戦略をそれぞれ Backward Strategy / Forward Strategy と呼ぶことにする
Backward Strategy 投げる 受け取る filters EVENT リレー REQ に変換 投げる
受け取る REQ EVENT/EOSE リレー 素朴 REQ モデル Backward Strategy ユニークな subId を付与 入力と出力の型が整理されてうれしいの図 その 1 CLOSE
Forward Strategy 投げる 受け取る filters EVENT リレー REQ に変換 投げる
受け取る REQ EVENT/EOSE リレー 素朴 REQ モデル Forward Strategy 固定値の subId を付与 (CLOSE の役割も担う) 入力と出力の型が整理されてうれしいの図 その 2 CLOSE 読み捨て
アプリケーションから見ると filters EVENT リレー なんやかんやいい感じにする ☑ 解決: subId をいちいち指定するのが面倒くさい ☑
解決: 適当なタイミングで CLOSE を投げる (= unsubscribe する) のが面倒くさい
これでオールハッピーだったんだっけ? • サブスクリプション上限のケア • 再接続処理 • エラーハンドリング • 「リレー設定を変更する」のハンドリング 🫠
…… が こちらを みている!
Solution: Case rx-nostr
全体構成 • RxReq, RxNostr という2種類のオブジェクトを定める • RxReq ◦ Strategy を定める
◦ アプリケーションから filter を受け入れる • RxNostr ◦ リレーたちとの通信をなんやかんやいい感じにする ◦ アプリケーションに EVENT を返す filters EVENT リレー なんやかんやいい感じにする RxReq RxNostr Application Logic リレー リレー filters
全体構成 • 内部オブジェクトとして、Connection を定める ◦ ひとつのリレーに対する伝送路と、その中の REQ 仮想伝送路の管理に責任を持つ ◦ 部分解決:
再接続処理 ◦ 解決: サブスクリプション上限のケア EVENT Connection なんやかんやいい感じにする RxReq RxNostr Application Logic Connection Connection リレー リレー リレー filters filters
再接続処理についてもっと詳しく • filter の `since` と `until` に関数を許す lazy filter
を定める ◦ 他のフィルター項目に関しては未対応 ◦ `since: () => Math.floor(new Date().getTime() / 1000)` とすればいつでも新鮮な タイムラインが手に入る ◦ 解決: 再接続処理 EVENT Connection なんやかんやいい感じにする RxReq RxNostr Application Logic Connection Connection リレー リレー リレー lazy filters lazy filters lazy filter は ここで評価されて filter になる
異常切断の検知 • ところで、WebSocket が切断された理由がリレー起因なのかを正確に知る方法はない ◦ RFC-6455 は Close frame におけるステータスコードを定めてはいる[5]が、
リレー実装がどれを返すのかはわからない (返すかすらわからない) ◦ 切断の理由がリレー実装レイヤーにあるとも限らない ◦ リレーが "行儀よく" コネクションを閉じたときは `websocket.onerror` は呼ばれない ▪ onerror が呼ばれる厳密な条件がわからない。誰か教えてほしい [5] https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
異常切断の検知 • 4000 番台のコードはアプリケーションの都合で好き勝手使っていいことになっている ◦ 実際、Nostr Protocol は "4000" を
"Don't Retry" の意味で使うよう定めている ◦ rx-nostr はこれを遵守するので 4000 を返されたときは静かに死ぬ • rx-nostr が自分自身の都合で WebSocket を切断するすべてのケースにコードをあててしまえば それ以外のコードをもって閉じられた場合を異常切断とみなすことができる ◦ 4537 と 4538 を rx-nostr 自身の都合のために定義して使っている ◦ leet 的なアレで番号を決めた気がしたが、由来は忘れた。もう誰にもわからない
エラーハンドリング • Rx の Observable にはストリーム上で「エラー」を返す仕組みがあるが、 一度エラーを返したストリームは終了してしまう • 「多重化した伝送路が多重に存在する」ので、エラーは複数回投げられる可能性がある •
⇒ エラー情報を別の Observable にまとめ上げて提供する
エラーハンドリング • 解決: エラーハンドリング
「リレー設定を変更する」と闘う EVENT Connection なんやかんやいい感じにする RxReq RxNostr Application Logic Connection Connection
リレー リレー リレー lazy filters lazy filters リレー設定が変更されるたびにここを筋肉で書き換える 解決💪: 「リレー設定を変更する」のハンドリング
Appendix: rx-nostr's code example
Operator is 便利 重複排除 可能な範囲でソート EVENT EVENT 署名を検証
送信もできるよ
Summary
おつノス! • 真面目に REQ をハンドルするのはあまりにも面倒くさいので、 便利なライブラリが必要とされている • ReactiveX (Rx) と
REQ の相性はいい • Rx を利用して REQ をハンドルするライブラリを作ってみたよ