mcpField
S
spring-boot

spring-boot

github.com/piomin開発1.4k89 回インストール1 週間前に更新
リポジトリ

spring-boot について

本番向けサービスを構築する開発者を対象とした、Spring Boot 4.x開発スキルです。クリーンアーキテクチャ、セキュリティ、テスト、データアクセス、デプロイの指針を、具体的な規約やコードパターンとともに提供します。

主な機能

  1. 01レイヤードアーキテクチャとコンストラクターインジェクション
  2. 02RESTのバリデーションとProblem Details形式のエラー
  3. 03セキュリティとOAuth2/JWTの指針
  4. 04Flywayマイグレーションとデータアクセスのパターン
  5. 05ユニットテストと統合テストの手順
  6. 06Actuatorのヘルスチェック設定

活用シーン

  1. 01実装前のSpring Bootサービス設計
  2. 02検証とエラー処理を備えたRESTエンドポイントの構築
  3. 03セキュリティ機能とデータベースマイグレーションの追加
  4. 04デプロイ前のテストとヘルスチェックの確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/piomin/claude-ai-spring-boot --skill spring-boot

インストール後、Claude Code で/spring-bootを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
cloud.md
data.md
security.md
testing.md
ui-contract.md
web.md

詳細

Spring Boot スキル

クリーンアーキテクチャと本番環境に対応したコードを重視した、エンタープライズ向け Spring Boot 4.x 開発です。

基本的なワークフロー

  1. 分析 - 要件を理解し、サービスの境界、API、データモデルを特定します
  2. 設計 - アーキテクチャを計画し、コーディング前に設計を確認します
  3. 実装 - コンストラクターインジェクションとレイヤードアーキテクチャを使用して構築します。リレーショナルデータベースを使用する場合は、エンティティと並行して Flyway マイグレーションを作成します(ddl-auto: validate)
  4. セキュリティ保護 - Spring Security、OAuth2、メソッドセキュリティを追加し、テストが合格することを確認します
  5. テスト - 単体テストと統合テストを作成します。./mvnw clean verify を実行してすべて合格することを確認します。統合テストはスキップせずに実行します
  6. デプロイ - Actuator でヘルスチェックを設定します。/actuator/health が UP を返し、/actuator/info に build.version が表示されることを確認します

クイックスタートテンプレート

エンティティ
@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    private String name;

    @DecimalMin("0.0")
    private BigDecimal price;

    // Getters/Setters (no Lombok)
}
リポジトリ
public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findByNameContainingIgnoreCase(String name);
}
サービス
@Service
@Transactional(readOnly = true)
public class ProductService {
    private final ProductRepository repo;

    public ProductService(ProductRepository repo) {
        this.repo = repo;
    }

    public List<ProductResponse> search(String name) {
        return repo.findByNameContainingIgnoreCase(name).stream()
            .map(ProductResponse::from)
            .toList();
    }

    @Transactional
    public ProductResponse create(ProductRequest request) {
        var product = new Product();
        product.setName(request.name());
        product.setPrice(request.price());
        return ProductResponse.from(repo.save(product));
    }
}
REST コントローラー
@RestController
@RequestMapping("/api/v1/products")
@Validated
public class ProductController {
    private final ProductService service;

    public ProductController(ProductService service) {
        this.service = service;
    }

    @GetMapping
    public List<ProductResponse> search(@RequestParam(defaultValue = "") String name) {
        return service.search(name);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public ProductResponse create(@Valid @RequestBody ProductRequest request) {
        return service.create(request);
    }
}
DTO(レコード)
public record ProductRequest(
    @NotBlank String name,
    @DecimalMin("0.0") BigDecimal price
) {}

public record ProductResponse(Long id, String name, BigDecimal price) {
    public static ProductResponse from(Product product) {
        return new ProductResponse(product.getId(), product.getName(), product.getPrice());
    }
}
グローバル例外ハンドラー(Problem Details)
@RestControllerAdvice
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {

    public record FieldMessage(String field, String code, String message) {}

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers,
                                                                  HttpStatusCode status, WebRequest request) {
        ex.getBody().setProperty("errors", ex.getFieldErrors().stream()
                .map(e -> new FieldMessage(e.getField(), code(e.getCode()), e.getDefaultMessage()))
                .toList());
        return super.handleMethodArgumentNotValid(ex, headers, status, request);
    }

    private static String code(String constraint) {
        return switch (constraint) {
            case "NotBlank", "NotNull" -> "required";
            case null, default -> "invalid";
        };
    }

    @ExceptionHandler
    ProblemDetail handleOptimisticLock(ObjectOptimisticLockingFailureException ex) {
        return ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, "Record was changed by someone else; reload and retry");
    }

    @ExceptionHandler
    ProblemDetail handleUnexpected(Exception ex) {
        logger.error("Unexpected error", ex);
        return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected server error");
    }
}

すべてのエラーは RFC 9457 Problem Details 形式で、フィールドエラーは errors: [{field, code, message}] に含めます。ハンドラーの完全版(一意制約、不正なソート、ドキュメント化されたコード)については、references/web.md を参照してください。

テストスライス
@WebMvcTest(ProductController.class)
class ProductControllerTest {
    @Autowired MockMvc mockMvc;
    @MockitoBean ProductService service;

