# 2026年版 NestJS マイクロサービス:アーキテクチャ、gRPC、面接対策ガイド > NestJS マイクロサービスのアーキテクチャ設計、gRPC トランスポートの構成、ストリーミングパターン、信頼性パターン、技術面接の頻出質問を体系的に解説します。 - Published: 2026-05-26 - Updated: 2026-05-26 - Author: SharpSkill - Tags: nestjs, microservices, grpc, node.js, typescript, architecture - Reading time: 10 min --- 2026年現在、エンタープライズシステムの開発現場では、モノリシックアーキテクチャからマイクロサービスへの移行が加速しています。とりわけ Node.js エコシステムにおいて、NestJS はそのモジュール設計と充実した抽象化レイヤーにより、マイクロサービス基盤として高い評価を得ています。NestJS が提供するトランスポート抽象化により、TCP、Redis、NATS、Kafka、gRPC といった多様な通信プロトコルを統一的な API で扱うことが可能です。本記事では、NestJS を活用したマイクロサービスのアーキテクチャ設計から gRPC の実装、ストリーミングパターン、信頼性の確保、そして技術面接で問われる頻出質問までを体系的に解説します。 > **gRPC と REST の使い分け** > > サービス間通信には gRPC、外部クライアント向けには REST を採用するのが現代的なマイクロサービスの定石です。gRPC は Protocol Buffers によるバイナリシリアライゼーションと HTTP/2 のマルチプレキシングにより、JSON ベースの REST と比較してレイテンシとペイロードサイズを大幅に削減できます。一方、REST はブラウザとの互換性やデバッグの容易さという利点を持ちます。両者を適材適所で組み合わせることが実践的なアプローチです。 ## NestJS マイクロサービスのトランスポートレイヤーアーキテクチャ NestJS のマイクロサービスモジュールは、サービス間通信の実装詳細をトランスポートレイヤーとして抽象化しています。この設計により、アプリケーションのビジネスロジックをトランスポートの種類から完全に分離できます。トランスポートを TCP から Redis に変更する場合でも、コントローラーやサービス層のコードを書き換える必要はありません。 このアーキテクチャの中核を担うのが、`@MessagePattern()` と `@EventPattern()` という2つのデコレータです。`@MessagePattern()` はリクエスト・レスポンス型の通信を実現します。呼び出し元のサービスはレスポンスを受け取るまで待機するため、注文の作成やユーザー情報の取得といった、結果を即座に必要とする処理に適しています。一方、`@EventPattern()` はイベント駆動型の非同期通信に使用されます。レスポンスを返さないファイア・アンド・フォーゲット方式であり、通知の送信やログの記録など、処理の完了を待つ必要がないケースに向いています。 ```typescript // orders.controller.ts import { Controller } from '@nestjs/common'; import { MessagePattern, EventPattern, Payload } from '@nestjs/microservices'; import { OrdersService } from './orders.service'; import { CreateOrderDto } from './dto/create-order.dto'; @Controller() export class OrdersController { constructor(private readonly ordersService: OrdersService) {} // Request-response: caller waits for the created order @MessagePattern('order.create') async createOrder(@Payload() data: CreateOrderDto) { return this.ordersService.create(data); } // Event-based: fire and forget, no response returned @EventPattern('order.shipped') async handleOrderShipped(@Payload() data: { orderId: string }) { await this.ordersService.markAsShipped(data.orderId); } } ``` 上記のコントローラーでは、`order.create` メッセージパターンに対して注文を作成し、その結果を呼び出し元へ返却しています。一方、`order.shipped` イベントを受信した場合は、注文を出荷済みに更新するだけでレスポンスは返しません。この2つのデコレータを使い分けることで、サービス間の結合度を最小限に抑えつつ、ユースケースに応じた最適な通信方式を選択できます。 なお、`@Payload()` デコレータはメッセージ本文からデータを抽出する役割を担います。バリデーションパイプと組み合わせることで、受信データの型安全性を確保することも可能です。 ## NestJS マイクロサービスにおける gRPC トランスポートの設定 gRPC は Google が開発した高性能 RPC フレームワークであり、Protocol Buffers(protobuf)をインターフェース定義言語として使用します。HTTP/2 を基盤とするため、接続のマルチプレキシング、ヘッダー圧縮、双方向ストリーミングといった機能を標準で利用できます。 NestJS で gRPC トランスポートを導入するには、最初にサービスのインターフェースを `.proto` ファイルで定義します。この定義がサービス間のコントラクト(契約)となり、クライアントとサーバーの双方がこのスキーマに基づいて通信を行います。 ```protobuf // proto/users.proto syntax = "proto3"; package users; service UsersService { rpc FindOne (UserById) returns (User); rpc FindMany (UserFilter) returns (stream User); } message UserById { string id = 1; } message UserFilter { string role = 1; int32 limit = 2; } message User { string id = 1; string email = 2; string name = 3; string role = 4; } ``` この proto ファイルでは、`UsersService` に2つの RPC メソッドを定義しています。`FindOne` は単一のユーザーを返すユナリー RPC であり、`FindMany` は条件に合致するユーザーをストリームで返すサーバーストリーミング RPC です。Protocol Buffers はスキーマファーストのアプローチを強制するため、サービス間のインターフェースが明確に文書化されるという副次的な利点もあります。 > **Protocol Buffers のパフォーマンス特性** > > Protocol Buffers はバイナリ形式でデータをシリアライズするため、JSON と比較してペイロードサイズが平均で 30-80% 削減されます。また、シリアライズ・デシリアライズの処理速度も数倍高速です。マイクロサービス間で大量のデータを交換するシステムでは、この差が全体のレイテンシに大きく影響します。 次に、NestJS アプリケーションを gRPC マイクロサービスとして起動するためのブートストラップコードを記述します。 ```typescript // main.ts import { NestFactory } from '@nestjs/core'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { join } from 'path'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.createMicroservice( AppModule, { transport: Transport.GRPC, options: { package: 'users', protoPath: join(__dirname, 'proto/users.proto'), url: '0.0.0.0:5000', }, }, ); await app.listen(); } bootstrap(); ``` `Transport.GRPC` を指定し、`package` には proto ファイルのパッケージ名、`protoPath` には proto ファイルのパスをそれぞれ設定します。`url` はサービスがリッスンするアドレスとポートです。 コントローラーでは、`@GrpcMethod()` デコレータを使用して proto ファイルで定義した RPC メソッドとハンドラーを対応付けます。 ```typescript // users.controller.ts import { Controller } from '@nestjs/common'; import { GrpcMethod } from '@nestjs/microservices'; import { UsersService } from './users.service'; @Controller() export class UsersController { constructor(private readonly usersService: UsersService) {} @GrpcMethod('UsersService', 'FindOne') async findOne(data: { id: string }) { return this.usersService.findById(data.id); } } ``` `@GrpcMethod()` の第1引数はサービス名、第2引数は RPC メソッド名です。proto ファイルの定義と正確に一致させる必要があります。戻り値のオブジェクトは NestJS が自動的に Protocol Buffers 形式にシリアライズしてクライアントに返却します。 ## NestJS での gRPC ストリーミングパターン gRPC が REST に対して持つ決定的な優位性の一つが、ストリーミング通信のネイティブサポートです。gRPC には4つの通信パターンがあります。ユナリー(単一リクエスト・単一レスポンス)、サーバーストリーミング(単一リクエスト・複数レスポンス)、クライアントストリーミング(複数リクエスト・単一レスポンス)、双方向ストリーミング(複数リクエスト・複数レスポンス)です。 NestJS では、RxJS の `Observable` を戻り値として返すことで、サーバーストリーミングを宣言的に実装できます。 ```typescript // users.controller.ts — server streaming import { Observable, from } from 'rxjs'; import { map } from 'rxjs/operators'; import { GrpcMethod } from '@nestjs/microservices'; @GrpcMethod('UsersService', 'FindMany') findMany(data: { role: string; limit: number }): Observable { // Stream users matching the filter one by one const users$ = from(this.usersService.findByRole(data.role, data.limit)); return users$.pipe( map((user) => ({ id: user.id, email: user.email, name: user.name, role: user.role, })), ); } ``` このパターンでは、フィルタ条件に合致するユーザーを一度にまとめて返すのではなく、1件ずつストリームとして送信します。クライアントはデータが到着するたびに逐次処理を開始できるため、最初のレスポンスまでの待ち時間(Time to First Byte)が短縮されます。数千件単位のデータを返却するケースでは、メモリ消費量の観点でも有利です。 双方向ストリーミングでは、クライアントとサーバーが同時にデータを送受信できます。リアルタイムチャットや株価のライブフィードなど、双方向の継続的なデータフローが必要なユースケースに適しています。NestJS では `Subject` を活用して双方向ストリーミングを実装することが可能です。 ## ハイブリッドアプリケーション:同一サービスでの HTTP と gRPC プロダクション環境では、1つのサービスが HTTP エンドポイントと gRPC エンドポイントの両方を提供する必要がある場面は珍しくありません。外部のフロントエンドアプリケーションには REST API を公開し、内部のマイクロサービス間通信には gRPC を使用するというアーキテクチャは、パフォーマンスと互換性を両立させる現実的な選択肢です。 NestJS はこのハイブリッドアプリケーションパターンをフレームワークレベルでサポートしています。`NestFactory.create()` で HTTP サーバーを作成した後、`connectMicroservice()` で gRPC トランスポートを追加接続します。 ```typescript // main.ts — hybrid application import { NestFactory } from '@nestjs/core'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { join } from 'path'; import { AppModule } from './app.module'; async function bootstrap() { // HTTP server on port 3000 const app = await NestFactory.create(AppModule); // gRPC microservice on port 5000 app.connectMicroservice({ transport: Transport.GRPC, options: { package: 'users', protoPath: join(__dirname, 'proto/users.proto'), url: '0.0.0.0:5000', }, }); await app.startAllMicroservices(); await app.listen(3000); } bootstrap(); ``` この構成では、ポート 3000 で HTTP リクエストを処理し、ポート 5000 で gRPC 呼び出しを受け付けます。`startAllMicroservices()` がすべてのマイクロサービストランスポートを起動し、その後 `listen(3000)` が HTTP サーバーを開始します。同一のサービス層やリポジトリをHTTP コントローラーと gRPC コントローラーの双方から利用できるため、コードの重複を回避しながら複数プロトコルに対応できます。 > **ハイブリッド構成の注意点** > > ハイブリッドアプリケーションでは、ヘルスチェックエンドポイントを HTTP 側と gRPC 側の両方に用意することが推奨されます。Kubernetes の readiness/liveness probe は HTTP で行い、gRPC のヘルスチェックには grpc-health-check プロトコルを使用すると、運用監視が容易になります。 ## サービス境界とドメイン駆動設計 マイクロサービスアーキテクチャにおいて、技術的な実装と同等に重要なのがサービス境界の設計です。不適切な境界設定は、分散モノリスと呼ばれるアンチパターンを生み出し、モノリスの単純さとマイクロサービスの利点の両方を失う結果を招きます。 ドメイン駆動設計(DDD)の「境界づけられたコンテキスト」(Bounded Context)は、サービス分割の指針として広く採用されています。各マイクロサービスは単一のビジネスドメインに対して責任を持ち、自身のデータストアを独立して管理します。これは「Database per Service」パターンとして知られ、サービス間のデータ結合を排除するための基本原則です。 具体的には、EC サイトであれば「注文」「在庫」「ユーザー」「決済」「通知」といった単位でサービスを分割します。各サービスは自身のデータベースを所有し、他のサービスのデータベースに直接アクセスすることはありません。サービス間のデータ参照が必要な場合は、gRPC やメッセージキューを通じた非同期通信で行います。 NestJS のモジュールシステムは、この境界づけられたコンテキストの実装と親和性が高い設計となっています。各ドメインを独立した NestJS モジュールとして開発し、モジュール間の依存関係を最小化した上で、スケーリングが必要になった段階で個別のマイクロサービスとして切り出すことが可能です。この段階的なアプローチは「モノリスファースト」戦略と呼ばれ、ドメインの理解が不十分な初期段階での過度な分割を回避できます。 ## 信頼性パターン:タイムアウト、リトライ、サーキットブレーカー 分散システムではネットワーク障害が日常的に発生します。単一プロセスのモノリスでは関数呼び出しが失敗することはほとんどありませんが、マイクロサービス間のリモート呼び出しは、ネットワーク遅延、一時的な障害、下流サービスのダウンなど、多様な理由で失敗し得ます。堅牢なマイクロサービスを構築するためには、タイムアウト、リトライ、サーキットブレーカーといった信頼性パターンを実装する必要があります。 NestJS では、RxJS のオペレータを活用してこれらのパターンを宣言的に組み込むことができます。 ```typescript // orders.service.ts import { Inject, Injectable } from '@nestjs/common'; import { ClientGrpc } from '@nestjs/microservices'; import { firstValueFrom, timeout, retry } from 'rxjs'; @Injectable() export class OrdersService { private usersService: any; constructor(@Inject('USERS_PACKAGE') private client: ClientGrpc) {} onModuleInit() { this.usersService = this.client.getService('UsersService'); } async getOrderWithUser(orderId: string, userId: string) { // 3-second deadline, 2 retries with exponential backoff const user = await firstValueFrom( this.usersService.findOne({ id: userId }).pipe( timeout(3000), retry({ count: 2, delay: (err, retryCount) => { const jitter = Math.random() * 100; return new Promise(r => setTimeout(r, 1000 * retryCount + jitter)); }}), ), ); return { orderId, user }; } } ``` 上記の実装では、3つの信頼性メカニズムが組み合わされています。`timeout(3000)` により、3秒以内にレスポンスが返らない場合は `TimeoutError` がスローされます。`retry` オペレータは最大2回のリトライを行い、リトライ間隔は指数バックオフ(1秒、2秒と増加)で制御されます。さらに、ジッター(ランダムな遅延の上乗せ)を加えることで、複数のクライアントが同時にリトライを行う「リトライストーム」を防止しています。 サーキットブレーカーパターンについては、NestJS には標準の実装が含まれていませんが、`opossum` や `cockatiel` といったライブラリを導入することで実装できます。サーキットブレーカーは、連続した障害が閾値を超えた場合に回路を「オープン」状態に切り替え、一定期間すべてのリクエストを即座に拒否します。これにより、障害が発生している下流サービスへの無駄なリクエストを抑止し、カスケード障害(障害の連鎖的波及)を防止します。 ## NestJS マイクロサービスの面接質問 NestJS を用いたマイクロサービス開発に関する技術面接では、フレームワークの機能理解だけでなく、分散システムの設計原則に対する深い理解が求められます。以下に、頻出する質問と模範的な回答の要点を整理します。 **Q1: `@MessagePattern()` と `@EventPattern()` の違いは何ですか?それぞれの適用場面を説明してください。** `@MessagePattern()` はリクエスト・レスポンス型の同期通信に使用します。呼び出し元はレスポンスを受け取るまで処理を待機します。注文の作成やユーザー認証など、処理結果を呼び出し元が必要とするケースに適しています。`@EventPattern()` はイベント駆動型の非同期通信に使用します。レスポンスを返さないファイア・アンド・フォーゲット方式であり、通知メール送信や監査ログの記録など、結果を待つ必要がない処理に向いています。設計上の判断基準は「呼び出し元がレスポンスを必要とするかどうか」です。 **Q2: NestJS でサポートされるトランスポートプロトコルと、それぞれの選定基準を教えてください。** NestJS は TCP、Redis、NATS、RabbitMQ(AMQP)、Kafka、gRPC のトランスポートをサポートしています。TCP は最もシンプルで外部依存がなく、小規模なシステムに適しています。Redis はPub/Sub による軽量なメッセージングに、NATS は高スループットかつ低レイテンシの通信に優れています。RabbitMQ はメッセージの永続化やルーティングが必要な場面で選択されます。Kafka はイベントソーシングや大規模ストリーミングに最適です。gRPC は型安全性と高パフォーマンスが求められるサービス間通信に適しています。 **Q3: gRPC が REST よりも適しているのはどのような場面ですか?** gRPC は Protocol Buffers によるバイナリシリアライゼーションを使用するため、JSON ベースの REST と比較してペイロードサイズが小さく、シリアライズ速度も高速です。HTTP/2 のマルチプレキシングにより、単一の TCP 接続上で複数のリクエストを並行処理できます。さらに、双方向ストリーミングのネイティブサポートにより、リアルタイムデータの配信にも対応可能です。ただし、ブラウザからの直接アクセスには向かないため、サービス間通信に使用し、外部向けには REST や GraphQL を併用するのが一般的です。 **Q4: マイクロサービスにおける分散トランザクションの処理方法について説明してください。** 分散トランザクションは Saga パターンで処理するのが標準的なアプローチです。コレオグラフィー方式では、各サービスが処理完了後にイベントを発行し、次のサービスがそれを購読して処理を継続します。サービス間の直接的な依存関係がなく、疎結合を維持できます。オーケストレーション方式では、中央の Saga オーケストレータがトランザクション全体のフローを制御します。処理が途中で失敗した場合は、補償トランザクション(compensating transaction)を実行して、それまでに完了した処理を取り消します。 **Q5: サーキットブレーカーパターンの動作原理と、マイクロサービスにおける必要性を説明してください。** サーキットブレーカーは、下流サービスの障害がシステム全体に波及するカスケード障害を防止するためのパターンです。「クローズド」状態ではリクエストを通常通り転送します。障害率が閾値を超えると「オープン」状態に遷移し、すべてのリクエストを即座に拒否してフォールバック値を返します。一定時間経過後に「ハーフオープン」状態となり、少数のリクエストを試行して下流サービスの回復を確認します。回復が確認されれば「クローズド」に戻り、依然として障害が続いていれば再度「オープン」になります。 ## まとめ 本記事では、NestJS を基盤としたマイクロサービスアーキテクチャの主要な概念と実装パターンを解説しました。 - **トランスポートレイヤー**: `@MessagePattern()` によるリクエスト・レスポンス通信と `@EventPattern()` によるイベント駆動通信を使い分けることで、サービス間の結合度を適切に制御できます - **gRPC トランスポート**: Protocol Buffers によるスキーマ定義と NestJS の `@GrpcMethod()` デコレータにより、型安全で高性能なサービス間通信を構築できます - **ストリーミング**: RxJS の Observable を活用したサーバーストリーミングにより、大量データの逐次配信とメモリ効率の向上を実現できます - **ハイブリッドアプリケーション**: HTTP と gRPC を同一プロセスで提供することで、外部向け REST API と内部向け gRPC を1つのコードベースで管理できます - **サービス境界**: DDD の境界づけられたコンテキストに基づいた設計と、Database per Service パターンの採用が、分散モノリスの回避に不可欠です - **信頼性パターン**: タイムアウト、指数バックオフ付きリトライ、サーキットブレーカーの3層構造により、分散環境特有の障害に対する耐性を確保できます - **面接対策**: フレームワークの API だけでなく、トランスポートの選定基準、Saga パターン、サーキットブレーカーといった分散システム設計の原則に対する理解が重要です --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ja/blog/node-nestjs/nestjs-microservices-grpc-architecture