Contents
1. マイクロサービスとは何か ― 概念と留意点
基本概念
- 機能単位で独立したプロセス群としてシステムを構築。
- 各サービスは 自己完結(データベース・ビジネスロジックを持つ)ため、開発チームが言語やフレームワークを選択しやすくなる。
主な利点
| 項目 | 具体的効果 |
|---|---|
| スケーラビリティ | 高負荷のサービスだけを水平に増やせる。CPU・メモリ使用率が最適化され、インフラコスト削減につながる。 |
| 組織独立性 | チーム単位でデプロイサイクルが短くなり、アジイル開発が実現しやすい。 |
| 技術選択の自由度 | 言語・ミドルウェアをサービスごとに最適化できる(例:リアルタイム処理は Kotlin、バッチは Scala 等)。 |
直面しやすい課題
- ネットワーク遅延・障害
サービス間呼び出しが増える分、通信エラーやレイテンシが全体性能に影響する。 - 分散トランザクション
ACID を保つには Saga パターンや イベント駆動 の設計が必須になる。 - 運用負荷
ロギング・モニタリング・構成管理ツールを複数導入し、統一的に可視化する仕組みが必要。
2. Spring Boot 3.2 と Java 21 の概要 ― プロジェクト作成手順
Spring Initializr の設定例(Maven/Java 21)
| 項目 | 設定値 |
|---|---|
| Project | Maven Project |
| Language | Java |
| Spring Boot | 3.2.x (最新) |
| Packaging | Jar |
| Java | 21 |
| Dependencies | Spring Web, Spring Data JPA, Spring Cloud OpenFeign, Spring Boot Actuator |
- https://start.spring.io にアクセスし上表通りに入力。
Generate→ ダウンロードした ZIP を解凍。- ターミナルで
./mvnw spring-boot:runを実行し、起動を確認。
Java 21 の新機能がそのまま利用可能
|
1 2 3 |
// record を使った DTO(自動的に getter が生成される) public record UserDto(Long id, String name) {} |
- レコード・シーリングクラス・パターンマッチング for switch が Spring のバインディングや Jackson とも相性良好。
- Jakarta EE 9+ への移行が完了しており、
javax.*→jakarta.*に置き換わった API をそのまま使用できる。
3. シンプルな REST API の実装・テスト & Docker コンテナ化
CRUD エンドポイント(最小構成)
|
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 |
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // Lombok @Getter/@Setter で省略可 } public interface UserRepository extends JpaRepository<User, Long> {} @RestController @RequestMapping("/api/users") @RequiredArgsConstructor class UserController { private final UserRepository repo; @GetMapping("/{id}") public ResponseEntity<User> get(@PathVariable Long id) { return repo.findById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @PostMapping public ResponseEntity<User> create(@RequestBody User user) { User saved = repo.save(user); return ResponseEntity.status(HttpStatus.CREATED).body(saved); } } |
JUnit 5 + MockMvc でコントローラ単体テスト
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@SpringBootTest @AutoConfigureMockMvc class UserControllerTest { @Autowired MockMvc mockMvc; @Autowired ObjectMapper om; @Test void createUser() throws Exception { var payload = new User(null, "Alice"); mockMvc.perform(post("/api/users") .contentType(MediaType.APPLICATION_JSON) .content(om.writeValueAsString(payload))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id").exists()) .andExpect(jsonPath("$.name").value("Alice")); } } |
Dockerfile(マルチステージビルド)とサイズの目安
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# ---------- build ---------- FROM maven:3.9-eclipse-temurin-21 AS build WORKDIR /src COPY pom.xml . COPY src ./src RUN mvn -B package -DskipTests # ---------- runtime ---------- FROM gcr.io/distroless/java21 COPY --from=build /src/target/*.jar app.jar ENTRYPOINT ["java","-jar","/app.jar"] |
※イメージサイズはベースに使用する
distroless/java21のバージョンや JAR の依存関係によって変動します。実際のビルド結果は「数十 MB 程度」になることが多いですが、正確な数値はdocker imagesで確認してください。
ビルド・起動例
|
1 2 3 |
docker build -t user-service . docker run -p 8080:8080 user-service |
docker‑compose でローカルに複数サービスを一括起動
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
version: "3.9" services: db: image: postgres:16-alpine environment: POSTGRES_USER: demo POSTGRES_PASSWORD: secret POSTGRES_DB: demo ports: ["5432:5432"] config-server: image: springcloud/config-server:2024.0.0 volumes: - ./config:/config environment: SPRING_PROFILES_ACTIVE: native ports: ["8888:8888"] user-service: build: . depends_on: [db, config-server] environment: SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/demo SPRING_CLOUD_CONFIG_URI: http://config-server:8888 ports: ["8080:8080"] |
|
1 2 |
docker compose up -d |
4. Spring Cloud の主要コンポーネントと Observability 設定
| コンポーネント | 用途 | 主な設定例 |
|---|---|---|
| Service Discovery (Eureka / Consul) | サービスの登録・検索 | spring.cloud.discovery.enabled=true など |
| Config Server | 外部化されたプロパティ管理 | Git リポジトリを指す spring.cloud.config.server.git.uri |
| LoadBalancer | クライアント側負荷分散 | @LoadBalanced RestTemplate または OpenFeign と併用 |
| Resilience4j | サーキットブレーカー・リトライ | application.yml に resilience4j.circuitbreaker.* を定義 |
| Actuator + Micrometer | メトリクス収集 & エンドポイント公開 | /actuator/prometheus で Prometheus 形式にエクスポート |
Actuator と Micrometer の設定例
|
1 2 3 4 5 6 7 8 9 10 |
management: endpoints: web: exposure: include: health,info,prometheus metrics: export: prometheus: enabled: true |
- Grafana で
jvm_memory_used_bytes{area="heap"}等を可視化すれば、ヒープ使用率や GC の頻度が即座に把握できる。 - Spring Cloud Sleuth(2024.0 以降)は自動的に
traceIdを付与し、分散トレースのベースとなる。
5. Dapr と Kubernetes デプロイ ― Helm Chart の基本構造
Spring Boot アプリと Dapr sidecar の連携
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# dapr.yaml(State Store 用 Redis コンポーネント) apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: state-store spec: type: state.redis version: v1 metadata: - name: redisHost value: "redis:6379" |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@RestController @RequiredArgsConstructor class OrderController { private final DaprClient dapr; // spring-cloud-dapr-starter が提供 @PostMapping("/orders") public ResponseEntity<Void> create(@RequestBody OrderDto order) { dapr.saveState("state-store", order.getId().toString(), order).block(); return ResponseEntity.accepted().build(); } @GetMapping("/orders/{id}") public OrderDto get(@PathVariable String id) { return dapr.getState("state-store", id, OrderDto.class) .block() .getValue(); } } |
参考: 「Spring Bootで始める Dapr 入門」は執筆時点で有効です。
ローカルクラスター(Kind)へのデプロイ手順
|
1 2 3 4 5 6 7 8 9 10 |
# 1. Kind クラスタ作成 kind create cluster --name demo # 2. Dapr のインストール (Helm) helm repo add dapr https://dapr.github.io/helm-charts/ helm install dapr dapr/dapr --namespace dapr-system --create-namespace # 3. アプリケーションデプロイ(Kustomize ベース) kubectl apply -k k8s/base |
Helm Chart の構成要素(簡易例)
|
1 2 3 4 5 6 |
# Chart.yaml apiVersion: v2 name: user-service version: 0.1.0 appVersion: "3.2" |
|
1 2 3 4 5 6 7 8 9 |
# values.yaml image: repository: ghcr.io/your-org/user-service tag: "latest" replicaCount: 2 dapr: enabled: true appId: user-service |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# templates/deployment.yaml(抜粋) apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "user-service.fullname" . }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app.kubernetes.io/name: {{ include "user-service.name" . }} template: metadata: labels: app.kubernetes.io/name: {{ include "user-service.name" . }} annotations: dapr.io/enabled: "{{ .Values.dapr.enabled }}" dapr.io/app-id: "{{ .Values.dapr.appId }}" spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" ports: - containerPort: 8080 |
values.yamlのみを書き換えることで、ステージング・本番環境の差分を GitOps ツール(ArgoCD 等)から管理できる。
6. 実務で活かすベストプラクティス
API バージョニング & エラーハンドリング
|
1 2 3 4 |
@RestController @RequestMapping("/api/v1/users") class UserV1Controller { /* ... */ } |
- バージョンをパスに組み込むことで、将来的な互換性破壊が安全になる。
@RestControllerAdviceで例外を統一フォーマット(code,message) に変換すると、フロントエンド側の実装コストが削減できる。
|
1 2 3 4 5 6 7 8 9 |
@RestControllerAdvice class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ErrorResponse> notFound(ResourceNotFoundException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ErrorResponse("NOT_FOUND", ex.getMessage())); } } |
統一ロギング戦略
|
1 2 3 4 5 6 7 8 9 10 11 |
<!-- 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> </configuration> |
- JSON 形式で出力し、Elastic Stack(Filebeat → Elasticsearch → Kibana)へ流すと検索・集計が容易になる。
- Sleuth が自動付与する
traceIdと組み合わせることで、サービス間トレーシングが一貫して取得できる。
CI/CD パイプライン(GitHub Actions)
|
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 47 48 49 50 51 52 53 |
# .github/workflows/ci-cd.yml name: CI / CD on: push: branches: [ main ] jobs: build-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup JDK 21 uses: actions/setup-java@v3 with: java-version: '21' distribution: temurin - name: Maven Build run: ./mvnw -B package --file pom.xml - name: Test run: ./mvnw test docker-build-push: needs: build-test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Login to GHCR uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build & Push Image run: | IMAGE=ghcr.io/${{ github.repository }}/user-service:${{ github.sha }} docker build -t $IMAGE . docker push $IMAGE deploy: needs: docker-build-push runs-on: ubuntu-latest steps: - name: Setup kubectl uses: azure/setup-kubectl@v3 with: version: 'v1.27.0' - name: Deploy via Helm run: | helm upgrade --install user-service ./helm/user-service \ --set image.tag=${{ github.sha }} \ --namespace prod --create-namespace |
- ポイント
- ビルドとテストを分離し、失敗した段階でパイプラインが止まるようにする。
image.tagに Git SHA を使用すれば、デプロイ履歴がコード変更と1対1で追跡できる。
7. 次のステップ
- ローカル環境で実装確認
-
./mvnw spring-boot:run→/api/v1/usersが期待通りに動くかテスト。 -
コンテナ化・Compose でマルチサービスを体験
-
データベース、Config Server、ユーザーサービスが連携できることを確認。
-
Dapr sidecar を追加し State Store / Pub‑Sub のサンプルを実行
-
dapr run --app-id user-service --components-path ./componentsでローカルにデプロイ。 -
Helm Chart と GitHub Actions による自動デプロイ
-
helm install→ CI が成功したら本番クラスターへ自動的にリリースされるフローを構築。 -
Observability の整備
- Prometheus + Grafana ダッシュボードで JVM、HTTP latency、CircuitBreaker の指標を可視化し、アラート設定も行う。
本ガイドは Java 21 / Spring Boot 3.2 をベースに、マイクロサービスの設計・実装・デプロイまでを一貫して体験できるよう構成しています。最新バージョンや外部リンクの有効性は随時チェックし、組織の要件に合わせてカスタマイズしてください。