7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring Boot で始める Dapr 入門:WSL 環境でマイクロサービス開発

7
Last updated at Posted at 2025-11-07

はじめに

マイクロサービスアーキテクチャでは、サービス間通信、状態管理、非同期メッセージングなど、ビジネスロジック以外の共通機能の実装に多くの時間を費やします。
さらに、ローカル環境と本番環境で異なるコードを書いたり、インフラ変更のたびにアプリケーションコードの修正が必要になるケースも少なくありません。

Dapr(Distributed Application Runtime) は、こうした課題を解決するランタイムです。
2024 年に Kubernetes や Prometheus と並ぶ CNCF プロジェクト1となり、現在急速に利用拡大されている注目の技術です。
私が関わっているプロジェクトでも Dapr を採用し、インフラ変更に強く、開発効率の高いサービス開発を実現しています。

私たちは Windows を開発環境としており、Docker や Linux ツールを活用するため WSL(Windows Subsystem for Linux)を日常的に利用しています。
Dapr は WSL 上で利用できますが、WSL × Dapr の情報は少なく、実際に動かそうとすると意外なところでハマります。ドキュメントに詳しく載っていない部分も多く、私も試行錯誤の連続でした。
本記事では、こうした実践で得た知見をもとに、Spring Boot を前提とした WSL 環境での Dapr 活用方法を紹介します。

本記事で学べること

  • Dapr の基本思想とマイクロサービス開発における価値
  • WSL 環境での Dapr のセットアップ
  • Spring Boot アプリケーションでの Dapr 活用

前提条件

  • Windows 11 (22H2以降) + WSL2 環境
  • WSL 内に Docker 導入済み
  • Java 開発環境(JDK 17 以上)
  • アプリケーション開発は Windows ホスト上で実施
  • Dapr 1.17

Dapr とは何か

Dapr の基本思想

Dapr(Distributed Application Runtime)は、マイクロサービスアプリケーション開発を支援するオープンソースのランタイムです。マイクロサービスアーキテクチャにおいて、共通的に必要となる機能を Building Blocks として提供しています。

Dapr 概要図

サービスを開発する際、以下のような課題に直面したことはないでしょうか?

よくある課題:

  • サービス間通信:URL 管理やサービスディスカバリやリトライ処理などを自前で実装
  • 外部サービスごとの実装:Redis、DynamoDB など、接続先ごとに異なるコードが必要
  • インフラ変更の影響:RabbitMQ から Kafka への変更で大規模なコード修正が発生

Dapr による解決:

  • 統一された API で実装を抽象化

    // Before: ストアごとに異なる実装
    redisClient.set("user:123", userData);    // Redis 用
    dynamoDBClient.putItem(request);          // DynamoDB(AWS)用
    // ※CosmosDB(Azure)、Firestore(GCP)など、クラウドプロバイダーごとに異なるコードが必要
    
    // After: どのストアでも同じコード
    daprClient.saveState("statestore", "user:123", userData).block();
    
  • 設定変更だけでインフラを切り替え

    メッセージブローカーを RabbitMQ → Kafka に変更する場合も、設定ファイル(pubsub.yaml)を変更するだけです。

    # pubsub.yaml の例
    apiVersion: dapr.io/v1alpha1
    kind: Component
    metadata:
      name: pubsub
    spec:
      type: pubsub.rabbitmq
      # ... 詳細は後述
    
    // アプリケーションコードは変更不要
    daprClient.publishEvent("pubsub", "orders", order).block();
    

この抽象化により、インフラ変更に強くビジネスロジックに集中できる設計が実現できます。

Dapr の動作モデル

Dapr は、アプリケーションとは別プロセスとして動作し、HTTP/gRPC 経由で通信します。
別プロセスで動作することで以下のメリットがあります。

  • 言語やフレームワークに依存しない: Java、Python、Go、.NET、Node.js など、HTTP/gRPC が使える言語ならどれでも OK
  • 既存アプリケーションへの組み込みが容易: アプリケーションコードの大幅な変更なしに Dapr の機能を利用可能
  • 独立したライフサイクル管理: Dapr のバージョンアップがアプリケーションのデプロイに影響しない

本記事では Spring Boot での実装例を紹介しますが、同じ考え方は他の言語でも適用できます。

