CordisとTypeScriptにおけるプラグインアーキテクチャの手間を解決
Node.jsやTypeScriptで拡張可能なアプリケーションを作成したことがあるなら、同じような落とし穴に遭遇したことがあるでしょう。ユーザーやシステムがプラグインを接続します。プラグインは5つのイベントリスナーを登録し、いくつかのタイマーを開始し、ルーティングを登録し、サービスを注入します。そして、プラグインがライブで無効化または更新されます。
次に何が起こるか?そう、メモリリークです。リスナーはたれ流しになり、タイマーはバックグラウンドでティックし続け、コンテキスト参照がガベージコレクションによるメモリクリーンアップを妨げます。Node.jsでは、依存関係のライフサイクル管理は手作業の骨が折れる仕事になることが多いです。
しばらく前、Koishiチャットボットフレームワークの開発者たちは、まさにこの問題に直面しました。何百ものサードパーティプラグインがプロセスを再起動せずに開始、分離、互いに置き換え、アンロードできるコアを作成する必要がありました。それがCordisフレームワークの始まりです。
Cordisとは一体何か
作成者は自分のプロジェクトを「時空間的合成のためのメタフレームワーク」と呼んでいます。賢そうで気取った表現ですが、本質は実際には地に足のがついたものです。
Cordisは、依存性注入コンテナ(IoC)、イベントバス、階層的コンテキストツリーを組み合わせたものです。各プラグインやサービスは独自のコンテキスト内で動作します。そのコンテキストが破棄されると、Cordisは自動的にそのすべてのリソースをクリーンアップします:イベントハンドラを削除し、タイマーを停止し、作成されたサービスを削除します。
ここには魔法はなく、明確な規律があります。プラグインが[object Object]、[object Object]、または[object Object]メソッドを使用する場合、フレームワークが自動的に副作用のクリーンアップを行います。
コンテキストモデルの仕組み
ライブラリの中央にある概念は[object Object]です。それは単なる設定を持つフラットなオブジェクトではなく、分岐するツリーです。
[object Object]を呼び出すと、フレームワークは子コンテキスト(フォーク)を生成します。子コンテキストは親からサービスを継承しますが、登録されたリソースへの独自の参照を保持します。
プラグインAを無効にすると、その子コンテキストが崩壊します。[object Object]ハンドラーは共有イベントバスから削除されますが、データベースサービスとプラグインBは静かに動作し続けます。
TypeScriptにおけるサービスと型付け
Cordisのサービスはベースクラス[object Object]の継承を通じて宣言されます。これにより、厳密な型付けを維持しながらコンテキストプロパティから直接アクセス可能になります:
[object Object]コンストラクトは、モジュールの読み込み順序の問題を解決します。データベースサービスが非同期的に初期化されるか、後から接続する場合、依存するプラグインは準備完了を待ち、それ自体がアクティブになります。
スコープの可視性の微調整
実際のプログラムでは、モジュールがすべてに反応すべきでないことがよくあります。例えば、あるハンドラーは特定のチャンネルからのメッセージや、特定のリクエストヘッダーを持つリクエストにのみ必要です。
Cordisは[object Object]呼び出しとコンテキストプロパティを通じてフィルタリングの概念を導入しています。サービスの可視性をツリーの特定のブランチに制限したり、不要なイベントをフィルタリングする述語を設定したりできます:
このアプローチが適しているタスク
このライブラリは特定のクラスのアプリケーションのために作成されました。FastifyやExpressでの通常のCRUD APIに持ち込むべきではありません。そこでは余分な抽象化レイヤーになってしまいます。
しかし、Cordisは以下に完璧に適合します:
- モジュラーCLIユーティリティとジェネレーター。ユーザーがコマンドやビルドパイプラインを拡張するnpmパッケージを提供できる場合。
- Electron/Tauriによるデスクトップアプリケーション。ウィンドウを再読み込みせずにオンザフライで有効化・無効化できるアドオンシステムやテーマを整理する場合。
- ボットと統合ハブ。サービスが dozen の異なるプラットフォーム(Telegram、Discord、Slack)と通信し、各アダプタが孤立した 삶을送る必要がある場合。
- 自動化ツール。プロセスがWebインターフェースやYAMLファイルを通じてユーザーによって動的に設定される場合。
落とし穴と欠点
完璧なツールはなく、Cordisにも多くの特有のニュアンスがあります:
- 学習曲線が急です。ドキュメントは乾燥した言語で、特定用語が豊富に書かれています。スコープのマージや副作用の概念を理解するには、ソースコードを注意深く読む必要があります。
- 聞き慣れないAPI。TypeScriptの型マージモジュールを通じてコンテキストオブジェクトにサービスをバインドすることは、装飾子を使ったNestJSやInversifyJSに慣れている人にとっては最初は混乱するかもしれません。
- メンタルモデルに縛られます。プロジェクトの建築に頻繁な動的コードアンロードが含まれていない場合、組み込みのエフェクトマネージャーの利点はコードの複雑さによって相殺されます。
試す価値はあるか
Cordisはコンポーネントのライフサイクル管理に焦点を当てた興味深いエンジニアリングプロジェクトです。拡張可能なシステムを作成する際のリソースリークの問題を優雅に解決します。
プラグイン可能なプラグインの成熟したエコシステムを持つシステムを設計していて、副作用を追跡する信頼できるメカニズムを箱から出して必要としているなら、リポジトリをフォークして[object Object]、テストの例研究了してください。純粋なTypeScriptでマイクロカーネルアーキテクチャを構築する方法の素晴らしい例です。
関連プロジェクト