RPC とは
y = f(x)
みなさんがいつも行っているこれです。
具体的にすれば、
$userName = DB::table('users')->find($userId)->name;
こう。なんでも良いんですけど。
この「関数呼び出し」を、実はローカルではなく 別の環境に接続して代わりに実行しよう 、というものが Remote Procedure Call です。
上記のデータベース取得も、別の環境(DB)に接続して代わりに取得しているので RPC っぽいです。
厳密には、別の環境側でも同じような形で関数が呼ばれるものが RPC となります(上記の例だと SQL に変換して送信するので RPC ではない)。
RPC の居場所
const username = await (await fetch(`/users/${userId}/name`)).text()
クライアントからサーバにデータを取得しにいったり、
await fetch(`/users/${userId}/name`, {
method: 'PATCH',
body: 'newHero',
})
サーバのデータを更新したりすることはよくあると思います。
でも、わかりづらいですよねこれ。
どうしてわかりづらいかって、
- どのパスがどんな動作を提供しているのかわからない
- どんな値を渡すのか、どんな値が返ってくるのかわからない
- もう何もわからない
つらい。
クライアントとサーバの距離感は遠く離れ、惑星間で通信を行うような気持ちになります。
そこをラッパーでなんとか名前と型を付けて紐付けようとするのがよくあることです。
const username = await Server.getUserName(userId)
await Server.updateUserName(userId, 'newHero!')
ずいぶんわかりやすくなりました。
名前と型をつけることによって、謎は闇に消え去りました(かのように見えます)。
この状態である程度通信わかりづらい問題は、クライアント目線では解決出来ています。
しかし、反対側であるサーバはどうでしょう。
Route::get('/users/{userId}/name', function (req, res) {
return DB::find(req.userId)->name;
});
Route::patch('/users/{userId}/name', function (req, res) {
$user = DB::find(req.userId);
$user->name = req.body;
$user->save();
});
うわー、わかりづらい!
クライアントの要求に答えていたら、わからないことだらけになってしまいました。
サーバは基本的に「リクエストを受け付け、レスポンスを返す」なので、 y = f(x)
である所の f(x)
を実装します。
function getUserName(int $userId): string {
return DB::find($userId)->name;
}
function updateUserName(int $userId, string $newName): void {
$user = DB::find($userId);
$user->name = $newName;
$user->save();
}
おお、これも名前付けと型付けによってわかりやすくなりました。
あれ、ちょっと待ってください。
よく見たら、同じ関数がクライアントにもサーバにもありませんか?
はい、これが Remote Procedure Call です。
サーバが実装している関数を、クライアントが叩く。逆も OK です。サーバから別のサーバに RPC を叩くことも良く行われます。
サーバとクライアントで惑星間くらいの距離感があったのが、 RPC のおかげで近所くらいまで近づいた気がします。
RPC を使うだけだと「お手軽嬉しい仲良し機能!」なんですが、 RPC 自体の実装をするとなると結構過激 です。
「静的に型をつけつつ、クライアントとサーバで整合性を常に取れるようにする」という厳しい要件を達成するため、歴史的に難しい様々な実装がなされてきました。
インターフェース記述言語(IDL)という新しい言語を用意して、そこに関数のインターフェースを記述し、そのファイルからクライアント・サーバ双方の言語に合わせたコードを生成するのが一般的です。
ONC-RPC や SOAP 、 XML-RPC や最近だと Protocol Buffers なんかが人気のようです。
MagicOnion
Cysharp/MagicOnion という新しい RPC ライブラリが最近登場しました。
これは クライアント・サーバ共に C# のみ 、という縛りを設けることにより、「C# のインターフェースを定義するだけ」 で RPC を実装出来てしまうという凄いものです。
C# の静的型付けに関する豊富な機能のおかげで、 IDL すら不要にしてしまいました。
ここまで距離が近づくと、クライアントとサーバはもう親友ですね。