📝 ローカル開発環境での構成

本記事では、Dapr ランタイムと外部リソース(Redis、RabbitMQ 等)は WSL 内で動作させ、Spring Boot アプリケーションは Windows ホスト上で開発する構成を採用しています。
もちろん、すべてを WSL 内で開発することも可能なのですが、起動の速さやデバッグのしやすさなど開発体験を考慮した結果、この開発スタイルに落ち着きました。

Project-local-Architecture.png

Kubernetes 環境では、Dapr は Pod 内のサイドカーコンテナとして動作します。しかし、ローカル開発ではプロセスレベルで分離されているだけです。いずれの環境でも、アプリケーションからの使い方は同じです。

なお、Dapr は Kubernetes およびセルフホスト環境を正式サポートしていますが、
AWS ECS などのコンテナ基盤には現時点で対応していません。
詳細は公式ドキュメント(Hosting environments)を参照してください。

Dapr の主要な Building Blocks

Dapr は以下のような「Building Blocks 」と呼ばれる機能を提供します。

Building Block 説明 用途
Service Invocation サービス間の同期呼び出し マイクロサービス間の API 呼び出し
Pub/Sub 非同期メッセージング イベント駆動アーキテクチャ
State Management 状態管理 データの永続化・取得
Bindings 外部システム連携 外部サービスのトリガー/出力
Secrets シークレット管理 認証情報の安全な管理

今回は、特によく使うService InvocationPub/SubState Managementに焦点を当てます。

環境構築

以下を参考に構築します。

導入方法について
Dapr は Docker、Kubernetes、セルフホストなど複数の導入方法を提供しています。本記事では、WSL 環境での開発を前提としているため、WSL 内に Dapr CLI をインストールし、Dapr ランタイムとミドルウェアは Docker コンテナとして実行する方法を採用しています。

Dapr CLI のインストール

WSL 環境で Dapr CLI をインストールします。

# WSL ターミナルで実行
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash

# バージョン確認
dapr --version

Dapr ランタイムの初期化

# Dapr 開発環境の初期化
dapr init

# 確認
dapr --version

初期化すると、以下がセットアップされます

  • Dapr ランタイム(daprd)

  • Dapr Placement(Actor 用)

  • デフォルトコンポーネント(Redis、Zipkin)

  • 以下にデフォルトの Dapr コンポーネント設定ファイルが配置されます

    "<dapr init したディレクトリ>"
    └── .dapr/
        └── components/
            ├── statestore.yaml # State Management用:Redis
            └── pubsub.yaml     # pub/sub用:RabbitMQ
    

以降 Dapr に関する操作は dapr init したディレクトリをカレントディレクトリとして記載しています。

ミドルウェアの準備

Dapr pub/sub を試すには、メッセージブローカーが必要です。docker-compose 等で準備しましょう。

# docker-compose.yml
version: '3.8'

services:
  rabbitmq:
    image: rabbitmq:3-management
    container_name: rabbitmq
    hostname: rabbitmq
    ports:
      - "5672:5672"
      - "15672:15672"
    restart: unless-stopped
# WSL 内で起動
docker-compose up -d

Dapr とアプリケーションの起動方法

Dapr ランタイム(daprd)を起動するには Dapr CLI のdapr runコマンドを実行します。

# パターン 1:jar を指定してアプリケーションと Dapr を一括起動
## app.jar は WSL 内に配置
dapr run --app-id myapp --app-port 8080 --dapr-http-port 3500 -- java -jar app.jar

# パターン 2:IDE と Dapr を別々に起動
## Dapr ランタイムだけを起動
dapr run --app-id myapp --app-port 8080 --dapr-http-port 3500

## Windows ホスト上の IDE からアプリケーションを起動
## ./gradlew bootRun(または bootRun)

Dapr ランタイムはアプリケーションごとに 1 つ必要です。複数アプリケーションの場合はそれぞれ異なるapp-id--app-port--dapr-http-port--dapr-grpc-portを指定します。

本記事では、Windows ホスト上の IDE で開発する想定のため、IDE と Dapr を別々に起動する前提で記載します。

代表的な dapr run のパラメータの説明は以下です。詳細は 公式 を参照してください。

