Share
Embed
TypeScriptとの歩み そしてtRPCのなにが嬉しいのか
2024年2月29日 (日本時間) にて行われたイベント https://optimind.connpass.com/event/308598/ の登壇資料です.情報は当時のもので,内容はすべて個人の意見です.
Luma
February 29, 2024
Transcript
37 slides
-
チーム・プロダクト紹介(3/4) どんな商品があって どこに届けたくて 使える車両はどれで 稼働できるドライバーは誰で その他,どんな制約があるか Loogiaのパラメータ パラメータを用意するために,お客様自身でデータを用 意・加工・手作業で作成,もしくはオプティマインド側で 都度マクロを用意するなど...
データが障壁になることは物流に限らずDXをしたいとき に立ちはだかる壁だろう. マクロでやるにしても課題が多い. 管理ができない,更新ができない,ノウハウがたまらない, 担当の人がマクロを扱えるかに依ってしまう,など. コネクト以前 お客さんの基幹データを手作業で加工してもらって... Excelでデータを毎回用意してもらって... この計画作成へのデータ投入,加工,並びに管理とお客様 のシステムとの連携全般をサービスとして提供するのがコ ネクトとなる.これにより,お客様のビジネスロジックを 汲むこともできる. マクロを誰かが用意して... -
チーム・プロダクト紹介(4/4) どんな商品があって どこに届けたくて 使える車両はどれで 稼働できるドライバーは誰で その他,どんな制約があるか Loogiaのパラメータ コネクト 例: •
ドライバー情報,ドライバー のシフト情報 • 商品データベース • 毎日更新される注文情報 お客様の基幹データ 取り込み方法,下処理,パラメータへの変換 はビジネスロジック等を考慮した専用のデー タパイプラインを個社ごとに構築. ETLツール,Dataform,そして専門のデータ エンジニアをアウトソーシングして実現. 中間データをお客様が直接,閲覧・修正できる.ビジネスに合わせて 微修正するフローをサポートしたり,さらにLoogiaの計算結果を”利 用”して更にビジネス特化の最適化に役立てる運用も実現している. -
プロダクト要件の概要 ざっくりどういう要件のWebアプリケーションか,だけわかるように紹介します. Web クライアント Firebase Authentication (認証・全社共通) Cloud Run (サービスAPI)
Cloud Run Jobs (バッチ処理) データベース • 基本は普通にWebアプリ • リクエスト量がさほど集中す ることはない • 長時間かかる処理があり,そ れはJobsに退避 • バッチ処理も同じリポジト リ・コードで管理して,TSで 書かれている -
作るうえで技術的に達成したかったこと • JS/TSでのある程度の開発ができる人が自然にフロントエンド・バックエ ンド両方の開発を,すぐにできる仕組み ◦ 時間もタイトな中でチームをすぐに作る必要があった ◦ 専属マネージャーがいるわけでもなく,FE・BE・インフラそれぞれ専 属で多くのメンバーを一気に抱えられるわけでもなかった.コストも 潤沢な訳では無い.
◦ 逆に,APIを外部に提供する必要や,高度な非機能要件はない.わりと 業務システムより. • メンバーの力量やヒューマンエラーしないことに依存しないコード品質を 一定水準以上に保つ仕組み ◦ 誰が書いても最低限の要請を自動で満たすようなセットアップが必要 ◦ レビュー,QA等に取れる時間も限られた中で,いかに「ヒューマンエ ラー」を事前に発生させないか -
2014 v1.1 v1.3 2015 v1.4 v1.5 v1.7 v1.6 Union Types
(和型) T | U JSX <My Comp /> ユーザ定義型ガード f unction (v ): v is T asy nc/await 2016 v1.8 v2.0 v2.1 Stringリテラル型 コントロールフロー解析強化 (デッドコード検出など) --strictNullChecks if (obj.f oo) { … } 系タイプガードの強化 nev er型 tagged unionについてはTypeScriptから構 造体を奪って、関数だけでまた構造体を得 るまでの道のり(チャーチ・エンコーディン グ) も見てね key of 型演算子 mapped ty pes 2017 v2.2 v2.3 v2.6 v2.4 v2.5 object型 Tagged Union Ty pes (直和型) -
2018 v2.7 v2.8 v3.2 再帰型は以前までもテクニックを使 えばできたが,より直接的にできる ようになった v2.9 v3.0 Conditional
Ty pes: T extends U ? X : Y bigint v3.1 2019 v3.3 v3.4 v3.7 v3.5 v3.6 Recursiv e Ty pe 2020 v3.8 v3.9 v4.0 v4.1 2021 v4.2 v4.3 v4.4 v4.5 Template Literal Ty pes -
2022 v4.6 v4.7 v4.8 v4.9 2023 v5.0 v5.1 v5.2 v5.3
... package.json の exports など 共変・反変の指定子 in/out unknownが実質 {} | null | undef ined 扱いに satisf ies演算子 using演算子 TypeScript Origins: The Documentaryやリリースノートもチェックしてみてください. -
TypeScriptとの歩み • 最初はちょっと便利な・修正されたJavaScript • 型システムとして必要な解析力と表現力を備えていく • ECMAScriptの先導者(仕様を主導する)から,ECMAScriptの先駆者(決ま りそうな仕様を先取りして実装)へ • 実践への活用と積み重ねの繰り返し.今ではやっとnpmでパッケージを探せ
ば大抵TypeScript定義も付いてくる時代に • DefinitelyTyped,Microsoft自身によるTypeScript及びVSCode開発への利用に よるドッグフーディング • 静的解析器として強力に.開発環境として便利に.型システムとして独自の 類をみない進化. -
TypeScriptの型システム • 漸進的型付けの部分的な採用 ◦ やはりベースはちょっとずつJSから移行する,ちょっとずつ整備し ていくという発想 • 二階の型システム (System F)
相当.これ自体はそこまで強力というわ けではなく,HaskellやScalaなどには劣る • 重要なBreakthrough: ◦ 型システム上での計算がチューリング完全に ▪ Tagged Union (型での構造の直接的な表現力) ▪ Conditional Types と infer (matchに相当) ▪ Recursive Types (ループ・再帰に相当) ◦ Template Literal Types により文字列リテラル型がパース可能に チューリング完全な型システムが必ずしもいいということにはならない 決定可能であるべきという前提をつけるのが型理論では基本になるし,例えばOCaml/Haskell/Coqなどがそう. -
TypeScriptのネイティブランタイムの台頭 • DenoやBunが登場 • これらは型情報を落として実行するというのが基本なので,基本的には JSランタイムにほかならない.なので,大事なのはTSを通した体験が どうかということ. • これらは実際とても体験がよい.DenoもBunも .ts
ファイルを直接実行 の対象とすることができて,DenoはGo言語のようなURLインポートで node_modulesいらず.Bunは高速なnpm installの実現.いずれにしても ts-node とそれのための煩わしい設定がいらないのは大きな変化 -
(2020年) 深い溝 ただ,リモートにあるTSの 関数を呼び出したいだけな のに... 当時の選択肢: • 一個一個の関数をうまくつなげる ◦ fetch(...)
as MyResponseType する • OpenAPIで中間のスキーマをまた別で定義してコード生成 • GraphQL前提でApollo + Nexus とコード生成 究極のDRYとSSOTを実現したいというかなんというか,これらはまだ完璧ではなかった. クライアント側でGraphQLを書くのとコード生成を許容すれば Apollo + Nexusはわるくなかったけどね -
(2020年) 当時の最先端 つなぎたいって話 2020年夏ぐらいにたまたま見つけた frourio がこれへの解決を提示する最 先端にいた. ↑見つけたあとにコントリビュートしたり新たな職探しを始めたのがきっかけで転職 つながるって話 上の話をだいたい
そのままできる frourioはaspidaというものと組み合わせて,コード生成で 補助しながら,関数を書いて呼び出すだけ,という体験 を提供しようとしたフレームワーク. きっとみんなこの発想はあったが,早期に実現していた のはfrourioだった. -
tRPCの概念概略図 router router [...] subscription procedure middlewareの合成 JSON→ JSON async
ジェネレータ handler routerとclientを つなぐ ツリー構造を成す まさにつなげてくれてるやつ...! ↓HTTPによる実現が基本 しかし,それに限らないの client ↑ async関数として,React Queryとし て,などなど GraphQLとRESTの融合みたいな抽象化だ! query JSON→JSON async関数 middleware contextの変換 -
tRPCのプロシージャの例 実際にプロジェクトでの例をかるーく編集したものを 紹介します.ほぼそのままです. 入力のないasync関数で, 検証済みトークン(ユーザーに 紐づく,ユーザーは必ず組織に所属する)がコンテキスト 上になければ no-session, 登録済みじゃなければ not-
registered,登録済みの組織なら registered と返します. procedure とは 「JSONを受け取りJSONを返すasync関 数」が基本. さらに,コンテキストを横からもらうことができる(後で 説明) -
寄り道: ちょっとzodの紹介 ところで,procedureの入出力のJSON型は zodを利用して定義できます. 例えば先程の getUserInfo の返り値の型は左 のような形で定義されています. JSですからランタイムで利用できて,ランタ イムで「実際にそのスキーマを満たすかチェ
ック」することができます. それと同時に,TSの対応する型を計算するこ とが型システム上で可能です.これは型を単 に書くだけでは実現できません.なぜなら, 型はランタイムに影響を与えることはない, というTypeScriptの根底があるためです. -
寄り道: ちょっとzodの紹介 zodを使うと気軽に「離れた場所とTypeScriptでつながる」ことができる • Cloud Run と Cloud Run Jobs
を結ぶところでも使っている.要は,単な るstringでやり取りしなければならないので,JSON.parseする.その時 にunknown相当を as T するのではなく,安全にfail-fastにパースする. ◦ メンバーの書いた 業務でCloudRun Jobsを使ってみた話 も参照して みてください • tRPCもzodの「離れた場所とTypeScriptでつながる」の例だ.ブラウザと サーバーという,TypeScriptで書けるけど離れた場所にある世界をつなぐ 存在 -
tRPCのmiddlewareとcontext まずは例から見ていきましょう. 左記は事前に用意したコンテキスト内のロール判定を もとに,ログイン済みでなければ401 Unauthorizedを 投げ,新たに,必ず検証済みトークンが入っているコ ンテキストを作成します. これで, userProcedure で作られたクエリはすべて認
証済みのユーザーしか到達しません. 新たにコンテキストを作成,といいつつコンテキスト は詰替えをしているだけに見えるかもしれません.し かし,これに大きな意味があります.(後述) 最後に,プロシージャ(のルートを作成する関数)に ミドルウェアを合成します.当然,何個でもミドルウ ェアを合成できます. ログイン済みユーザーのみ使えるプロシージャを作ることを考えます. -
tRPCのmiddlewareとcontext 整理します. • queryはJSONを受け取りJSONを返すasync関数.ただし,contextを 別で利用できる. • contextはqueryを定義したprocedureにより決まる. • procedureは0個以上のmiddlewareの合成である. •
middlewareはcontextを受け取りcontextを返すasync関数 これはかなりえらい抽象化...! -
tRPCのその他のトピック • subscription (websocketなどで実現されるリアルタイムな配信) • テスト • 開発用ツール (trpc-panelなど) •
エラーハンドリング・エラーフォーマッティング などなど -
選択肢のおさらいとその他 • OpenAPI とコード生成 • GraphQLとApollo + Nexus (コード生成) •
Frourio + Aspida • tRPC • Zodios • ElysiaJS + Eden (Bun) • ts-rest • Hono RPC 新時代...! -
まとめ: 課題はどのように達成された? • JS/TSでのある程度の開発ができる人が自然にフロントエンド・バックエンド 両方の開発を,すぐにできる仕組み ◦ Node.jsを使用すればTSでバックエンドもかける.さらに tRPC を利用す ることでそのギャップを最小限にする.フロントエンドを書くようにバ
ックエンドをかけるし,逆も同様. • メンバーの力量やヒューマンエラーしないことに依存しないコード品質を一 定水準以上に保つ仕組み ◦ CIやESLintの設定により徹底してルール準拠,さらに tRPC や zod によ り手動で外の世界と繋ぐというのをなくし,フレームワークで対処.人 やスタイルに依らない仕組み作りが可能. ◦ CIが通ればCDも通る.レビューではロジックを見ることに集中できる.