NestJS meetup #5
cls-hooked による実行コンテキストの保存と利用Hiroaki KARASAWA from dinii, incNestJS meetup online #52023/03/31
View Slide
2自己紹介● 氏名○ 唐澤弘明 or @karszawa● 所属○ 株式会社 dinii○ 飲食店の POS システムやモバイルオーダーを作っています○ 【顧客情報 x 喫食情報】で飲食業界で初めて CRM を実現しています● 興味○ TypeScript, React, GraphQL○ NestJS 歴 ⇒ 3年(ダイニーに入社してから)
飲食店の All in One SaaS“ダイニー”
dinii と NestJS4● ダイニーのバックエンドは NestJS によるモノリスです○ 最近 Modular Monolith っぽく分割し始めたユーザー数【450万人】到達! 🎉🎉🎉
【本題】ダイニーにおける cls-hooked の活用5
cls-hooked is …6Continuation-Local Storage ( Hooked )● Async hooks の便利なラッパーAsync hooks is …● Node.js の API で非同期処理のライフサイクルを追跡できる 🤨● その関数がどういうコンテキストで呼び出されたかを記録できる 🤔● 関数の呼び出しごとに呼び出しの粒度で情報を保持できる 🧐
関数の呼び出しごとに呼び出しの粒度で情報を保持できる7例● Async hooks を使わないならこんな感じ● もちろん main がたくさん呼び出されると上手く行かない
cls-hooked を利用するとこうなる8例`context` が cls-hooked によって作成されている
便利ですよね?9
ダイニーでの活用例を紹介します10
実用例 ① リクエストごとの id を保存してロガーで出力11ヘッダーから x-request-id を抽出事前に定義した context にセットcontext を作成(グローバルで OK)AppModule にて利用※ Express.js の app.use と同じNestMiddleware として実装できる
実用例 ① リクエストごとの id を保存してロガーで出力12先程の context から値を取得logger で出力※ ちなみに winston を使っています※ ちなみに winston の出力先はこんな感じ開発環境 ⇒ Human readable format本番環境 ⇒ JSON で標準出力 + Sentry
実用例 ② NestJS x TypeORM13データベースのトランザクションか否かのコンテキストを保存してトランザクション中での利用であればトランザクション用のコネクションを利用する● その関数がトランザクション内で実行されていれば失敗時はロールバックして欲しい● トランザクションではないなら不要な処理はしないで欲しい● それぞれ書き分けるのは面倒くさいというときありませんか?
実用例 ② トランザクションのコンテキスト14odavid/typeorm-transactional-cls-hooked というライブラリの実装にて cls-hooked が活用されています
実用例 ② トランザクションのコンテキスト15おもむろに runOnTransactionalCommit という関数を呼び出すとトランザクションコミット時に処理を実行してくれる(これも呼び出しのコンテキストをストレージとして利用している)
実用例 ② トランザクションのコンテキスト16NestJS での TypeORM の実践に関してはこちらの記事もご覧ください!
実用例 ③ Transactional を魔改造17OLAP 的なメソッドにおいてリードレプリカの利用を強制する● 重い分析系のクエリを間違ってもプライマリのデータベースに向けたくない● あるメソッド以下では絶対に特定のコネクション(レプリカ向け)を利用させる
実用例 ③ Transactional を魔改造18デフォルトのデータソースレプリカのデータソースAnalytical のアノテーションが付いているかどうかをcls-hooked で保持し、付いていればレプリカの方のDataSource を使うよう Repository にパッチをあてる
まとめ19
まとめ20● cls-hooked について説明しました● アイデア次第でいろいろなことができる● 実現不可能と思っていた難しい課題があっさり解決してしまうことも● 実は 500 行ぐらいしかないライブラリなので読んでみて下さい