    @Test
    void createProduct_validRequest_returns201() throws Exception {
        var response = new ProductResponse(1L, "Widget", new BigDecimal("10.00"));
        when(service.create(any())).thenReturn(response);

        mockMvc.perform(post("/api/v1/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {"name":"Widget","price":10.0}
                    """))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name").value("Widget"));
    }
}

リファレンスガイド

状況に応じて詳細なパターンを読み込みます。

トピックリファレンス読み込むタイミング
Web/RESTreferences/web.mdコントローラー、ページ分割されたリスト、バリデーション、PUT と version、errors[] を含む Problem Details(RFC 9457)
UI 向けに構築された APIreferences/ui-contract.mdUI または生成されたクライアントが OpenAPI コントラクトを利用する場合のみ(conventions を参照):/api、springdoc 3 のコントラクトエクスポート、エラーコード、フィルター、リレーション、Keycloak、「UI を接続する」
データアクセスreferences/data.mdJPA、リポジトリ、トランザクション、クエリ、Flyway マイグレーション
セキュリティreferences/security.mdSpring Security 6、OAuth2、JWT、認証
クラウド/設定references/cloud.mdConfig Server、サービスディスカバリー、レジリエンス、仮想スレッド、HTTP インターフェイス、GraalVM ネイティブ、OTLP トレーシング
テストreferences/testing.md単体テスト、統合テスト、スライステスト、Testcontainers @ServiceConnection

制約

必ず行うこと
  • コンストラクターインジェクションを使用する(フィールドインジェクションは禁止)
  • すべてのリクエストボディに @Valid を付ける
  • 複数ステップの書き込みには @Transactional を使用する
  • 読み取りには @Transactional(readOnly = true) を使用する
  • @ConfigurationProperties による型安全な設定を使用する
  • @RestControllerAdvice によるグローバル例外処理を行う
  • シークレットを外部化する(プロパティファイルではなく環境変数を使用する)
  • リレーショナルデータベースにデータを永続化する場合は Flyway マイグレーションと ddl-auto: validate を使用する(それ以外の場合は Flyway を使用しない)
  • エラーは Problem Details 形式とし、リストエンドポイントは PagedModel を返す
  • セキュリティの背後でライブネスプローブとレディネスプローブが機能するよう、/actuator/health/** を許可する
  • UI 向けに API を構築する場合のみ(conventions の「UI 向けに構築された REST API」を参照)、references/ui-contract.md に従う
必ず避けること
  • フィールドインジェクション(フィールドへの @Autowired)
  • エンドポイントの入力バリデーションを省略する
  • ブロッキングコードとリアクティブコードを混在させる
  • シークレットを application.properties に保存する
  • 非推奨の Spring Boot 2.x/3.x パターンを使用する
  • URL、認証情報、環境値をハードコードする
  • テスト以外で ddl-auto: update/create を使用する、または適用済みのマイグレーションを編集する
  • コントローラーから Page/PageImpl を返す、またはアプリケーションクラスに @EnableSpringDataWebSupport を付ける(@WebMvcTest で二重に登録されます)
  • コントローラーメソッドに @ApiResponse を付ける(springdoc 3 では成功レスポンスが削除されます。OpenApiCustomizer を使用してください)
  • キャッチオールハンドラーから例外のメッセージを返す
Spring Boot 4 / Spring Data 4 に関する注意事項
  • PropertyReferenceException と TypeInformation は org.springframework.data.core に移動しました
  • @MockBean/@SpyBean は削除されました。@MockitoBean/@MockitoSpyBean を使用してください
  • @WebMvcTest はリポジトリを作成しません。コントローラーが注入するすべてのリポジトリに対して、テスト内で @MockitoBean が必要です
  • ./mvnw clean verify でビルドしてください。target 内に IDE がコンパイルしたクラスがあると、javac のエラーが隠れることがあります

アーキテクチャパターン

プロジェクト構成:

src/main/java/pl/piomin/services/
├── controller/     # REST endpoints
├── service/        # Business logic
├── repository/     # Data access
├── model/          # Entities
├── dto/            # Request/Response DTOs
├── config/         # Configuration
└── exception/      # Custom exceptions + handler

レイヤー構成:

  • コントローラー → サービス → リポジトリ
  • コントローラーは HTTP とバリデーションを担当します
  • サービスはビジネスロジックとトランザクションを担当します
  • リポジトリはデータ永続化を担当します

クリーンアーキテクチャの原則:

  • ドメインモデルはフレームワークから独立させます
  • ユースケース駆動の設計
  • 依存性逆転(インターフェース)
  • レイヤー間の境界を明確にします

よく使われるアノテーション

アノテーション用途
@RestControllerREST コントローラー(@Controller と @ResponseBody を組み合わせたもの)
@Serviceビジネスロジックコンポーネント
@Repositoryデータアクセスコンポーネント
@Transactionalトランザクション管理
@Validバリデーションを実行
@ConfigurationPropertiesプロパティをクラスにバインド
@EnableMethodSecurityメソッドセキュリティを有効化

リアクティブ WebFlux エンドポイント

@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/{id}")
    public Mono<ResponseEntity<OrderDto>> getOrder(@PathVariable UUID id) {
        return orderService.findById(id)
                .map(ResponseEntity::ok)
                .defaultIfEmpty(ResponseEntity.notFound().build());
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<OrderDto> createOrder(@Valid @RequestBody CreateOrderRequest request) {
        return orderService.create(request);
    }
}

Spring Security JWT

@Configuration
@EnableMethodSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/actuator/health/**").permitAll()  // + "/actuator/info" when built for a UI
                        .anyRequest().authenticated())
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
                .build();
    }
}

ナレッジベース

Spring Boot 4.x、Java 21+、Spring WebFlux、Project Reactor、Spring Data JPA、Spring Security、OAuth2/JWT、Hibernate、R2DBC、Spring Cloud、Resilience4j、Micrometer、JUnit 5、Testcontainers、Mockito、Maven、Paketo Buildpacks、Skaffold

コメント