Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Node-AI のリッチな WEB フロントエンドを支える技術

neno
March 10, 2024

Node-AI のリッチな WEB フロントエンドを支える技術

neno

March 10, 2024
Tweet

More Decks by neno

Other Decks in Technology

Transcript

  1. 自己紹介 1 • 所属: NTTコミュニケーションズ イノベーションセンター • 趣味: C#, OSS,

    ドール, 一眼(α7 IV), 映画館 • 執心領域 • C# ⇔ TypeScript • SignalR 何縫ねの。 nenoNaninu nenoMake ブログ https://blog.neno.dev その他 https://neno.dev
  2. OSS 紹介 2 属性を付与するだけ Tapper • C# の型定義から TypeScript の型定義を生成する

    .NET Tool/ library • JSON / MessagePack 対応! https://github.com/nenoNaninu/Tapper
  3. OSS 紹介 3 • C# の SignalR Client を強く型付けするための Source

    Generator TypedSignalR.Client Before After (using TypedSignalR.Client) こんな SignalR の Hub と Receiver の interface が あったとして… 脱文字列! 全てが強く型付け! https://github.com/nenoNaninu/TypedSignalR.Client
  4. 4 • TypeScript の SignalR Client を強く型付けするための .NET Tool /

    library TypedSignalR.Client.TypeScript Before After (using TypedSignalR.Client.TypeScript) 脱文字列! 全てが強く型付け! TypeScript 用の型を C# から自動生成 MessagePack Hub Protocol 対応! https://github.com/nenoNaninu/TypedSignalR.Client.TypeScript 属性を付与するだけ! OSS 紹介
  5. 5 • SignalR 使ったアプリを快適に開発するための GUI を自動生成する library • 2 step

    で利用可能! • http pipeline に middleware の追加 • Hub と Receiver を定義してる interface に属性を付与 • JWT 認証 サポート • パラメータのユーザ定義型サポート • JSON で入力! SignalR 版 SwaggerUI TypedSignalR.Client.DevTools https://github.com/nenoNaninu/TypedSignalR.Client.DevTools OSS 紹介
  6. AspNetCore.SignalR.OpenTelemetry OSS 紹介 6 • トレースのための計装 • 最低限のログ • 接続時

    • Transport 層の情報も出力(WebSocket 等) • メソッド呼び出し時 • HubName.MethodName の素朴なログ • メソッド呼び出し毎にログのスコープを追加 • HubName, MethodName, InvocationId を 振っているのでログの検索性が向上 • Duration • 切断時 • 切断時に例外が発生していれば例外もログに出力 Inspired by HttpLogging SignalR のメソッド呼び出し毎に スパンが切られるように https://github.com/nenoNaninu/AspNetCore.SignalR.OpenTelemetry
  7. Node-AI とは 9 • NTT Com の Engineers’ Blog にいろいろ書いています

    • https://engineers.ntt.com/entry/2024/02/19/055601 フロントエンドを Vue.js 2.x から React にリプレイスしました。
  8. キャンバスの画的な表現 12 • 普通の(?) HTML • <div> とか • Canvas

    element (<canvas>) • SVG element (<svg>) • Scalable Vector Graphics どう表現するか?
  9. キャンバスの画的な表現 13 • 普通の(?) HTML • <div> とか • Canvas

    element (<canvas>) • SVG element (<svg>) • Scalable Vector Graphics どう表現するか? 結論: SVG を選択
  10. キャンバスの画的な表現 14 • 普通の(?) HTML • <div> とか • Canvas

    element (<canvas>) • SVG element (<svg>) • Scalable Vector Graphics どう表現するか? 結論: SVG を選択 なぜ?
  11. キャンバスの画的な表現 16 キャンバス内に表示される要素(カード等)の実装方法 ① 普通の WEB アプリ同様に実装可能なパターン (HTML / SVG)

    • SVG の子要素は普通に React で構築できる • SVG には <foreignObject> という要素があり、 <foreignObject> の子要素は普通に HTML を記述できる • そのため、スタイリング/イベント/状態の取り回しに特別な事が不要 ② 別系統の実装が必要なパターン (Canvas element) • スタイリング/状態/イベントの取り扱いが完全に別系統 • 素の React で GUI を表現する事は不可能
  12. キャンバスの画的な表現 17 座標系の取り扱い ① 座標系の権を握れるパターン (SVG / Canvas element) •

    DOM の座標系と世界座標系の分離 • 世界座標系上のどこの領域を表示するかの制御 ② 座標系の権を握れないパターン (HTML) • DOM の座標系と世界座標系の分離が困難で自由が効かない • 座標・表示領域にまつわる事ほぼ全て CSS でゴリ押し • 適切な座標系の取り扱いが何かと複雑になりがちで辛い
  13. キャンバスの画的な表現 18 座標系の取り扱い ① 座標系の権を握れるパターン (SVG / Canvas element) •

    DOM の座標系と世界座標系の分離 • 世界座標系上のどこの領域を表示するかの制御 ② 座標系の権を握れないパターン (HTML) • DOM の座標系と世界座標系の分離が困難で自由が効かない • 座標・表示領域にまつわる事ほぼ全て CSS でゴリ押し • 適切な座標系の取り扱いが何かと複雑になりがちで辛い 世界座標系 : キャンバス上のオブジェクト(カード等)が 配置されている座標系
  14. キャンバスの画的な表現 19 座標系の取り扱い ① 座標系の権を握れるパターン (SVG / Canvas element) •

    DOM の座標系と世界座標系の分離 • 世界座標系上のどこの領域を表示するかの制御 ② 座標系の権を握れないパターン (HTML) • DOM の座標系と世界座標系の分離が困難で自由が効かない • 座標・表示領域にまつわる事ほぼ全て CSS でゴリ押し • 適切な座標系の取り扱いが何かと複雑になりがちで辛い 世界座標系 : キャンバス上のオブジェクト(カード等)が 配置されている座標系 キャンバスの 拡大縮小や ミニマップで表示領域を 可視化するために必須
  15. キャンバスの画的な表現 20 座標系の取り扱い ① 座標系の権を握れるパターン (SVG / Canvas element) •

    DOM の座標系と世界座標系の分離 • 世界座標系上のどこの領域を表示するかの制御 ② 座標系の権を握れないパターン (HTML) • DOM の座標系と世界座標系の分離が困難で自由が効かない • 座標・表示領域にまつわる事ほぼ全て CSS でゴリ押し • 適切な座標系の取り扱いが何かと複雑になりがちで辛い 世界座標系 : キャンバス上のオブジェクト(カード等)が 配置されている座標系 キャンバスの 拡大縮小や ミニマップで表示領域を 可視化するために必須 ブラウザ/ライブラリに任せると 座標系の権は握れない
  16. キャンバスの画的な表現 21 SVG のメリット • 標準的な WEB アプリ開発の知識がそのまま使える • DOM

    の座標系と世界座標系の分離できる • キャンバス上のオブジェクトの表示領域の制御がしやすい
  17. キャンバスの画的な表現 22 SVG のメリット • 標準的な WEB アプリ開発の知識がそのまま使える • DOM

    の座標系と世界座標系の分離できる • キャンバス上のオブジェクトの表示領域の制御がしやすい 具体的にどのように実装するか?
  18. キャンバスの画的な表現 28 SVG の API transform で 座標を簡単に制御可能 svg の子要素に

    React コンポーネントの配置可能 viewBox で 表示領域を制御
  19. キャンバスの画的な表現 29 SVG の API transform で 座標を簡単に制御可能 foreignObject 内では

    通常の HTML を記述可能 svg の子要素に React コンポーネントの配置可能 viewBox で 表示領域を制御
  20. キャンバスの画的な表現 30 SVG の API 画としての表現力は十分 座標系も考えやすい 実装もしやすい transform で

    座標を簡単に制御可能 foreignObject 内では 通常の HTML を記述可能 svg の子要素に React コンポーネントの配置可能 viewBox で 表示領域を制御
  21. キャンバスの画的な表現 31 ViewBox を素で扱うのは少々難儀 ① ViewBox の適切な制御を都度考えるのは難しい • e.g., 右下隅に最大までzoom

    in してから zoom outした場合の表示領域は… ② スクリーン(viewport)座標系と世界座標系の対応させる必要あり • ViewBox の値に次第で異なるので色々計算する必要あり ③ スクリーンサイズの変化に応じた ViewBox に制御 • 要するにブラウザのウィンドウサイズの変化 世界座標系は結果的に SVG 上での座標系と対応
  22. キャンバスの画的な表現 32 カメラという概念を導入 • 開発者に ViewBox を意識させない。 • 開発者が意識するのは カメラの位置をどう動かすか。

    • 細かい適切な制御はカメラの 内部実装で隠蔽。 • 座標変換系のメソッドも用意。 開発者はこれらのメソッド使えば 深い事気にする必要なし。 • ゲームエンジンっぽい API。 馴染みがある人はすんなり 理解できる。 これくらい実装すれば座標系を完全に掌握できる。 ライブラリ不要。
  23. リアルタイム共同編集 36 Node-AI はデータ分析業務のコラボレーションを推進しています • 1つのキャンバスに複数人で参加する • 分析者同士がいっしょに議論をしながら作業 • ドメイン知識を持っている人やステークホルダーを巻き込んで分析

    リプレイス前は1つのキャンバスを 同時に複数人が操作する ユースケースに対応できていなかった ブラウザで更新かけないと 他人の操作内容が表示されなかった
  24. • SignalR: リアルタイムの双方向 RPC library リアルタイム共同編集 40 リアルタイム通信は SignalR Front-end

    (TypeScript / React) Back-end (C# / ASP.NET Core) SignalR WebSocket / SSE / Long Polling
  25. • SignalR: リアルタイムの双方向 RPC library リアルタイム共同編集 41 リアルタイム通信は SignalR Front-end

    (TypeScript / React) Back-end (C# / ASP.NET Core) SignalR Tapper TypedSignalR.Client.TypeScript WebSocket / SSE / Long Polling
  26. • SignalR: リアルタイムの双方向 RPC library リアルタイム共同編集 42 リアルタイム通信は SignalR Front-end

    (TypeScript / React) Back-end (C# / ASP.NET Core) SignalR Tapper TypedSignalR.Client.TypeScript TypeScript 側は こんな感じで記述 (C# からコードが生成される) WebSocket / SSE / Long Polling
  27. • SignalR: リアルタイムの双方向 RPC library リアルタイム共同編集 43 リアルタイム通信は SignalR Front-end

    (TypeScript / React) Back-end (C# / ASP.NET Core) SignalR Tapper TypedSignalR.Client.TypeScript TypedSignalR.Client TypedSignalR.Client.DevTools AspNetCore.SignalR.OpenTelemetry WebSocket / SSE / Long Polling TypeScript 側は こんな感じで記述 (C# からコードが生成される)
  28. リアルタイム共同編集 51 • Command パターンは「命令」を「オブジェクト」にするパターン • Undo/Redo は操作履歴を stack に積む必要があるのでこのパターンが必須

    Undo/Redo に必要なのは Command パターン 典型的な Command パターンは React に不適切 この interface を実装した class に 変更対象のオブジェクトの 参照を握らせ、 execute() 実行時に副作用を起こし 状態/描画に影響を及ぼす
  29. リアルタイム共同編集 52 • Command パターンは「命令」を「オブジェクト」にするパターン • Undo/Redo は操作履歴を stack に積む必要があるのでこのパターンが必須

    Undo/Redo に必要なのは Command パターン 典型的な Command パターンは React に不適切 この interface を実装した class に 変更対象のオブジェクトの 参照を握らせ、 execute() 実行時に副作用を起こし 状態/描画に影響を及ぼす React の思想/実装 双方の面で噛み合わない
  30. リアルタイム共同編集 57 React に適した パターンを考える Message (データ) Service (処理) 基本は

    command パターン。 これを分解し React に適合させつつ 自分たちのアプリに 適切な設計に落とし込む
  31. リアルタイム共同編集 58 React に適した パターンを考える Message (データ) Service (処理) 基本は

    command パターン。 これを分解し React に適合させつつ 自分たちのアプリに 適切な設計に落とし込む React は関数型を指向している 親和性 UP!
  32. リアルタイム共同編集 59 React に適した パターンを考える Message (データ) Service (処理) 各

    command 毎に存在する 既存の状態を受け取り 新しい状態を返す各副作用の無い service ① command と stack の管理 ② 適切な CommandExecutor の呼び出し 基本は command パターン。 これを分解し React に適合させつつ 自分たちのアプリに 適切な設計に落とし込む CommandExecutor CommandRunner React は関数型を指向している 親和性 UP!
  33. リアルタイム共同編集 65 Command を dispatch する際のコード 例えばカード配置時の Callback はこんな感じ Reducer

    は同期メソッドなので 非同期処理を済ませてから dispatch する Reducer のコード
  34. リアルタイム共同編集 66 Command を dispatch する際のコード 例えばカード配置時の Callback はこんな感じ Reducer

    は同期メソッドなので 非同期処理を済ませてから dispatch する Reducer のコード CommandRunner.run() の中では ① Stack の操作 ② 対象の command に適した CommandExecutor の実行
  35. リアルタイム共同編集 68 Undo/Redo するコード CommandRunner.undoAsync() の中では ① Stack の操作 (同期)

    ② 対象の command に適した SignalR の RPC 呼び出し(非同期) ③ 対象の command に適した CommandExecutor の実行 (同期)
  36. リアルタイム共同編集 69 Undo/Redo するコード Undo/Redo は非同期が生じる。 Redux の世界で非同期を実行するには Redux Thunk

    や redux-saga を導入する必要性ある。 それは実現したい事に対してヘビー。 CommandRunner.undoAsync() の中では ① Stack の操作 (同期) ② 対象の command に適した SignalR の RPC 呼び出し(非同期) ③ 対象の command に適した CommandExecutor の実行 (同期)
  37. リアルタイム共同編集 70 Undo/Redo するコード Dispatch する前に非同期処理を 実行しておく CommandRunner.undoAsync() の中では ①

    Stack の操作 (同期) ② 対象の command に適した SignalR の RPC 呼び出し(非同期) ③ 対象の command に適した CommandExecutor の実行 (同期) Undo/Redo は非同期が生じる。 Redux の世界で非同期を実行するには Redux Thunk や redux-saga を導入する必要性ある。 それは実現したい事に対してヘビー。
  38. リアルタイム共同編集 74 機能追加は簡単でなければいけない ① OperationType (enum)にメンバを追加し ② 追加した OperationType に対応する

    Command を作成し ③ 追加した Command に対応する CommandExecutor を実装し ④ CommandExecutor を配列に追加する
  39. リアルタイム共同編集 75 機能追加は簡単でなければいけない ① OperationType (enum)にメンバを追加し ② 追加した OperationType に対応する

    Command を作成し ③ 追加した Command に対応する CommandExecutor を実装し ④ CommandExecutor を配列に追加する Stack の操作等は 開発者に意識させない
  40. リアルタイム共同編集 76 機能追加は簡単でなければいけない ① OperationType (enum)にメンバを追加し ② 追加した OperationType に対応する

    Command を作成し ③ 追加した Command に対応する CommandExecutor を実装し ④ CommandExecutor を配列に追加する OperationType で switch とかは書かない Stack の操作等は 開発者に意識させない
  41. Node-AI は時系列データの分析に特化したノーコードツール • β版公開中!是非使ってね! https://nodeai.io/ キャンバスの画的な表現 • SVG を活用 •

    カメラという抽象 リアルタイム共同編集 • リアルタイム通信は SignalR • リアルタイム共同編集 + Undo/Redo のための command パターンベースの設計 まとめ 77 提示したコードや設計は あくまで「エッセンス」です