Ohara Lab

動的ディスパッチとは?

スサノオの透過型プロキシを支えている仕組みの一つが、動的ディスパッチ (Dynamic Dispatch) である。

名前だけを見ると難しそうだが、考え方は単純である。

例えば、デバイス制御クラスに

device.move(...)
device.stop()
device.get_position()

というメソッドが存在するとする。

通常の Web API では、これらに対応して

/move
/stop
/get_position

のような API を一つずつサーバー側に定義する。

これに対して動的ディスパッチでは、

呼び出したいメソッド名
+
引数

をサーバーへ送り、サーバー側で対応するデバイスクラスのメソッドを探して実行する。

概念的には、

client.move(...)
        ↓
network
        ↓
server
        ↓
device.move(...)

という対応を自動的に行っている。

例えば制御クラスへ

def measure(self, time):
    ...

というメソッドを追加した場合、通信側に measure 専用の処理を新たに書かなくても、

device.measure(10)

のような形でネットワーク越しに呼び出すことができる。

スサノオでは、通信方式に合わせてデバイス制御プログラムを書くのではなく、

まず普通の Python クラスとしてデバイス制御を記述し、その機能を通信側から公開する

という考え方を基本としている。

この方式の利点は、新しい機器や新しいメソッドを追加するときに、通信部分のプログラムを毎回作り直す必要がほとんどないことである。

特に研究用途の機器では、装置ごとに API が大きく異なり、必要な機能も頻繁に追加される。このような環境では、動的ディスパッチによって制御クラスをそのまま公開できることは大きな利点になる。

一方、動的ディスパッチだけでは、

どのメソッドが存在するのか?
どのような引数を渡すのか?
引数は int なのか float なのか?
どのような値が返ってくるのか?

といった I/F の仕様が、外部からは分かりにくい。

そこでスサノオでは、ほぼ完全に動的な方法だけでなく、API の I/F を明示的に定義する方法も用意している。


Pydantic と OpenAPI

ここからは、少し Web API の内部に踏み込む。

FastAPI では、Pydantic を利用して、API に渡すデータの型や構造を明示的に定義することができる。

例えば、

class MoveRequest(BaseModel):
    x: float
    y: float
    z: float

と書けば、

x は float
y は float
z は float

という入力仕様をプログラムとして定義できる。

Pydantic は単なるドキュメントではない。

実際に API に送られてきたデータについて、

必要な値が存在するか
型が正しいか
データ構造が正しいか

なども検証する。

さらに FastAPI は、このように定義された API から OpenAPI の仕様を自動生成することができる。

OpenAPI は Web API の I/F を機械的に記述するための標準的な仕様であり、

どの API が存在するか
どの HTTP メソッドを使うか
どのような引数を受け取るか
どのようなデータを返すか

といった情報を記述する。

概念的には、

Python の制御クラス
        ↓
Pydantic による型・I/F 定義
        ↓
FastAPI
        ↓
OpenAPI

という関係になる。

OpenAPI が用意されることで、人間が API の仕様を確認できるだけでなく、OpenAPI に対応したツールやクライアントプログラムから API の構造を機械的に取得することもできる。


動的な API と明示的な API

スサノオでは、用途に応じて二つの方法を使うことができる。

ほぼ完全な動的ディスパッチ

制御クラスに存在するメソッドを、ほぼそのままネットワーク越しに公開する。

device class
      ↓
dynamic dispatch
      ↓
network API

この方式では、デバイス固有のメソッドを追加しても、通信側の変更を最小限にできる。

そのため、

などに向いている。

Pydantic / OpenAPI による明示的 I/F

一方、公開する API をあらかじめ明確に決めたい場合には、Pydantic を使って I/F を明示的に定義する。

device class
      ↓
explicit API definition
      ↓
Pydantic
      ↓
FastAPI
      ↓
OpenAPI

この方法では、

などを明確にできる。

特に、

といった用途ではこちらが適している。

重要なのは、どちらか一方を選択する必要はないことである。

スサノオでは、

制御クラスをほぼそのまま公開できる動的な仕組みを残しながら、必要な部分については Pydantic / OpenAPI によって I/F を明示的に定義する

という構成を取ることができる。

すべてのデバイスに厳密な API 定義を要求すると、新しい機器を追加するたびに多くの記述が必要になる。

逆に、すべてを完全に動的にすると、外部から利用する場合に API の仕様が分かりにくくなる。

そのため、

自由度が必要な部分
    → 動的ディスパッチ

仕様を固定したい部分
    → Pydantic / OpenAPI

という使い分けを可能にしている。


BL774 との関係

BL774 の REST API も、デバイス側の機能を比較的動的に公開する構成であり、考え方としてはスサノオの動的ディスパッチ方式に近い。

また、BL774 でも OpenAPI に従った API の記述が試みされている(情報が少し古い)

ただし、少なくとも現在の BL774 の構成では、Pydantic 等を用いて各デバイスの I/F を型まで含めて明示的に定義し、それを OpenAPI の定義として積極的に利用する方式までは採用されていないと考えられる。

スサノオでは BL774 互換の考え方を残しながら、必要なデバイスについては Pydantic / OpenAPI を利用した、より明示的な I/F 定義も選択できるようにしている。