Contents
前提条件と開発環境の準備
Spring Boot 3.2 と JDK 21 でマイクロサービス基盤を構築するにあたっては、使用するツールのバージョンが相互に対応していることが最重要です。このセクションでは、2026 年時点で「最新安定版」とみなされている主要ツールと、そのインストール手順・環境変数設定方法をまとめます。
ツール一覧(公式ドキュメント参照)
| ツール | 推奨バージョン | 主な利用目的 |
|---|---|---|
| JDK | 21 (LTS) | 言語機能・パフォーマンス向上 |
| ビルドツール | Maven 3.x / Gradle 8.x(いずれも最新安定版) | 依存管理・ビルド自動化 |
| IDE | Spring Tool Suite 4、IntelliJ IDEA Ultimate (2023‑12) | コード補完・デバッグ支援 |
| Docker Engine | 最新安定リリース(公式サイトの docker version で確認) |
コンテナイメージ作成・実行 |
| Git | 最新安定版 | ソース管理 & CI/CD パイプライン |
注: バージョンは頻繁に更新されるため、インストール直前に公式サイトで最新情報を確認してください。
インストール手順(OS 共通)
- JDK 21
- OpenJDK または Oracle JDK を公式サイトからダウンロードし、
/usr/local/java/jdk-21へ展開。 -
JAVA_HOMEとPATHに以下を追記(~/.bashrcや~/.zshrc):bash
export JAVA_HOME=/usr/local/java/jdk-21
export PATH=$JAVA_HOME/bin:$PATH -
Maven / Gradle
-
macOS では
brew install maven gradle、Linux/Windows は公式 zip を展開し同様にPATHに追加。 -
IDE のセットアップ
-
Spring Tool Suite 4 のインストーラを実行し、Spring Boot 用プラグインが有効か確認。IntelliJ を使用する場合は「Spring」プラグインを有効化してください。
-
Docker Desktop / Engine
-
Docker Hub から最新版のインストーラ(Windows/macOS)または公式リポジトリ(Linux)で
apt/yum経由で導入。デーモン起動を確認したら、docker versionコマンドでバージョンが表示されることをチェックします。 -
Git
-
公式インストーラまたはパッケージマネージャで導入し、以下の基本設定を行います。
bash
git config --global user.name "Your Name"
git config --global user.email you@example.com
動作確認コマンド例
|
1 2 3 4 5 6 |
java -version # JDK 21 が表示されること mvn -v # Maven のバージョンが表示されること gradle -v # Gradle のバージョンが表示されること docker --version # Docker Engine のバージョンが表示されること git --version # Git のバージョンが表示されること |
プロジェクト作成とパッケージ設計
本章では、Spring Initializr を用いたプロジェクトの雛形生成から、クリーンアーキテクチャに基づくディレクトリ構造までを解説します。適切なパッケージ分割は保守性・テスト容易性の向上につながります。
Spring Initializr の使い方
- https://start.spring.io にアクセスし、以下の項目を設定します。
- Project: Maven (または Gradle)
- Language: Java
- Spring Boot: 3.2.x(最新リリース)
- Group / Artifact:
com.example/orderservice - Packaging: Jar
-
Java: 21
-
必要な依存関係は次の通りです。
text
Spring Web
Spring Data JPA
Spring Boot Actuator
Spring Cloud Discovery (LoadBalancer)
Lombok(開発時のみ)
spring-boot-devtools(任意)
- 「Generate」ボタンで ZIP を取得し、IDE にインポートします。
ポイント:
spring-cloud-starter-netflix-eurekaは非推奨です。代わりにspring-cloud-starter-loadbalancerとspring-cloud-starter-consul-discovery(または Kubernetes‑native のspring-cloud-kubernetes-discovery) を選択してください。
推奨パッケージ構造
以下は「ドメイン駆動設計(DDD)に基づく」典型的なレイアウトです。各層がフレームワークへの依存を最小化するよう意図しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
src/main/java/com/example/orderservice ├── application # Use‑case / Service 層 │ ├── OrderService.java │ └── dto/ │ ├── OrderCreateRequest.java │ └── OrderResponse.java ├── domain # エンティティとリポジトリインタフェース │ ├── model/ │ │ └── Order.java // Immutable record が望ましい │ └── repository/ │ └── OrderRepository.java // Spring Data JPA の拡張インタフェース ├── infrastructure # 外部システムとの接続実装 │ ├── persistence/ │ │ └── JpaOrderRepository.java │ └── config/ │ └── DiscoveryConfig.java // Spring Cloud Discovery 設定 └── common # ロギング・例外クラス等の汎用コンポーネント |
主要設計上の留意点
- エンティティは immutable に近い形で実装し、状態変更は Service 層のメソッドで行う。
- DTO は Java 21 の
recordを活用してボイラープレートを削減する。 - リポジトリはインタフェースだけ定義し、実装は
infrastructure.persistenceに置くことでテスト時に簡単にモックできる。
コア実装:REST API とセキュリティ
この章では、エンドポイントの設計例と共通的なエラーハンドリング、さらにステートレス認証として JWT を利用する方法を示します。統一された実装パターンはチーム全体でコードレビューや保守を容易にします。
コントローラ・サービス層の基本実装例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// src/main/java/com/example/orderservice/application/OrderController.java @RestController @RequestMapping("/api/v1/orders") @RequiredArgsConstructor public class OrderController { private final OrderService orderService; @PostMapping public ResponseEntity<OrderResponse> create(@RequestBody @Valid OrderCreateRequest request) { var created = orderService.create(request); return ResponseEntity.status(HttpStatus.CREATED).body(created); } @GetMapping("/{id}") public ResponseEntity<OrderResponse> get(@PathVariable Long id) { return ResponseEntity.ok(orderService.findById(id)); } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// src/main/java/com/example/orderservice/application/OrderService.java @Service @RequiredArgsConstructor public class OrderService { private final OrderRepository repository; private final OrderMapper mapper = Mappers.getMapper(OrderMapper.class); @Transactional public OrderResponse create(OrderCreateRequest request) { var entity = mapper.toEntity(request); var saved = repository.save(entity); return mapper.toResponse(saved); } @Transactional(readOnly = true) public OrderResponse findById(Long id) { var order = repository.findById(id) .orElseThrow(() -> new EntityNotFoundException("Order not found")); return mapper.toResponse(order); } } |
統一エラーハンドリング
@RestControllerAdvice によって例外ごとに HTTP ステータスと JSON ボディを統一します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// src/main/java/com/example/orderservice/common/GlobalExceptionHandler.java @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(EntityNotFoundException.class) public ResponseEntity<ApiError> handleNotFound(EntityNotFoundException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ApiError("NOT_FOUND", ex.getMessage())); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ApiError> handleValidation(MethodArgumentNotValidException ex) { var errors = ex.getBindingResult().getFieldErrors() .stream() .map(e -> e.getField() + ": " + e.getDefaultMessage()) .collect(Collectors.joining("; ")); return ResponseEntity.badRequest() .body(new ApiError("VALIDATION_ERROR", errors)); } } |
JWT 認証の実装フロー
- 認証エンドポイント (
/auth/login) でユーザー情報を検証し、io.jsonwebtoken.Jwtsによりトークンを生成。 - フィルタ
JwtAuthenticationFilterがリクエストヘッダーからトークンを抽出し、SecurityContextHolderに認可情報を設定。
|
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 |
// src/main/java/com/example/orderservice/security/JwtAuthenticationFilter.java public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtProvider jwtProvider; private final UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String header = request.getHeader(HttpHeaders.AUTHORIZATION); if (StringUtils.hasText(header) && header.startsWith("Bearer ")) { String token = header.substring(7); if (jwtProvider.validateToken(token)) { String username = jwtProvider.extractUsername(token); UserDetails user = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth); } } filterChain.doFilter(request, response); } } |
SecurityConfig でフィルタチェーンに登録し、 /auth/** と swagger-ui/** は除外します。
OpenAPI(Swagger)自動生成
Spring Doc のスターターパッケージを pom.xml に追加すれば、ビルド時に API ドキュメントが自動的に公開されます。
|
1 2 3 4 5 6 |
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <!-- バージョンは「最新安定版」へ置き換えてください --> </dependency> |
/swagger-ui.html にアクセスすると、上記コントローラのメタ情報が即座に可視化されます。
設定とコンテナ化:Config Server、Docker、サービスディスカバリ
このセクションでは、外部設定管理(Spring Cloud Config Server)と、マイクロサービス間の名前解決・ロードバランシングに Spring Cloud Discovery を組み合わせた構成を紹介します。Docker と docker‑compose によるローカル環境再現手順も併せて示します。
Spring Cloud Discovery の選択肢(Eureka からの脱却)
| 実装 | 主な利用シーン | メリット |
|---|---|---|
| Spring Cloud LoadBalancer + Consul | オンプレミス・クラウド横断的に使用 | 軽量、Health Check が標準装備 |
| Spring Cloud Kubernetes Discovery | 完全に K8s 上で完結させたい場合 | K8s の Service と連携し、外部サーバ不要 |
| Apache Zookeeper(オプション) | 高可用性が必須なレガシー環境 | 強固な分散ロック機能 |
推奨: 開発・検証フェーズは
spring-cloud-starter-consul-discovery、本番は Kubernetes が前提であればspring-cloud-kubernetes-discoveryを選択してください。
DiscoveryConfig のサンプル(Consul)
|
1 2 3 4 5 6 7 |
// src/main/java/com/example/orderservice/config/DiscoveryConfig.java @Configuration @EnableDiscoveryClient // Spring Cloud が自動的に Consul に登録 public class DiscoveryConfig { // 追加のカスタマイズが必要な場合は Bean 定義をここに記述 } |
Config Server の基本設定
application.yml に外部設定サーバの URL を記載し、プロファイルごとの設定ファイルをリポジトリで管理します。
|
1 2 3 4 5 6 |
spring: config: import: optional:configserver:http://localhost:8888 profiles: active: dev # 起動時に -Dspring.profiles.active=prod で切替可能 |
Config Server 自体は公式 Docker イメージ springcloud/configserver の最新版を使用し、native プロファイルでローカルリポジトリを参照させます。
Dockerfile と docker‑compose(ローカルスタック)
以下はマイクロサービス本体と支援コンテナの構成例です。Dockerfile はビルド段階とランタイム段階に分離し、イメージサイズを最小化します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Dockerfile (order-service) FROM eclipse-temurin:21-jdk-alpine AS build WORKDIR /app COPY mvnw pom.xml ./ RUN ./mvnw dependency:go-offline -B COPY src ./src RUN ./mvnw package -DskipTests FROM eclipse-temurin:21-jre-alpine VOLUME /tmp ARG JAR_FILE=/app/target/*.jar COPY --from=build ${JAR_FILE} app.jar ENTRYPOINT ["java","-XX:+UseContainerSupport","-jar","/app.jar"] |
|
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 36 37 38 39 40 41 42 43 44 45 46 |
# docker-compose.yml(プロジェクトルート) version: "3.9" services: config-server: image: springcloud/configserver:latest environment: - SPRING_PROFILES_ACTIVE=native volumes: - ./config-repo:/config-repo ports: - "8888:8888" consul-server: image: hashicorp/consul:latest command: agent -dev -client=0.0.0.0 ports: - "8500:8500" mysql: image: mysql:8.0 environment: MYSQL_DATABASE: orderdb MYSQL_USER: order_user MYSQL_PASSWORD: secret MYSQL_ROOT_PASSWORD: rootpw volumes: - db-data:/var/lib/mysql ports: - "3306:3306" order-service: build: context: ./order-service environment: - SPRING_PROFILES_ACTIVE=dev - MYSQL_PASSWORD=secret depends_on: - config-server - consul-server - mysql ports: - "8080:8080" volumes: db-data: |
環境プロファイルの管理ポイント
application-dev.ymlとapplication-prod.ymlを config-repo に格納し、機密情報は Spring Cloud Config の暗号化機能(encrypt.key)で保護。- Docker Compose では
SPRING_PROFILES_ACTIVE=devを明示的に指定し、本番環境は Kubernetes の ConfigMap/Secret と併用して切り替える設計とします。
テスト・CI/CD・Observability(可観測性)
高品質なマイクロサービスを継続的にリリースするためのテスト戦略、GitHub Actions を活用したパイプライン、そして実運用で欠かせないメトリクスとログ収集について解説します。
単体テストと統合テストのベストプラクティス
- JUnit 5 + Mockito でビジネスロジックを純粋に検証。
- Testcontainers を利用して実際の MySQL コンテナ上でリポジトリ層の統合テストを実行し、本番環境と同等の動作確認を行う。
|
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 |
@SpringBootTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) class OrderServiceIntegrationTest { @Container static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0") .withDatabaseName("orderdb") .withUsername("test") .withPassword("test"); @DynamicPropertySource static void setDatasource(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", mysql::getJdbcUrl); registry.add("spring.datasource.username", mysql::getUsername); registry.add("spring.datasource.password", mysql::getPassword); } @Autowired private OrderService orderService; @Test void shouldCreateOrder() { var req = new OrderCreateRequest(/* ... */); var resp = orderService.create(req); assertNotNull(resp.id()); } } |
GitHub Actions による CI/CD パイプライン
以下は「コードのビルド → Docker イメージ作成 → GCR (または GHCR) へプッシュ → Kubernetes デプロイ」を自動化した例です。
|
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 36 37 38 39 |
# .github/workflows/ci-cd.yml name: CI/CD Pipeline on: push: branches: [ main ] jobs: build-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up JDK 21 uses: actions/setup-java@v3 with: java-version: '21' distribution: 'temurin' - name: Build & Test run: ./mvnw -B verify - name: Build Docker image run: | docker build -t ghcr.io/${{ github.repository }}/order-service:${{ github.sha }} . echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin docker push ghcr.io/${{ github.repository }}/order-service:${{ github.sha }} deploy: needs: build-test runs-on: ubuntu-latest steps: - name: Deploy to Kubernetes uses: azure/k8s-deploy@v4 with: manifests: | k8s/deployment.yaml k8s/service.yaml images: | ghcr.io/${{ github.repository }}/order-service:${{ github.sha }} namespace: production |
ポイント:
k8sディレクトリに配置するマニフェストは、imagePullPolicy: IfNotPresentとし、ロールバック用にrevisionHistoryLimitを設定しておくと安全です。
Observability:メトリクス・トレーシング・ログ
-
Actuator + Micrometer
yaml
management:
endpoints:
web:
exposure:
include: health,info,prometheus
metrics:
export:
prometheus:
enabled: true
/actuator/prometheusが Prometheus にスクレイプされ、Grafana で可視化できます。 -
分散トレーシング
OpenTelemetry の Spring Boot スターターパッケージを追加し、Jaeger または Zipkin へスパンを送信します。 -
ロギングの統一
logback-spring.xmlで JSON フォーマット(例:net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder)に切り替えると、ELK/EFK スタックへの取り込みが容易です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!-- src/main/resources/logback-spring.xml --> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"/> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> <!-- 開発時は DEBUG を有効化 --> <logger name="com.example.orderservice" level="DEBUG"/> </configuration> |
ログ出力のベストプラクティス
- プレースホルダー (
log.info("Order {} created", id)) を必ず使用し、文字列結合コストを削減。 - 構造化ログ で
orderId,userIdといったキー情報を明示的に出力し、検索性と集計精度を向上させる。
まとめ
本稿では、Spring Boot 3.2 / JDK 21 環境下でマイクロサービス基盤を構築するための全体像を示しました。主要ポイントは次の通りです。
| 項目 | 推奨アプローチ |
|---|---|
| 開発環境 | 最新 LTS の JDK 21 と公式ドキュメントで確認できるビルドツール最新版 |
| サービスディスカバリ | Eureka ではなく Spring Cloud LoadBalancer + Consul、または Kubernetes‑native の Discovery を採用 |
| 設定管理 | Spring Cloud Config Server(Git リポジトリ)+プロファイルで環境差分を一元化 |
| コンテナ化 | 多段 Dockerfile と docker‑compose でローカルスタックを再現 |
| テスト・CI/CD | Testcontainers + GitHub Actions による自動ビルド・デプロイパイプライン |
| Observability | Actuator + Micrometer + OpenTelemetry、構造化ログによるフルスタック可観測性 |
これらを組み合わせて実装すれば、スケーラブルかつ保守しやすいマイクロサービスアーキテクチャが手に入ります。ぜひ本ガイドをプロジェクトのテンプレートとして活用し、継続的な改善サイクルへとつなげてください。