パラメータ 役割 説明
--app-id Dapr ランタイムの識別名 サービスディスカバリで使用される論理名
--app-port アプリケーションとの接続 Dapr がアプリケーションに接続するポート
--dapr-http-port Dapr ランタイムの待受ポート Dapr の HTTP API のポート
--dapr-grpc-port Dapr ランタイムの待受ポート Dapr の gRPC API のポート
--app-channel-address アプリケーションの待受ネットワークアドレス デフォルト:127.0.0.1
--resources-path コンポーネント設定の配置ディレクトリ デフォルト:./dapr/components

--app-portは、Dapr がアプリケーションに接続するためのポート番号です。アプリケーション側の設定と一致させる必要があります。

# application.yml
server:
  port: 8080

--resources-pathは、コンポーネント設定ファイル(statestore.yaml、pubsub.yaml 等)を配置するディレクトリです。Dapr 起動時に、指定ディレクトリ内の設定ファイルが自動的に読み込まれ、コンポーネントとして登録されます。

⚠️ WSL 環境での重要な注意点

Dapr の Pub/Sub を WSL 環境で利用する場合、Subscriber 側には特別な設定が必要です。
Pub/Sub では、以下のような通信が発生します。

  1. Publisher → Dapr(WSL) → メッセージブローカー(WSL/Docker)
  2. メッセージブローカー → Dapr(WSL) → Subscriber アプリ(Windows ホスト上)

このとき、2. の通信では WSL 内の Dapr から Windows ホスト上のアプリへアクセスする必要があります
しかし、WSL のデフォルトネットワーク構成(NAT モード)では、Dapr が localhost でホスト上のアプリに到達できません。そこで、以下のいずれかの対応が必要となります。

対応1:ホストIPアドレスの明示的指定(NATモード)

Subscriber 起動時に --app-channel-address でホストマシンの IP アドレスを指定します。

HOST_IP="ホストマシンの IP" # 例: 172.XX.XXX.X

# Subscriber 用の Dapr を起動(--app-channel-address でホスト IP を指定)
dapr run --app-id subscriber --app-port 8081 --dapr-http-port 3501 --app-channel-address $HOST_IP
# Publisher 用の Dapr を起動(別のターミナル)
dapr run --app-id publisher --app-port 8080 --dapr-http-port 3500

Dapr ランタイム(WSL 内)から Windows ホスト上の Subscriber アプリ(172.XX.XXX.X)への通信時、デフォルトの localhost(127.0.0.1)では到達できません。Windows ホストの IP アドレスを指定することで、WSL からホストへの通信が可能になります。

対応2:Mirrored ネットワークモード構成の採用

Windows 11 22H2 以降 であれば、WSL2 で「Mirrored ネットワークモード」を構成することができます。
この構成では、WSL と Windows ホストが同じネットワーク空間を共有するため、localhost で相互通信が可能になり、--app-channel-addressの指定は不要となります。
「Mirrored ネットワークモード」は、WSL を開発環境で利用する場合に便利なことが多いため、ぜひお試しください。

# %USERPROFILE%\.wslconfig
[wsl2]
networkingMode=mirrored

設定後、WSL を再起動してください。

wsl --shutdown

WSL 内から localhost でホスト上のアプリケーションを呼び出せるようになります。

# IP アドレスが Windows ホストマシンと同一の IP アドレスになるはず
ip addr show eth0 | grep 'inet\b' | awk '{print $2}' | cut -d/ -f1
# Windows ホスト上のアプリケーションへ疎通確認
curl http://localhost:8080

参考: WSL における高度な設定の構成

実践:Spring Boot アプリケーションで Dapr を使う

プロジェクトの準備

Spring Boot プロジェクトに以下の依存関係を追加します。
Dapr の機能を利用するには、io.dapr.client.DaprClient を利用します。

// build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'io.dapr.spring:dapr-spring-boot-starter:0.13.3'
    
    // 他の依存関係
}

application.yml(基本設定)

server:
  port: 8080

dapr:
  client:
    http-port: 3500     # Dapr HTTP port (デフォルト: 3500)
    grpc-port: 50001    # Dapr gRPC port (デフォルト: 50001)

Building Block : Service Invocation

