spring-boot-engineer について
本番運用を意識した Java サービスを構築する開発者向けの Spring Boot エンジニアリングスキルです。設計から実装、セキュリティ、テスト、デプロイまでを、分野別ガイドと基本構成例で支援します。
主な機能
- 01要件とアーキテクチャの計画
- 02レイヤー化した Spring Boot 実装ガイド
- 03セキュリティと入力検証の実践
- 04単体・統合・スライステスト
- 05Actuator のヘルスチェックガイド
活用シーン
- 01REST サービスの境界と API の設計
- 02検証付き Spring Boot エンドポイントの実装
- 03テストやセキュリティルールの失敗調査
- 04デプロイ前のサービス状態確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ jeffallan/ claude-skills --skill spring-boot-engineerインストール後、Claude Code で/spring-boot-engineerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/cloud.mddata.mdsecurity.mdtesting.mdweb.md詳細
Spring Boot エンジニア
コアワークフロー
- 要件を分析する — サービスの境界、API、データモデル、セキュリティ要件を特定します
- アーキテクチャを設計する — マイクロサービス、データアクセス、クラウド統合、セキュリティを計画し、コーディング前に設計を確認します
- 実装する — コンストラクターインジェクションとレイヤードアーキテクチャを使用してサービスを作成します(以下のクイックスタートを参照)
- セキュリティを確保する — Spring Security、OAuth2、メソッドセキュリティ、CORS 設定を追加し、セキュリティルールがコンパイルされ、テストに合格することを確認します。コンパイルまたはテストに失敗した場合は、エラー出力を確認し、失敗したルールまたは設定を修正してから、次に進む前に再実行します
- テストする — 単体テスト、統合テスト、スライステストを作成し、
./mvnw test(または./gradlew test)を実行して、次に進む前にすべて合格することを確認します。テストに失敗した場合は、スタックトレースを確認し、失敗したアサーションまたはコンポーネントを特定して修正し、テストスイート全体を再実行します - デプロイする — Actuator を使用してヘルスチェックとオブザーバビリティを設定し、
/actuator/healthがUPを返すことを確認します。ヘルス状態がDOWNの場合は、レスポンス内のcomponentsの詳細を確認し、失敗したコンポーネント(例:データソース、ブローカー)を解決して、再度確認します
リファレンスガイド
状況に応じて詳細なガイダンスを読み込みます。
| トピック | リファレンス | 読み込むタイミング |
|---|---|---|
| Web レイヤー | references/web.md | コントローラー、REST API、バリデーション、例外処理 |
| データアクセス | references/data.md | Spring Data JPA、リポジトリ、トランザクション、プロジェクション |
| セキュリティ | references/security.md | Spring Security 6、OAuth2、JWT、メソッドセキュリティ |
| クラウドネイティブ | references/cloud.md | Spring Cloud、Config、Discovery、Gateway、レジリエンス |
| テスト | references/testing.md | @SpringBootTest、MockMvc、Testcontainers、テストスライス |
クイックスタート — 最小限の動作構成
標準的な Spring Boot の機能は、次のレイヤーで構成されます。コピーして利用する際の出発点として使用してください。
エンティティ
@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 or use @Data (Lombok)
}
リポジトリ
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByNameContainingIgnoreCase(String name);
}
サービス(コンストラクターインジェクション)
@Service
public class ProductService {
private final ProductRepository repo;
public ProductService(ProductRepository repo) { // constructor injection — no @Autowired
this.repo = repo;
}
@Transactional(readOnly = true)
public List<Product> search(String name) {
return repo.findByNameContainingIgnoreCase(name);
}
@Transactional
public Product create(ProductRequest request) {
var product = new Product();
product.setName(request.name());
product.setPrice(request.price());
return 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<Product> search(@RequestParam(defaultValue = "") String name) {
return service.search(name);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Product create(@Valid @RequestBody ProductRequest request) {
return service.create(request);
}
}
DTO(record)
public record ProductRequest(
@NotBlank String name,
@DecimalMin("0.0") BigDecimal price
) {}
グローバル例外ハンドラー
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> handleValidation(MethodArgumentNotValidException ex) {
return ex.getBindingResult().getFieldErrors().stream()
.collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
}
@ExceptionHandler(EntityNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Map<String, String> handleNotFound(EntityNotFoundException ex) {
return Map.of("error", ex.getMessage());
}
}
テストスライス
@WebMvcTest(ProductController.class)
class ProductControllerTest {
@Autowired MockMvc mockMvc;
@MockBean ProductService service;
@Test
void createProduct_validRequest_returns201() throws Exception {
var product = new Product(); product.setName("Widget"); product.setPrice(BigDecimal.TEN);
when(service.create(any())).thenReturn(product);
mockMvc.perform(post("/api/v1/products")
.contentType(MediaType.APPLICATION_JSON)
.content("""{"name":"Widget","price":10.0}"""))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Widget"));
}
}
制約
必ず行うこと
| ルール | 正しいパターン |
|---|---|
| コンストラクターインジェクション | public MyService(Dep dep) { this.dep = dep; } |
| API 入力を検証する | すべての変更エンドポイントで @Valid @RequestBody MyRequest req を使用 |
| 型安全な設定 | レコードまたはクラスにバインドされた @ConfigurationProperties(prefix = "app") |
| 適切なステレオタイプ | ビジネスロジックには @Service、データには @Repository、HTTP には @RestController |
| トランザクションの範囲 | 複数ステップの書き込みには @Transactional、読み取りには @Transactional(readOnly = true) |
| 内部情報を隠す | @RestControllerAdvice でドメイン例外をキャッチし、スタックトレースではなく問題の詳細を返す |
| シークレットを外部化する | 環境変数または Spring Cloud Config を使用し、application.properties には決して記述しない |
絶対に行わないこと
- フィールドインジェクションを使用する(フィールドに
@Autowiredを付ける) - API エンドポイントの入力検証を省略する
@Service/@Repository/@Controllerが適切な場面で@Componentを使用する- ブロッキングコードとリアクティブコードを混在させる(例:WebFlux チェーン内で
.block()を呼び出す) - シークレットや認証情報を
application.properties/application.ymlに保存する - URL、認証情報、環境固有の値をハードコードする
- 非推奨の Spring Boot 2.x パターンを使用する(例:
WebSecurityConfigurerAdapter)
@jeffallan(Synergetic Solutions のプリンシパルコンサルタント)がメンテナンスしています
コメント
Markdown 対応