サービス間通信:RESTとgRPC
サービスを同期的かつ効率的に接続します。
「サービス間通信:RESTとgRPC」はCoddyKit上の無料PHP Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPHP Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 PHP Academyコースには全4レッスンが含まれています。
RESTとgRPC
サービスは同期的に通信する必要があります。リクエストを送り、応答を受け取るという形です。主な選択肢は、HTTP/JSON上のRESTとHTTP/2 + Protobuf上のgRPCです。それぞれ重視するものが異なり、RESTは到達性と人間による扱いやすさ、gRPCは速度と厳密なコントラクトを重視します。
このレッスンでは、PHPの観点から両方を紹介し、どのような場合にそれぞれを選ぶべきかを説明します。
REST:共通語
RESTはURLの背後にリソースをモデル化し、HTTPメソッドとステータスコードで意味を表現します。強みは、汎用的なツール、キャッシュ可能性、curlによるデバッグのしやすさ、専用クライアントが不要なことです。弱みは、冗長なJSON、強制されるスキーマがないこと、リクエストとレスポンスのやり取りに限られることです。
公開APIやブラウザー向けのエンドポイントでは、RESTがほぼ常に適しています。
RESTサービスの呼び出し
PSR-18対応のHTTPクライアント(ここではGuzzle)を使用します。必ず接続タイムアウトとリクエストタイムアウトを設定してください。低速なサービスへの呼び出しが無制限に待機すると、PHP-FPMのワーカーを使い果たし、障害が連鎖する可能性があります。
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$http = new Client([
'base_uri' => 'http://customers-svc/',
'connect_timeout' => 1.0, // never block forever on connect
'timeout' => 3.0, // total request budget
'http_errors' => false,
]);
$res = $http->get('customers/42', ['headers' => ['Accept' => 'application/json']]);
if ($res->getStatusCode() === 200) {
$customer = json_decode((string) $res->getBody(), true);
echo $customer['email'] . "\n";
}ステータスコードが契約になる
サービス間のRESTでは、HTTPステータスコードこそがエラープロトコルです。意図を持って扱ってください。
2xxは成功、4xxは呼び出し側の誤り(無条件にリトライしないでください)、5xxやタイムアウトはリトライ可能です。- 競合には
409、バリデーションには422、レート制限には429を使用します(Retry-Afterを尊重してください)。
400をリトライしても呼び出しを無駄にするだけですが、バックオフを使って503をリトライするのは正しい対応です。
<?php
function isRetryable(int $status): bool {
return $status === 0 // timeout/connection error
|| $status === 429
|| ($status >= 500 && $status !== 501);
}
var_dump(isRetryable(503)); // true
var_dump(isRetryable(400)); // falsegRPC:コントラクトファーストで高速
gRPCはProtocol Buffersを使用します。.protoファイルでサービスとメッセージを定義し、型安全なクライアントおよびサーバースタブを生成します。HTTP/2上でバイナリ形式のProtobufを使うため、JSONよりはるかにコンパクトでレイテンシも低く、ストリーミングにも対応しています。
syntax = "proto3";
package customers;
service Customers {
rpc GetCustomer (GetCustomerRequest) returns (Customer);
}
message GetCustomerRequest { string id = 1; }
message Customer {
string id = 1;
string email = 2;
int32 loyalty_points = 3;
}PHPスタブの生成
gRPCのPHP拡張とprotocプラグインをインストールし、.protoからクライアントクラスを生成します。PHPはext-grpcを使って十分な機能を備えたgRPCクライアントとして動作できますが、ネイティブPHPのgRPCサーバーを実行するには、通常RoadrunnerまたはSwooleが必要です。
pecl install grpc
composer require grpc/grpc google/protobuf
protoc --proto_path=. \
--php_out=./generated \
--grpc_out=./generated \
--plugin=protoc-gen-grpc=$(which grpc_php_plugin) \
customers.protogRPCクライアントの呼び出し
生成されたスタブにより、型付きのリクエストとレスポンスを使用できます。gRPCの呼び出しはメッセージとstatusオブジェクトを返すため、レスポンスを信頼する前に必ずステータスコードを確認してください。
<?php
require 'vendor/autoload.php';
use Customers\CustomersClient;
use Customers\GetCustomerRequest;
use Grpc\ChannelCredentials;
$client = new CustomersClient('customers-svc:50051', [
'credentials' => ChannelCredentials::createInsecure(),
]);
$req = (new GetCustomerRequest())->setId('42');
[$reply, $status] = $client->GetCustomer($req)->wait();
if ($status->code === \Grpc\STATUS_OK) {
echo $reply->getEmail(), "\n";
} else {
fwrite(STDERR, "gRPC error: {$status->details}\n");
}スキーマの進化
Protobufは、ルールを守れば前方互換性と後方互換性を実現できるように設計されています。
- フィールド番号を再利用したり変更したりしないでください。新しいフィールドには新しい番号を割り当てます。
- 削除したフィールドには
reservedを指定し、番号を再利用できないようにします。 - 古いクライアントは未知のフィールドを無視し、存在しないフィールドには型のデフォルト値が使われます。
JSON/RESTでは、このような仕組みは自動的に提供されません。互換性は慣習によって維持し、理想的には共有するOpenAPIスキーマとコントラクトテストによって検証します。
message Customer {
string id = 1;
string email = 2;
reserved 3; // old 'loyalty_points', never reuse 3
reserved "loyalty_points";
string display_name = 4; // new field, safe additive change
}ストリーミング
gRPCは4種類の呼び出し形式をサポートします。RESTが標準でサポートするのは最初の形式だけです。
- Unary — 1つのリクエストに対して1つのレスポンス。
- サーバーストリーミング — 1つのリクエストに対してレスポンスのストリーム(例:ライブ更新)。
- クライアントストリーミング — リクエストのストリームに対して1つのレスポンス(例:一括アップロード)。
- 双方向 — 双方が同時にストリーミング。
プッシュや長時間にわたるデータフローが必要なユースケースでは、RESTエンドポイントをポーリングするよりもgRPCストリーミングが適しています。
コンテキストとデッドラインの伝播
同期呼び出しはチェーンを形成するため、すべてのリクエストに2つの情報を含める必要があります。エンドツーエンドの追跡に使う相関ID/トレースIDと、低速な末端サービスによってチェーン全体がハングしないようにするデッドラインです。gRPCにはデッドラインの機能が組み込まれています。RESTでは、下流に渡すタイムアウト予算を段階的に短くすることで同等の仕組みを実現します。
<?php
// REST: shrink the remaining budget as the call chain deepens
function forwardHeaders(array $incoming, float $remainingMs): array {
return [
'X-Correlation-Id' => $incoming['X-Correlation-Id'] ?? bin2hex(random_bytes(8)),
// downstream must finish within what's left of our budget
'X-Timeout-Ms' => (string) max(0, (int) $remainingMs),
];
}
print_r(forwardHeaders(['X-Correlation-Id' => 'trace-9'], 1500));どちらを選ぶか
実践的な判断基準は次のとおりです。
- RESTは、公開APIやパートナーAPI、ブラウザークライアント、単純なCRUD、デバッグのしやすさ、幅広いキャッシュ対応に適しています。
- gRPCは、内部の高トラフィック・低レイテンシなサービス間通信、厳密な型付きコントラクト、ストリーミングに適しています。
多くのシステムでは両方を使います。サービス間ではゲートウェイの背後でgRPCを使い、外部向けのエッジではRESTを使う構成です。1つのツールに、もう一方のツールの役割まで無理に担わせないでください。
クイックチェック
ユースケースに合ったプロトコルを選びます。
まとめ
同期的なサービス間通信の要点は次のとおりです。
- REST/JSON — 汎用性が高く、デバッグしやすく、キャッシュ可能です。ステータスコードが契約となるため、必ずタイムアウトを設定します。
- gRPC/Protobuf — コントラクトファーストで、コンパクトかつ高速です。ストリーミングに対応し、型付きのPHPスタブを生成できます。
- ステータスコードやgRPCコードからリトライ可能かどうかを判断し、クライアントエラーは決してリトライしません。
- スキーマは追加的に進化させ、Protobufのフィールド番号は決して再利用しません。
- エッジではREST、内部サービス間ではgRPCを使う構成が、一般的で妥当な分担です。
次は、ゲートウェイとサービスディスカバリを使って、これらすべてのサービスをルーティングし、見つける方法を学びます。
よくある質問
「サービス間通信:RESTとgRPC」レッスンは無料ですか?
はい。「サービス間通信:RESTとgRPC」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、PHP Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 PHP Academyコースには全4レッスンが含まれています。
「サービス間通信:RESTとgRPC」で何を学びますか?
サービスを同期的かつ効率的に接続します。 ブラウザで直接実行するハンズオンコードでPHP Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
PHP Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのPHP Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「サービス間通信:RESTとgRPC」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このPHP Academyレッスンでコードを書いて実行できますか?
はい。すべてのPHP Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- モノリスからマイクロサービスへ
- サービス間通信:RESTとgRPC
- APIゲートウェイとサービスディスカバリ
- レジリエンス:サーキットブレーカーとリトライ