サービス間通信の実装を抽象化し、名前ベースの呼び出しができます。
Dapr-ServiceInvocation.png

従来のサービス通信の実装:

  • サービスの URL を環境ごとに管理する必要がある
  • リトライロジックを自分で実装する必要がある
  • サービスディスカバリの仕組みを自前で用意する必要がある
// 直接 URL を指定
restTemplate.getForObject(url + "/users/123", User.class);

Dapr を使った通信の実装:

  • app-idによる論理名でサービスを呼び出し、URLの管理が不要
  • Dapr がサービスの場所を自動で解決
  • リトライ、タイムアウト、サーキットブレーカーは設定で制御可能
    ※リトライポリシーの設定例は公式サイト(Resiliency)を参照してください
// app-id を指定(URL 不要)
daprClient.invokeMethod(
    "user-service",        // app-id(論理名)
    "users/123",           // エンドポイント
    userDto,               // Body
    HttpExtension.GET,     // HTTP メソッド
    metadata,              // メタデータ(ヘッダー)
    User.class             // レスポンスの型
).block();

たとえば、ローカル開発ではlocalhost:8080、本番ではuser-service.production.svc.cluster.localとなっていても、アプリケーションコードは"user-service"という app-id を指定するだけで済みます。

本記事ではローカル環境での基本的な使い方の紹介にとどめています。
WSL環境で複数サービスを安定して連携させる場合は、サービスディスカバリ(Consul等)の導入が推奨されます。

Building Block : State Management

状態(データ)の保存・取得を、ストアの種類に依存しない形で実現します。
Dapr-StateManagement.png

Dapr コンポーネント設定(statestore.yaml)

サポートされているコンポーネントや設定は公式サイト(State Store)を参照してください。
※Redis の設定詳細はこちら

# ~/.dapr/components/statestore.yaml
## コンポーネント名: `statestore`
## 対象バックエンド: Redis
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore # コンポーネント名/アプリ内から参照
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
  # 他にも設定可能な項目:
  # - TTL(有効期限)
  # - keyPrefix(キーのプレフィックス)

この設定ファイルを変更するだけで、Redis から DynamoDB や CosmosDB に切り替えることができます。アプリケーションコードの変更は不要です。

実装例

@Service
@RequiredArgsConstructor
public class StateService {
    private final DaprClient daprClient;
    private static final String STORE_NAME = "statestore";

    public void saveState(String key, String value) {
        // どのストアを使っていても同じコード
        daprClient.saveState(STORE_NAME, key, value).block();
    }

    public String getState(String key) {
        State<String> state = daprClient.getState(
            "statestore", // ストア名
            key,          // キー名
            String.class  // データの型
        ).block();
        return state != null ? state.getValue() : null;
    }
}

💡ポイント

  • "statestore"はコンポーネント設定ファイルで定義した名前
  • バックエンドのストア実装(Redis/DynamoDB等)はアプリケーションから隠蔽される
  • シリアライズ・デシリアライズは自動で行われる
  • Daprのapp-idごとに名前空間が作成され、データが分離される

Building Block : Pub/Sub

メッセージブローカーを使った非同期通信を、メッセージブローカーの種類に依存しない形で実現します。
Dapr-PubSub.png

Dapr コンポーネント設定(pubsub.yaml)

サポートされているコンポーネントや設定は公式サイト(Pub/sub)を参照してください。
※RabbitMQ の設定詳細はこちら

# ~/.dapr/components/pubsub.yaml
## コンポーネント名: `pubsub`
## 対象バックエンド: RabbitMQ
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: pubsub # コンポーネント名/アプリ内から参照
spec:
  type: pubsub.rabbitmq
  version: v1
  metadata:
  - name: host
    value: amqp://localhost:5672
  - name: durable
    value: "true" # キューを永続化
  # 他にも設定可能な項目:
  # - deliveryMode(配信モード)
  # - prefetchCount(プリフェッチ数)
  # - maxRetries(最大リトライ回数)

Publisher 側の実装

メッセージを発行する側は、DaprClient を使ってトピックにメッセージを送信します。

@RestController
@RequestMapping("/api/publisher")
@RequiredArgsConstructor
public class PublisherController {
    private final DaprClient daprClient;

