Contents
マイクロサービス設計の基本原則
マイクロサービスは 単一責任 と Bounded Context を徹底することで、変更範囲を最小化し、チームが独立してデリバリーできる基盤を構築します。また、API‑ファースト のアプローチはインターフェイスの合意形成を早期に行うため、実装フェーズでの齟齬を防げます。
単一責任と Bounded Context
単一責務は「1 つのビジネス機能だけを担当する」こと、Bounded Context は「データ所有権と用語が明確に分離された領域」を指します。これらを守ると次のような効果が得られます。
- 変更の局所化:サービス単位でスキーマやロジックを修正しても、他サービスへの影響は API のみになる
- デプロイリスクの低減:小さなサービスは頻繁に安全にリリースできる
参考: Domain‑Driven Design(Eric Evans, 2003)では「コンテキストマップを描く」ことが分割指針になると明示されています。
実装例:顧客管理 vs. 注文処理
| サービス | 主なエンティティ | API の公開例 |
|---|---|---|
| Customer Service | Customer、Address |
GET /customers/{id} |
| Order Service | Order、OrderItem |
POST /orders |
顧客情報のスキーマ変更は Customer Service のみで完結し、Order Service は API 仕様が変わらなければ影響を受けません。
API‑ファーストアプローチ
API を先に設計し、OpenAPI 3.0 で契約を書き出すことで以下のメリットが得られます。
- 自動生成コード と モックサーバ が即座に利用可能になる
- フロントエンドとバックエンドの開発が並行でき、納期短縮につながる
手順概要
- OpenAPI 定義作成(
openapi: 3.0.1) openapi-generator-maven-plugin(Spring Boot 3.x 対応)でコントローラ雛形を生成prismやstoplight/prism-cliでローカルモックサーバ起動
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# openapi.yaml の抜粋 openapi: 3.0.1 info: title: Customer Service API paths: /customers/{id}: get: summary: 顧客情報取得 parameters: - name: id in: path required: true schema: type: string responses: '200': description: 正常応答 content: application/json: schema: $ref: '#/components/schemas/Customer' components: schemas: Customer: type: object properties: id: type: string name: type: string |
備考:Spring Boot 3.2 以降は
springdoc-openapiが標準的に利用でき、Java 21 のモジュールシステムとも相性が良いです。
通信プロトコルと API 設計
サービス間通信は ユースケース に合わせて REST, gRPC, GraphQL のいずれかを選択します。選定指標を明確にすると、パフォーマンスや保守性の課題が事前に回避できます。
プロトコル選定基準
| 観点 | REST(JSON/HTTP1.1) | gRPC(Protocol Buffers/HTTP2) | GraphQL |
|---|---|---|---|
| データ形式 | テキスト JSON | バイナリ PB | クエリ結果は JSON |
| レイテンシ | ミリ秒単位、ネットワークに依存 | 適切なチューニングで 数ミリ秒以下 が実現可能(公式ベンチマーク参照) | クエリ最適化次第 |
| ストリーミング | なし(WebSocket で代替可) | 双方向ストリームが標準 | サブスクリプションは別実装 |
| 学習コスト | 低 | 中(proto 言語とコード生成) | 高(SDL とクエリ設計) |
| 適用シーン | 外部公開 API、既存エコシステムとの互換性重視 | 高頻度・低レイテンシが必須の内部 RPC | フロントエンドで柔軟なデータ取得が必要 |
具体例
- 在庫確認:
CheckStockRPC は 数ミリ秒 のレイテンシ要件を満たすため、gRPC を採用(Spring Boot 3.x +grpc-spring-boot-starterv2.13)。 - 商品検索 UI:ユーザーが自由にフィールドを組み合わせて取得したいので GraphQL エンドポイントを提供し、
graphql-java-kickstart(v15) と連携。
参考: gRPC 公式サイト「Performance」セクション(2024‑03)
OpenAPI と AsyncAPI の併用
- REST → OpenAPI 3.0 でインターフェイスを定義
- 非同期メッセージ(Kafka, RabbitMQ 等) → AsyncAPI 2.x でスキーマ化
CI パイプラインにバリデーションステップを組み込むと、変更時の破壊的インパクトを早期検出できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# asyncapi.yaml(Kafka 用) asyncapi: '2.4.0' info: title: Order Events version: '1.0.0' servers: prod: url: broker.kafka.local:9092 protocol: kafka channels: order.created: publish: message: $ref: '#/components/messages/OrderCreated' components: messages: OrderCreated: payload: type: object properties: orderId: type: string amount: type: number |
asyncapi-generator-maven-plugin(Spring Cloud Stream 4.0 対応)でリスナ雛形を生成- GitHub Actions では
speccy lintとasyncapi validateを実行し、PR 時点でスキーマ整合性を保証
データ管理と分散トランザクション回避
マイクロサービスは 各サービスが独自 DB を持つ のが原則です。分散トランザクションは可用性リスクになるため、Saga パターン で代替します。
サービスごとのデータベース設計
| ポイント | 推奨方針 |
|---|---|
| データ所有権 | コンテキスト図に明示し、外部キーは使用しない |
| テーブル命名 | serviceName_entity 形式で衝突回避(例:inventory_product) |
| スキーマ変更 | API バージョニングとマイグレーションツール(Flyway 9.x)で段階的に実施 |
実装ヒント
- Spring Boot 3 の
@Transactionalは ローカルトランザクション に限定し、外部参照は必ず REST/gRPC で取得 - データベース接続プールは HikariCP(デフォルト)を使用し、
maxLifetimeとidleTimeoutを環境に合わせて調整
Saga パターンの実装例
コーディネート型 Saga(Spring State Machine 3.0)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Configuration @EnableStateMachineFactory public class OrderSagaConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> { @Override public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception { states .withStates() .initial(OrderStates.CREATED) .state(OrderStates.PAYMENT_CONFIRMED) .end(OrderStates.COMPLETED); } // 省略: トランジションと補償アクションの定義 } |
- 成功フロー:
CREATED → PAYMENT_CONFIRMED → COMPLETED - 失敗時:
COMPENSATE_PAYMENT等の逆操作が自動的に呼び出され、全体として最終的一貫性を保つ
イベント駆動型 Saga(Kafka + Spring Cloud Stream 4.0)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@EnableBinding(OrderChannels.class) public class OrderSagaProcessor { @StreamListener(target = OrderChannels.ORDER_CREATED, condition = "headers['type']=='order'") public void handleOrderCreated(@Payload OrderCreatedEvent event) { // ビジネスロジック実行 → 成功なら次イベントを発行 } @StreamListener(OrderChannels.COMPENSATE_ORDER) public void compensate(@Payload CompensationEvent event) { // 失敗時の逆操作 } } |
- 各サービスは トピック単位 に責務を分割し、失敗が起きたら補償イベントだけでロールバックできるため、システム全体の可用性が向上します。
参考: Microservices Patterns(Chris Richardson, 2018)第 9 章「Saga」
レジリエンス・セキュリティ実装パターン
サービス間障害は想定内とし、Resilience4j を用いた回路遮断・リトライで自サービスを保護します。認可は OAuth2 / OIDC に統一し、Zero‑Trust の原則で通信を暗号化します。
Resilience4j の活用
| 機能 | 主な設定項目 |
|---|---|
| Circuit Breaker | failureRateThreshold, waitDurationInOpenState |
| Retry | 最大リトライ回数、バックオフ戦略 |
| Bulkhead | スレッドプール/セマフォで同時呼び出しを制限 |
| Timeout | 呼び出しの上限時間 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Bean public CircuitBreakerConfig circuitBreakerConfig() { return CircuitBreakerConfig.custom() .failureRateThreshold(50) // 失敗率が 50% 超でオープン .waitDurationInOpenState(Duration.ofSeconds(30)) .permittedNumberOfCallsInHalfOpenState(10) .build(); } @Bean public RetryConfig retryConfig() { return RetryConfig.custom() .maxAttempts(3) .waitDuration(Duration.ofMillis(200)) .build(); } |
- 設定は
application.ymlに集中させ、環境ごとのオーバーライドはprofile別に管理 Resilience4jSpringBoot2(バージョン 2.1)と組み合わせると、AOP アノテーション (@CircuitBreaker,@Retry) がシームレスに機能
OAuth2 / OIDC と Zero‑Trust 設計
| 要素 | 推奨実装 |
|---|---|
| 認証基盤 | Keycloak 22.x、Auth0 も可(OpenID Connect 対応) |
| トークン検証 | Spring Security 6 の JwtDecoder を利用し JWK Set URL で自動取得 |
| mTLS | Istio 1.20 の PeerAuthentication によりサービス間通信を暗号化 |
| Token 有効期限 | アクセストークンは 5 分、リフレッシュトークンはバックエンドで安全に保管 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .oauth2ResourceServer(oauth -> oauth.jwt(jwt -> jwt.jwkSetUri("https://idp.example.com/.well-known/jwks.json"))) .authorizeHttpRequests(authz -> authz .requestMatchers("/public/**").permitAll() .anyRequest().authenticated()); return http.build(); } } |
- Zero‑Trust:Istio の
AuthorizationPolicyで RBAC を細粒度に定義し、サービス間でも最小権限の原則を徹底 - トラフィックはすべて HTTPS + mTLS に統一することで、内部脅威にも耐えられる設計となります
コンテナ化・オーケストレーションと Observability
Docker と Kubernetes でマイクロサービスをデプロイし、Observability(トレース・メトリクス・ログ)で運用可視性を確保します。軽量なコンテナとパラメータ化された Helm チャートが CI/CD の自動化に貢献します。
Dockerfile 最適化と Helm Chart 構造(バージョン情報)
- マルチステージビルド:Spring Boot 3.2 の
java -jar方式をベースに、最終イメージは 30〜60 MB 程度に抑えられる実績があります(公式ガイド参照)。 - ベースイメージ:
eclipse-temurin:21-jre-alpine(Alpine Linux)でサイズ削減と脆弱性低減を同時達成
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# ---------- Build stage ---------- FROM eclipse-temurin:21-jdk-alpine AS build WORKDIR /src COPY gradlew . COPY gradle ./gradle COPY build.gradle.kts settings.gradle.kts ./ RUN ./gradlew bootJar --no-daemon # ---------- Runtime stage ---------- FROM eclipse-temurin:21-jre-alpine ARG JAR_FILE=/src/build/libs/*.jar COPY --from=build ${JAR_FILE} app.jar ENTRYPOINT ["java","-XX:+UseContainerSupport","-jar","/app.jar"] |
Helm Chart ディレクトリ例(Kubernetes 1.30 対応)
|
1 2 3 4 5 6 7 8 |
chart/ ├── Chart.yaml # メタ情報、バージョンは semver で管理 ├── values.yaml # image.repository, tag, replicaCount 等を外部化 └── templates/ ├── deployment.yaml # resources.limits/requests を values から参照 ├── service.yaml └── ingress.yaml |
values.yamlのimage.tagは GitHub Actions の SHA で自動更新し、GitOps によるデプロイを実現helm testでヘルスチェック・Smoke Test を組み込み、CI パイプラインに統合
OpenTelemetry と Prometheus/Grafana による観測
| コンポーネント | 主な役割 |
|---|---|
| OpenTelemetry Collector | トレース(Jaeger)とメトリクス(Prometheus)を集約 |
| Jaeger UI | 分散トレースの可視化 |
| Prometheus | 時系列データ収集、アラート定義 |
| Grafana | ダッシュボードで KPI をリアルタイム表示 |
設定例(Spring Boot 3.2 + OpenTelemetry Java Agent)
application.yml
|
1 2 3 4 5 6 7 |
otel: exporter: otlp: endpoint: http://otel-collector.monitoring.svc:4317 resource: attributes: service.name=order-service,service.version=1.0.0 |
起動スクリプト(Kubernetes の Deployment に追加)
|
1 2 3 4 5 6 7 8 9 10 11 |
containers: - name: order-service image: ghcr.io/example/order-service:{{ .Values.image.tag }} env: - name: JAVA_TOOL_OPTIONS value: "-javaagent:/opentelemetry-javaagent.jar" volumeMounts: - name: otel-agent mountPath: /opentelemetry-javaagent.jar subPath: opentelemetry-javaagent.jar |
- Prometheus アノテーション(Spring Boot Actuator 3.2)
|
1 2 3 4 5 6 7 8 9 10 |
management: endpoints: web: exposure: include: health,info,prometheus metrics: export: prometheus: enabled: true |
Grafana のテンプレートは {{ .Release.Name }}-service ラベルで自動集約でき、SLO/SLI をダッシュボードに可視化します。
参考: OpenTelemetry 公式ガイド「Java Instrumentation」2024‑04 更新版
CI/CD、GitOps とテスト戦略
継続的インテグレーションとデリバリーを コードベースだけで完結 させることで、ヒューマンエラーを排除し、デプロイ頻度を向上させます。テストは ユニット → 契約 → 統合 のピラミッドで網羅的に実施します。
GitHub Actions と Argo CD の自動化フロー
- コードプッシュ →
mainブランチで CI が走る - ビルド・テスト完了後、Docker イメージを GitHub Container Registry にプッシュ
- Argo CD が Helm Chart の
values.yaml.image.tagを更新し、対象クラスターへ同期
ワークフロー例(.github/workflows/ci-cd.yml)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
name: CI/CD on: push: branches: [ main ] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 21 uses: actions/setup-java@v3 with: distribution: temurin java-version: '21' - name: Build & Test run: ./gradlew clean build --no-daemon - name: Build Docker image env: IMAGE_TAG: ${{ github.sha }} run: | docker build -t ghcr.io/${{ github.repository }}:${IMAGE_TAG} . echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin docker push ghcr.io/${{ github.repository }}:${IMAGE_TAG} - name: Update Helm values & sync Argo CD uses: argoproj/argo-cd-action@v2 with: app-name: order-service image-tag: ${{ env.IMAGE_TAG }} |
- Argo CD は
helmfile.yamlと併用すれば、複数サービスのロールバックも Git のコミット単位で即座に実行可能です。
テストピラミッドとカナリアリリース
| レベル | 主な目的 | 推奨ツール |
|---|---|---|
| ユニットテスト | ビジネスロジックの正当性 | JUnit 5、Mockito |
| 契約テスト | サービス間 API の合意保証 | Pact (JVM) |
| 統合テスト | 複数コンポーネントの相互作用検証 | Testcontainers、Spring Boot Test |
Pact による契約テスト例
|
1 2 3 4 5 6 7 8 9 10 11 |
@Provider("order-service") @Consumer("payment-service") class OrderPactTest { @TestTemplate @ExtendWith(PactVerificationInvocationContextProvider.class) void pactVerification(TestTarget target) { // Payment Service が期待する /orders/{id} のレスポンスを検証 } } |
- CI では
pact-brokerに結果をプッシュし、相手側のビルドが成功しているか自動チェック
カナリアリリース(Argo Rollouts)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: name: order-service spec: strategy: canary: steps: - setWeight: 10 - pause: {duration: 5m} - analysis: templates: - name: success-rate templateName: prometheus-success-rate |
- Prometheus の
success_rateが閾値を下回った場合に自動ロールバックし、リスクを最小化
参考: Continuous Delivery(Jez Humble, 2010)第 7 章「Canary Deployments」
まとめ
- 設計:単一責任・Bounded Context と API‑ファーストでインターフェイスを明確化
- 通信:ユースケースに合わせて REST / gRPC / GraphQL を選択し、OpenAPI/AsyncAPI でスキーマ管理
- データ:サービスごとの DB 所有と Saga パターンで分散トランザクションを回避
- レジリエンス・セキュリティ:Resilience4j と Zero‑Trust に基づく認可設計で障害耐性と防御力を確保
- コンテナ化 & Observability:マルチステージ Docker、パラメータ化 Helm、OpenTelemetry + Prometheus/Grafana で運用透明性を実現
- CI/CD・テスト:GitHub Actions + Argo CD の GitOps パイプラインとテストピラミッドで高速かつ安全なデリバリー
これらのベストプラクティスをプロジェクトに組み込むことで、モダンな Java マイクロサービス基盤を迅速・安定・セキュアに構築できるはずです。ぜひ実装フェーズで各項目をご確認ください。