ソフトウェア設計
マイクロカーネルアーキテクチャ(プラグインアーキテクチャ)
post:
マイクロカーネルアーキテクチャは、システムの基本機能を持つコアと、機能を追加するプラグインに処理を分ける設計方式です。 プラグインアーキテクチャとも呼ばれ、共通の仕組みを保ちながら機能を拡張できます。
マイクロカーネルアーキテクチャとは?
システムに共通して必要な機能をコアに置き、用途ごとに異なる処理や追加機能をプラグインとして分ける構成です。 コアが用意した接続方法に従ってプラグインを組み合わせることで、コアの変更を抑えながら機能を追加できます。
例えば、エディターの基本操作をコアが提供し、プログラミング言語ごとの補完や表示を拡張機能に任せる構成が考えられます。 必要な拡張機能だけを選ぶことで、利用者ごとに機能の組み合わせを変えられます。
コア・プラグイン・接続ルール
コアシステム
コアはシステムの基盤となる部分です。共通の処理やプラグインを利用するための仕組みを提供します。 設計によっては、プラグインの発見、読み込み、依存関係の管理も担います。コアに含める機能は、システムの目的によって異なります。
プラグイン
プラグインは、特定の機能や用途別の処理を実装する部品です。例えば、ファイル形式ごとの読み込み処理や、顧客ごとに異なる業務ルールを分けられます。 追加や取り外しがいつできるかは実装次第で、変更後に再起動が必要な場合もあります。
プラグイン契約
コアとプラグインがやり取りするための約束事を、ここではプラグイン契約と呼びます。 呼び出す関数やデータの形式、登録方法、エラーの扱いなどを決めます。 コアが用意した拡張ポイントに合う実装であれば、内部の処理を変えても同じ接続方法を使えます。ただし、動作やバージョンにも互換性が必要です。
プラグインの発見と管理
コアがプラグインを使うには、何が存在し、どの機能を提供するのかを知る必要があります。 その情報を登録・参照する仕組みをレジストリと呼びます。名前や提供する機能、依存先など、何を記録するかは実装によって異なります。
プラグインを見つける方法には、次のようなものがあります。これらを組み合わせる構成もあります。
- マニフェスト:プラグインに付属する設定ファイルに、名前や機能、依存関係などを記述する
- ディレクトリの走査:決められた場所に置かれたプラグインのファイルを探す
- 自己登録:起動したプラグインやサービスが、コアなどに自分の提供する機能を登録する
例えばVS Codeの拡張機能はpackage.jsonをマニフェストとして使い、別の拡張機能への依存をextensionDependenciesで宣言できます。
プラグインのライフサイクル
プラグインを管理する流れには、次の段階があります。実際にどの段階をサポートするかはシステムによって異なります。
- 発見:設定や保存場所から利用できるプラグインを見つける
- 依存解決:必要な部品やバージョンがそろっているかを確認する
- 読み込み・初期化:プラグインを読み込み、必要な設定を渡す
- 有効化・実行:プラグインの機能を利用できる状態にする
- 無効化・アンロード:必要に応じて処理を止め、資源を解放する
実行中に追加・削除できる仕組みもありますが、すべてのシステムが対応するわけではありません。依存するプラグインがある場合は、その停止や再読み込みも考慮します。
プロセス内とプロセス外の接続
プラグインの実行場所によって、接続の方法や障害が及ぶ範囲は変わります。 プロセスとは、OSが実行中のプログラムを管理する単位です。
| 項目 | プロセス内 | プロセス外 |
|---|---|---|
| 接続方法 | コアと同じプロセスにモジュールなどを読み込み、関数を呼ぶ | 別のプロセスで動かし、プロセス間通信やHTTPなどでやり取りする |
| 処理の負荷 | 直接呼び出せるため、通信の負荷を抑えやすい | 通信やデータの変換に追加の負荷がかかる |
| 障害の影響 | 異常終了やメモリ破壊がコアにも及ぶ場合がある | プロセスを分けることで影響を抑えやすいが、依存する機能には影響が及ぶ |
| 実装言語 | ランタイムや接続方法に応じた互換性が必要 | 通信の約束を守れば、異なる言語で実装しやすい |
プロセスを分けても、自動的に復旧するわけではありません。障害の検知や再起動の方法、共有するデータの保護も別途設計します。 また、別プロセスで動かすだけで安全になるわけではなく、プラグインに与える権限も考える必要があります。
プラグイン同士の依存
プラグインは互いに独立していると追加・削除しやすくなりますが、別のプラグインの機能が必要な場合もあります。 その場合は依存関係を宣言し、相手の公開インターフェースを利用します。依存先のバージョンや、利用できない場合の動作も考慮します。
AがBに、BがAに依存するような循環依存は、読み込み順の決定や個別の変更を難しくします。 複数のプラグインが共通の機能を必要とする場合は、共通ライブラリやコアのサービスに分ける方法もあります。ただし、共通部分を増やすと、そこへの依存も増えるため役割を見極めます。
契約のバージョンと互換性
公開した契約を変更すると、それを使うプラグインも変更が必要になる場合があります。 契約にバージョンを設け、プラグインが対応する範囲を宣言できるようにすると、組み合わせの判定に役立ちます。
項目の削除や意味の変更は互換性を壊す可能性があります。項目の追加も、受け取る側が未知の項目を許容しない場合などには互換性を壊します。 変更の影響を確認したうえで、新旧バージョンの並行提供や廃止までの予告期間を設ける方法があります。
長所と短所
長所
- コアの変更を抑えながら、必要な機能を追加・差し替えしやすい
- 機能ごとに開発・テストを分けやすく、不要な機能を含めずに配布できる場合がある
- 公開された契約を使って、第三者が拡張機能を開発できる
短所
- 契約や依存関係、読み込みの仕組みを設計・管理する必要がある
- プラグインの組み合わせが増えると、互換性のテストが複雑になる
- 接続方式によっては呼び出しや通信に追加の負荷がかかる
- プラグイン間の依存が増えると、個別の変更や取り外しが難しくなる
向いている場面
エディターの拡張機能や、顧客ごとに異なる業務ルールのように、共通部分を保ちながら機能を追加・変更したいシステムに適しています。 第三者が拡張機能を作る場合にも、公開された契約が接続の基準になります。
一方、機能が少なく変更もほとんどない場合は、プラグインの管理機構が過剰になることがあります。 機能を独立させにくい場合や、通信の負荷が厳しい場合も、分割の利点と費用を比較する必要があります。 この方式自体は複数サーバーへの分散や負荷分散を提供しないため、それらが必要なら別途設計します。
OSのマイクロカーネルとの関係
OSの分野でも、最小限の機能をカーネルに置き、ファイルシステムやドライバーなどのサービスをカーネルの外で動かす構成をマイクロカーネルと呼びます。 例えばseL4はOSを構成するためのマイクロカーネルで、通常のOSサービスはユーザー空間のプログラムとして実装します。
小さな中心と外部の機能に分ける考え方は似ていますが、OSのサービスがそのままアプリケーションのプラグインであるとは限りません。 この記事では、アプリケーションにも使えるコアとプラグインの設計方式を中心に説明しています。
おまけ:個人開発のOasisをプラグインアーキテクチャで見る
私が個人開発しているOasisは、OpenWrt向けの実験的なAIアシスタントです。 ここではOasisのパッケージ構成を例に、コアとプラグインの関係を見てみます。 以下は、Oasis本体のバージョン3.2.6を含むソースコードをもとにした説明です。構成や実装は今後変わる可能性があります。
コアと追加パッケージ
oasisパッケージは、CLIでのチャットやAIサービスとの通信などを提供するコアに当たります。
この上に、Web画面やツール連携などの機能を追加するパッケージを組み合わせます。
例えばluci-app-oasisはLuCIのWeb画面を、oasis-mod-toolはAIからのツール利用を提供します。
コアにはチャット処理、AIサービスとの通信、設定や履歴の管理など、複数の機能が含まれています。 コアを最小限の仲介機能に絞った構成というより、機能を持つコアに拡張を組み合わせた例として見ることができます。
プラグインの接続と発見
OasisではOpenWrtのプロセス間通信の仕組みであるubusを利用します。
例えばコアのoasis.chatやoasis.titleには、それぞれ呼び出せるメソッドがあり、ローカルツールの呼び出しにもubusを使います。
オブジェクト名、メソッド名、引数や戻り値の形式が、接続する際の約束事に当たります。
ツールの情報は、対象ディレクトリのスクリプトを調べたり、/etc/oasis/tool-manifest.d/のマニフェストを読み取ったりして管理します。
Luaやucodeで実装したローカルツールも扱えます。マニフェストによる情報の管理と、ubusによるメソッドの呼び出しは、それぞれ別の役割です。
すべての追加機能がubusだけで接続されるわけではありません。例えばoasis-mod-toolには、コア側から読み込むLuaモジュールも含まれます。
そのため、Oasis全体を一律にプロセス外プラグインとして扱うのではなく、機能ごとに接続方法を見ます。
パッケージ同士の依存
luci-app-oasisとoasis-mod-toolはoasisに依存します。
また、エージェント機能を提供するoasis-mod-agentと、RPC経由でのツール実行を提供するoasis-tool-edgeは、oasis-mod-toolに依存します。
これらはパッケージの定義に記載された一方向の依存関係です。
依存関係があるため、oasis-mod-toolの削除を考えるときは、そのパッケージに依存する機能も考慮する必要があります。
パッケージ管理ツールの動作や指定したオプションによって、削除の可否や影響は異なります。
拡張しやすくするための考え方
今後さらに機能を分ける場合は、AIサービスごとの通信処理や、UCIの解析・検証を独立したモジュールとして扱うことも考えられます。 必要な分離の程度は、変更の頻度や管理の負担に応じて決めることになります。 また、ubusのオブジェクト名、メソッド、引数や戻り値の仕様を整理しておくと、接続する側が実装しやすくなります。
Oasisの概要は紹介記事、ソースコードはOasisのGitHubリポジトリで確認できます。
参考資料
- Visual Studio Code: Extension Manifest — マニフェストと依存関係
- Visual Studio Code: Extension Host — 拡張機能の実行環境
- OSGi Core: Module Layer — バンドルの依存関係
- seL4: Frequently Asked Questions — OSのマイクロカーネルとサービス