    @PostMapping("/publish")
    public String publishMessage(@RequestBody MessageDto message) {
        // どのブローカーでも同じコード
        daprClient.publishEvent(
            "pubsub",           // コンポーネント名
            "orders",           // トピック名
            message             // 送信メッセージ
        ).block();
        
        return "Message published!";
    }
}

💡ポイント

  • "pubsub"はコンポーネント設定ファイルで定義した名前
  • トピック名は Subscriber 側でも同じ名前を使用する
  • メッセージの型は任意の Java オブジェクトが使用可能
  • リトライポリシー、デッドレターキューは設定で制御可能

Subscriber 側の実装

メッセージを受信する側は、@Topicアノテーションを使用します。

@RestController
@RequiredArgsConstructor
public class SubscriberController {

    // @Topic アノテーションで自動的に Subscription 登録される
    // 受信するコンポーネント名・トピック名を設定
    @Topic(name = "orders", pubsubName = "pubsub")
    @PostMapping("/orders")
    public ResponseEntity<Void> handleOrder(
        @RequestBody CloudEvent<MessageDto> event
    ) {
        MessageDto message = event.getData();
        System.out.println("Received: " + message);
        return ResponseEntity.ok().build();
    }
}

Dapr では、メッセージを CloudEvents という標準形式で扱います。
これにより、異なるシステム間でのイベント互換性が保証されます。

動作確認例

# WSL 上でメッセージブローカーと Dapr 起動
# Publisher 用の Dapr
dapr run --app-id publisher --app-port 8080 --dapr-http-port 3500
# Subscriber 用の Dapr(別のターミナル)
dapr run --app-id subscriber --app-port 8081 --dapr-http-port 3501 --app-channel-address $HOST_IP
# Windows ホスト上で各サービスを起動
# メッセージを発行
curl -X POST http://localhost:8080/api/publisher/publish \
  -H "Content-Type: application/json" \
  -d '{"orderId": "12345", "amount": 100.00}'

# Subscriberのログで受信を確認

@Topic アノテーションの仕組み:

@Topicアノテーションを使用すると、Dapr 側で自動的に Subscription が登録されます。これは、Spring Boot が Dapr 用の特別なエンドポイント/dapr/subscribeを内部的に公開し、Dapr がこのエンドポイントを通じて Subscription 情報を取得するためです。

動作の流れ:

  1. Spring Boot アプリ起動時、@Topicアノテーションが付いたメソッドをスキャン
  2. /dapr/subscribeエンドポイントを自動生成
  3. Dapr が定期的にこのエンドポイントをチェック
  4. Subscription を自動登録

詳細は後述の Dapr ダッシュボードで確認できます。

Dapr ダッシュボードの活用

Dapr ダッシュボードは、Dapr の状態を確認できるデバッグツールです。

ダッシュボードの起動

# WSL で起動
dapr dashboard -p 9999

ブラウザで http://localhost:9999 にアクセスして、以下の情報を確認できます。

  • Dapr サイドカーの状態
  • Dapr コンポーネントの設定と状態
  • Pubsub トピックの一覧

ダッシュボードはデバッグや状態確認に非常に役立ちます。

Subscription 確認

@Topicアノテーションで自動登録された Subscription が確認できます。

2025-10-24-18-59-36.png

この画面で、正しく Subscription が登録されているか確認できます。もし表示されない場合、@Topic アノテーションの設定や Dapr 起動時のパラメータ等に問題があります。

まとめ

本記事では、WSL 環境で Dapr をローカル開発に活用する方法を紹介しました。

Dapr の大きな強みは、一度書いたコードがどの環境でも動くことです。
ローカルで開発したアプリケーションは、Dapr コンポーネントの設定を変更するだけで、クラウド環境でもそのまま動作します。

Dapr には学習コストがありますが、その投資に見合う価値があります。

  • インフラ固有の実装から解放される
  • マルチクラウド戦略を容易に実現できる
  • 最も重要なビジネスロジックに集中できる

面倒なことは Dapr に任せて、本質的な価値創造に時間を使いましょう!
この記事が、サービス開発における選択肢の一つとして、お役に立てば幸いです。

参考資料

We Are Hiring!

  1. CNCF(Cloud Native Computing Foundation):クラウドネイティブ技術の推進団体

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?