- AntigravityでProtocol Buffers定義とgRPCサーバーコードを自動生成
- REST APIからgRPCへの移行をAIが支援し、通信速度を最大10倍高速化
- gRPCエンジニアの月単価は80〜120万円、マイクロサービス案件で需要急増中
マイクロサービス間通信のデファクトスタンダードとなったgRPC。JSON over HTTPに比べて最大10倍高速なバイナリ通信、厳格な型定義、双方向ストリーミングなど、エンタープライズ開発に不可欠な機能を備えています。
しかし、Protocol Buffers(protobuf)の定義、コード生成、サーバー実装、クライアント生成と、学習コストと作業量が多いのがgRPCの課題です。Google Antigravityを使えば、これらの作業を大幅に効率化できます。
- gRPCの基礎概念とREST APIとの比較
- AntigravityでProtocol Buffers定義を自動生成する方法
- gRPCサーバー・クライアントの実装をAIで効率化する手法
- ストリーミングRPCの実装パターン
- gRPC-Webでフロントエンドからの呼び出しを実現する方法
gRPCとは?REST APIとの違い
gRPCの概要
gRPC(gRPC Remote Procedure Calls)は、Googleが開発した高性能なRPCフレームワークです。HTTP/2上で動作し、Protocol Buffersでデータをシリアライズすることで、高効率な通信を実現します。
| 比較項目 | REST API | gRPC |
|---|---|---|
| プロトコル | HTTP/1.1 | HTTP/2 |
| データ形式 | JSON(テキスト) | Protocol Buffers(バイナリ) |
| 型安全性 | OpenAPI / Zod | 言語レベルで保証 |
| ストリーミング | WebSocket別途 | 4種類の通信パターン内蔵 |
| コード生成 | 手動 / OpenAPI Generator | protoc で自動生成 |
| パフォーマンス | 基準 | 5〜10倍高速 |
| ブラウザ対応 | ◎ | △(gRPC-Web必要) |
gRPCの4つの通信パターン
- Unary RPC: 1リクエスト → 1レスポンス(REST APIと同様)
- Server Streaming: 1リクエスト → 複数レスポンス(リアルタイムフィード)
- Client Streaming: 複数リクエスト → 1レスポンス(ファイルアップロード)
- Bidirectional Streaming: 双方向リアルタイム通信(チャット・ゲーム)
SES現場でのgRPC需要
2026年現在、gRPCスキルの需要は急増しています:
- マイクロサービス間通信: サービスメッシュ(Istio/Linkerd)でgRPCが標準に
- リアルタイム処理: 金融・IoTでのストリーミング処理にgRPCが採用
- モバイルバックエンド: gRPCのバイナリ通信で帯域幅を削減
- AI/ML基盤: TensorFlow ServingやTriton推論サーバーのAPI
gRPCエンジニアの月単価は80〜120万円で、特にGo + gRPCの組み合わせは最も需要が高いスキルセットの一つです。
AntigravityでProtocol Buffers定義を自動生成
ユースケースからProtobuf定義を生成
Antigravityに自然言語でユースケースを記述すると、Protocol Buffers定義を自動生成できます:
ECサイトの注文管理マイクロサービスのProtocol Buffers定義を作成して。
- 注文の作成・取得・一覧・更新・キャンセル
- 注文ステータスの変更通知(Server Streaming)
- 注文アイテムには商品ID、数量、単価を含む
- ページネーション対応の一覧API
- 認証用のメタデータ対応
Antigravityが生成するProtobuf定義:
// proto/order/v1/order.proto
syntax = "proto3";
package order.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
option go_package = "github.com/example/order-service/gen/order/v1;orderv1";
// 注文サービス定義
service OrderService {
// 注文作成
rpc CreateOrder(CreateOrderRequest) returns (Order);
// 注文取得
rpc GetOrder(GetOrderRequest) returns (Order);
// 注文一覧
rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse);
// 注文更新
rpc UpdateOrder(UpdateOrderRequest) returns (Order);
// 注文キャンセル
rpc CancelOrder(CancelOrderRequest) returns (Order);
// 注文ステータス変更通知(Server Streaming)
rpc WatchOrderStatus(WatchOrderStatusRequest) returns (stream OrderStatusEvent);
}
// 注文ステータス
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_CONFIRMED = 2;
ORDER_STATUS_PROCESSING = 3;
ORDER_STATUS_SHIPPED = 4;
ORDER_STATUS_DELIVERED = 5;
ORDER_STATUS_CANCELLED = 6;
}
// 注文メッセージ
message Order {
string id = 1;
string customer_id = 2;
repeated OrderItem items = 3;
OrderStatus status = 4;
int64 total_amount = 5; // 税込合計(円)
string shipping_address = 6;
google.protobuf.Timestamp created_at = 7;
google.protobuf.Timestamp updated_at = 8;
}
message OrderItem {
string product_id = 1;
string product_name = 2;
int32 quantity = 3;
int64 unit_price = 4; // 単価(円)
int64 subtotal = 5; // 小計(円)
}
// リクエスト・レスポンス
message CreateOrderRequest {
string customer_id = 1;
repeated OrderItemInput items = 2;
string shipping_address = 3;
}
message OrderItemInput {
string product_id = 1;
int32 quantity = 2;
}
message GetOrderRequest {
string id = 1;
}
message ListOrdersRequest {
string customer_id = 1;
OrderStatus status_filter = 2;
int32 page_size = 3;
string page_token = 4;
}
message ListOrdersResponse {
repeated Order orders = 1;
string next_page_token = 2;
int32 total_count = 3;
}
message UpdateOrderRequest {
string id = 1;
string shipping_address = 2;
}
message CancelOrderRequest {
string id = 1;
string reason = 2;
}
message WatchOrderStatusRequest {
string order_id = 1;
}
message OrderStatusEvent {
string order_id = 1;
OrderStatus previous_status = 2;
OrderStatus current_status = 3;
google.protobuf.Timestamp changed_at = 4;
string message = 5;
}
buf.yamlの設定自動化
Antigravityに「bufのプロジェクト設定もセットアップして」と指示すると、Buf CLI用の設定も自動生成します:
# buf.yaml
version: v2
modules:
- path: proto
lint:
use:
- STANDARD
except:
- PACKAGE_VERSION_SUFFIX
breaking:
use:
- FILE
# buf.gen.yaml
version: v2
managed:
enabled: true
plugins:
- remote: buf.build/protocolbuffers/go
out: gen
opt: paths=source_relative
- remote: buf.build/grpc/go
out: gen
opt: paths=source_relative
- remote: buf.build/connectrpc/go
out: gen
opt: paths=source_relative
gRPCサーバーの実装
Go + Connect RPC で高性能サーバーを構築
Antigravityに「生成したProtobufからGoのgRPCサーバーを実装して」と指示します:
Goでorder.v1.OrderServiceのgRPCサーバーを実装して。
- Connect RPCフレームワークを使用
- PostgreSQLをデータストアとして
- 構造化ログ(slog)
- OpenTelemetryトレーシング
- ページネーションはカーソルベース
// internal/service/order.go
package service
import (
"context"
"fmt"
"log/slog"
"time"
"connectrpc.com/connect"
orderv1 "github.com/example/order-service/gen/order/v1"
"github.com/example/order-service/internal/repository"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"google.golang.org/protobuf/types/known/timestamppb"
)
var tracer = otel.Tracer("order-service")
type OrderService struct {
repo repository.OrderRepository
logger *slog.Logger
}
func NewOrderService(repo repository.OrderRepository, logger *slog.Logger) *OrderService {
return &OrderService{repo: repo, logger: logger}
}
func (s *OrderService) CreateOrder(
ctx context.Context,
req *connect.Request[orderv1.CreateOrderRequest],
) (*connect.Response[orderv1.Order], error) {
ctx, span := tracer.Start(ctx, "OrderService.CreateOrder")
defer span.End()
msg := req.Msg
s.logger.InfoContext(ctx, "注文作成開始",
slog.String("customer_id", msg.CustomerId),
slog.Int("item_count", len(msg.Items)),
)
// バリデーション
if msg.CustomerId == "" {
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("customer_id is required"))
}
if len(msg.Items) == 0 {
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("at least one item is required"))
}
// 注文アイテムの構築
items := make([]repository.OrderItem, len(msg.Items))
var totalAmount int64
for i, item := range msg.Items {
product, err := s.repo.GetProduct(ctx, item.ProductId)
if err != nil {
return nil, connect.NewError(connect.CodeNotFound,
fmt.Errorf("product %s not found", item.ProductId))
}
subtotal := product.Price * int64(item.Quantity)
items[i] = repository.OrderItem{
ProductID: item.ProductId,
ProductName: product.Name,
Quantity: item.Quantity,
UnitPrice: product.Price,
Subtotal: subtotal,
}
totalAmount += subtotal
}
span.SetAttributes(attribute.Int64("order.total_amount", totalAmount))
// 注文をDBに保存
order, err := s.repo.CreateOrder(ctx, repository.CreateOrderInput{
CustomerID: msg.CustomerId,
Items: items,
TotalAmount: totalAmount,
ShippingAddress: msg.ShippingAddress,
})
if err != nil {
s.logger.ErrorContext(ctx, "注文作成失敗", slog.Any("error", err))
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to create order"))
}
s.logger.InfoContext(ctx, "注文作成完了",
slog.String("order_id", order.ID),
slog.Int64("total_amount", totalAmount),
)
return connect.NewResponse(toProtoOrder(order)), nil
}
func (s *OrderService) GetOrder(
ctx context.Context,
req *connect.Request[orderv1.GetOrderRequest],
) (*connect.Response[orderv1.Order], error) {
ctx, span := tracer.Start(ctx, "OrderService.GetOrder")
defer span.End()
order, err := s.repo.GetOrder(ctx, req.Msg.Id)
if err != nil {
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("order not found"))
}
return connect.NewResponse(toProtoOrder(order)), nil
}
func (s *OrderService) WatchOrderStatus(
ctx context.Context,
req *connect.Request[orderv1.WatchOrderStatusRequest],
stream *connect.ServerStream[orderv1.OrderStatusEvent],
) error {
ctx, span := tracer.Start(ctx, "OrderService.WatchOrderStatus")
defer span.End()
orderID := req.Msg.OrderId
s.logger.InfoContext(ctx, "ステータス監視開始", slog.String("order_id", orderID))
// ステータス変更チャネルを購読
events, cancel := s.repo.SubscribeOrderStatus(ctx, orderID)
defer cancel()
for {
select {
case <-ctx.Done():
return nil
case event, ok := <-events:
if !ok {
return nil
}
if err := stream.Send(&orderv1.OrderStatusEvent{
OrderId: orderID,
PreviousStatus: toProtoStatus(event.PreviousStatus),
CurrentStatus: toProtoStatus(event.CurrentStatus),
ChangedAt: timestamppb.New(event.ChangedAt),
Message: event.Message,
}); err != nil {
return err
}
}
}
}
func toProtoOrder(o *repository.Order) *orderv1.Order {
items := make([]*orderv1.OrderItem, len(o.Items))
for i, item := range o.Items {
items[i] = &orderv1.OrderItem{
ProductId: item.ProductID,
ProductName: item.ProductName,
Quantity: item.Quantity,
UnitPrice: item.UnitPrice,
Subtotal: item.Subtotal,
}
}
return &orderv1.Order{
Id: o.ID,
CustomerId: o.CustomerID,
Items: items,
Status: toProtoStatus(o.Status),
TotalAmount: o.TotalAmount,
ShippingAddress: o.ShippingAddress,
CreatedAt: timestamppb.New(o.CreatedAt),
UpdatedAt: timestamppb.New(o.UpdatedAt),
}
}
func toProtoStatus(s string) orderv1.OrderStatus {
switch s {
case "pending":
return orderv1.OrderStatus_ORDER_STATUS_PENDING
case "confirmed":
return orderv1.OrderStatus_ORDER_STATUS_CONFIRMED
case "processing":
return orderv1.OrderStatus_ORDER_STATUS_PROCESSING
case "shipped":
return orderv1.OrderStatus_ORDER_STATUS_SHIPPED
case "delivered":
return orderv1.OrderStatus_ORDER_STATUS_DELIVERED
case "cancelled":
return orderv1.OrderStatus_ORDER_STATUS_CANCELLED
default:
return orderv1.OrderStatus_ORDER_STATUS_UNSPECIFIED
}
}
gRPCのテスト自動化
ユニットテストの自動生成
Antigravityに「OrderServiceのユニットテストを網羅的に生成して」と指示します:
// internal/service/order_test.go
package service_test
import (
"context"
"testing"
"connectrpc.com/connect"
orderv1 "github.com/example/order-service/gen/order/v1"
"github.com/example/order-service/internal/service"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCreateOrder_Success(t *testing.T) {
repo := newMockRepo()
svc := service.NewOrderService(repo, testLogger())
req := connect.NewRequest(&orderv1.CreateOrderRequest{
CustomerId: "customer-1",
Items: []*orderv1.OrderItemInput{
{ProductId: "product-1", Quantity: 2},
{ProductId: "product-2", Quantity: 1},
},
ShippingAddress: "東京都渋谷区...",
})
resp, err := svc.CreateOrder(context.Background(), req)
require.NoError(t, err)
assert.NotEmpty(t, resp.Msg.Id)
assert.Equal(t, orderv1.OrderStatus_ORDER_STATUS_PENDING, resp.Msg.Status)
assert.Len(t, resp.Msg.Items, 2)
}
func TestCreateOrder_EmptyItems(t *testing.T) {
repo := newMockRepo()
svc := service.NewOrderService(repo, testLogger())
req := connect.NewRequest(&orderv1.CreateOrderRequest{
CustomerId: "customer-1",
Items: []*orderv1.OrderItemInput{},
})
_, err := svc.CreateOrder(context.Background(), req)
require.Error(t, err)
assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
}
func TestGetOrder_NotFound(t *testing.T) {
repo := newMockRepo()
svc := service.NewOrderService(repo, testLogger())
req := connect.NewRequest(&orderv1.GetOrderRequest{
Id: "non-existent",
})
_, err := svc.GetOrder(context.Background(), req)
require.Error(t, err)
assert.Equal(t, connect.CodeNotFound, connect.CodeOf(err))
}
gRPCurlによるE2Eテスト
# grpcurl でサービスの動作確認
grpcurl -plaintext -d '{
"customer_id": "customer-1",
"items": [
{"product_id": "product-1", "quantity": 2}
],
"shipping_address": "東京都渋谷区..."
}' localhost:8080 order.v1.OrderService/CreateOrder
gRPC-WebでブラウザからgRPCを呼ぶ
Connect-Webの導入
ブラウザからgRPCサービスを呼び出すには、Connect-Webが最適です:
// frontend/src/api/orderClient.ts
import { createConnectTransport } from '@connectrpc/connect-web';
import { createClient } from '@connectrpc/connect';
import { OrderService } from '../gen/order/v1/order_connect';
const transport = createConnectTransport({
baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080',
});
export const orderClient = createClient(OrderService, transport);
// 使用例
export async function createOrder(customerId: string, items: OrderItemInput[]) {
const response = await orderClient.createOrder({
customerId,
items,
shippingAddress: '東京都渋谷区...',
});
return response;
}
// Server Streaming の使用例
export async function* watchOrderStatus(orderId: string) {
for await (const event of orderClient.watchOrderStatus({ orderId })) {
yield {
orderId: event.orderId,
previousStatus: event.previousStatus,
currentStatus: event.currentStatus,
changedAt: event.changedAt?.toDate(),
message: event.message,
};
}
}
REST APIからgRPCへの段階的移行
gRPC-GatewayでREST互換性を維持
既存のRESTクライアントとの互換性を維持しながらgRPCに移行するパターン:
// REST互換アノテーション
import "google/api/annotations.proto";
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (Order) {
option (google.api.http) = {
post: "/v1/orders"
body: "*"
};
}
rpc GetOrder(GetOrderRequest) returns (Order) {
option (google.api.http) = {
get: "/v1/orders/{id}"
};
}
rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse) {
option (google.api.http) = {
get: "/v1/orders"
};
}
}
これにより、同じサービス定義からgRPCエンドポイントとREST APIの両方を自動生成できます。
SES現場でのgRPCプロジェクト実績の作り方
ポートフォリオ構築
gRPCスキルをアピールするためのプロジェクト例:
- マイクロサービスサンプル: 3-4サービスのgRPC通信、サービスメッシュ対応
- ストリーミングアプリ: チャットやリアルタイム通知のServer Streaming実装
- gRPC-Gateway: REST互換レイヤーの構築と負荷テスト
- Buf + CI/CD: Protocol Buffersのlint、breaking change検出の自動化
gRPCスキルのキャリアパス
| レベル | 月単価 | 求められるスキル |
|---|---|---|
| ジュニア | 60〜80万円 | protobuf定義、Unary RPC実装 |
| ミドル | 80〜100万円 | ストリーミング、テスト、CI/CD統合 |
| シニア | 100〜130万円 | サービスメッシュ、パフォーマンスチューニング、設計レビュー |

まとめ
Google Antigravityを活用することで、gRPC開発の学習コストと作業量を大幅に削減できます。
- Protocol Buffers: 自然言語から.protoファイルを自動生成
- サーバー実装: Connect RPCフレームワークでGoサーバーを効率的に実装
- テスト: ユニットテスト、E2Eテストの自動生成
- フロントエンド連携: Connect-WebでブラウザからシームレスにgRPCを呼び出し
gRPCは2026年のSES現場で最も需要が高いスキルの一つです。Antigravityの支援を活用して、効率的にスキルアップしていきましょう。
SES BASEでは、Go/gRPC、マイクロサービス、Kubernetesの高単価案件を多数掲載しています。gRPCスキルを活かせる案件をチェックしましょう。