{"id":"spring-boot-engineer","name":"spring-boot-engineer","summary":"Spring Boot 3.xの設定生成、RESTコントローラーの作成、Spring Security 6認証フローの実装、Spring Data JPAリポジトリの構築、リアクティブWebFluxエンドポイントの設定などを行います。","body":"# Spring Boot Engineer\n\n## Core Workflow\n\n1. **Analyze requirements** — Identify service boundaries, APIs, data models, security needs\n2. **Design architecture** — Plan microservices, data access, cloud integration, security; confirm design before coding\n3. **Implement** — Create services with constructor injection and layered architecture (see Quick Start below)\n4. **Secure** — Add Spring Security, OAuth2, method security, CORS configuration; verify security rules compile and pass tests. If compilation or tests fail: review error output, fix the failing rule or configuration, and re-run before proceeding\n5. **Test** — Write unit, integration, and slice tests; run `./mvnw test` (or `./gradlew test`) and confirm all pass before proceeding. If tests fail: review the stack trace, isolate the failing assertion or component, fix the issue, and re-run the full suite\n6. **Deploy** — Configure health checks and observability via Actuator; validate `/actuator/health` returns `UP`. If health is `DOWN`: check the `components` detail in the response, resolve the failing component (e.g., datasource, broker), and re-validate\n\n## Reference Guide\n\nLoad detailed guidance based on context:\n\n| Topic | Reference | Load When |\n|-------|-----------|-----------|\n| Web Layer | `references/web.md` | Controllers, REST APIs, validation, exception handling |\n| Data Access | `references/data.md` | Spring Data JPA, repositories, transactions, projections |\n| Security | `references/security.md` | Spring Security 6, OAuth2, JWT, method security |\n| Cloud Native | `references/cloud.md` | Spring Cloud, Config, Discovery, Gateway, resilience |\n| Testing | `references/testing.md` | @SpringBootTest, MockMvc, Testcontainers, test slices |\n\n## Quick Start — Minimal Working Structure\n\nA standard Spring Boot feature consists of these layers. Use these as copy-paste starting points.\n\n### Entity\n\n```java\n@Entity\n@Table(name = \"products\")\npublic class Product {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @NotBlank\n    private String name;\n\n    @DecimalMin(\"0.0\")\n    private BigDecimal price;\n\n    // getters / setters or use @Data (Lombok)\n}\n```\n\n### Repository\n\n```java\npublic interface ProductRepository extends JpaRepository<Product, Long> {\n    List<Product> findByNameContainingIgnoreCase(String name);\n}\n```\n\n### Service (constructor injection)\n\n```java\n@Service\npublic class ProductService {\n    private final ProductRepository repo;\n\n    public ProductService(ProductRepository repo) { // constructor injection — no @Autowired\n        this.repo = repo;\n    }\n\n    @Transactional(readOnly = true)\n    public List<Product> search(String name) {\n        return repo.findByNameContainingIgnoreCase(name);\n    }\n\n    @Transactional\n    public Product create(ProductRequest request) {\n        var product = new Product();\n        product.setName(request.name());\n        product.setPrice(request.price());\n        return repo.save(product);\n    }\n}\n```\n\n### REST Controller\n\n```java\n@RestController\n@RequestMapping(\"/api/v1/products\")\n@Validated\npublic class ProductController {\n    private final ProductService service;\n\n    public ProductController(ProductService service) {\n        this.service = service;\n    }\n\n    @GetMapping\n    public List<Product> search(@RequestParam(defaultValue = \"\") String name) {\n        return service.search(name);\n    }\n\n    @PostMapping\n    @ResponseStatus(HttpStatus.CREATED)\n    public Product create(@Valid @RequestBody ProductRequest request) {\n        return service.create(request);\n    }\n}\n```\n\n### DTO (record)\n\n```java\npublic record ProductRequest(\n    @NotBlank String name,\n    @DecimalMin(\"0.0\") BigDecimal price\n) {}\n```\n\n### Global Exception Handler\n\n```java\n@RestControllerAdvice\npublic class GlobalExceptionHandler {\n    @ExceptionHandler(MethodArgumentNotValidException.class)\n    @ResponseStatus(HttpStatus.BAD_REQUEST)\n    public Map<String, String> handleValidation(MethodArgumentNotValidException ex) {\n        return ex.getBindingResult().getFieldErrors().stream()\n            .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));\n    }\n\n    @ExceptionHandler(EntityNotFoundException.class)\n    @ResponseStatus(HttpStatus.NOT_FOUND)\n    public Map<String, String> handleNotFound(EntityNotFoundException ex) {\n        return Map.of(\"error\", ex.getMessage());\n    }\n}\n```\n\n### Test Slice\n\n```java\n@WebMvcTest(ProductController.class)\nclass ProductControllerTest {\n    @Autowired MockMvc mockMvc;\n    @MockBean ProductService service;\n\n    @Test\n    void createProduct_validRequest_returns201() throws Exception {\n        var product = new Product(); product.setName(\"Widget\"); product.setPrice(BigDecimal.TEN);\n        when(service.create(any())).thenReturn(product);\n\n        mockMvc.perform(post(\"/api/v1/products\")\n                .contentType(MediaType.APPLICATION_JSON)\n                .content(\"\"\"{\"name\":\"Widget\",\"price\":10.0}\"\"\"))\n            .andExpect(status().isCreated())\n            .andExpect(jsonPath(\"$.name\").value(\"Widget\"));\n    }\n}\n```\n\n## Constraints\n\n### MUST DO\n\n| Rule | Correct Pattern |\n|------|----------------|\n| Constructor injection | `public MyService(Dep dep) { this.dep = dep; }` |\n| Validate API input | `@Valid @RequestBody MyRequest req` on every mutating endpoint |\n| Type-safe config | `@ConfigurationProperties(prefix = \"app\")` bound to a record/class |\n| Appropriate stereotype | `@Service` for business logic, `@Repository` for data, `@RestController` for HTTP |\n| Transaction scope | `@Transactional` on multi-step writes; `@Transactional(readOnly = true)` on reads |\n| Hide internals | Catch domain exceptions in `@RestControllerAdvice`; return problem details, not stack traces |\n| Externalize secrets | Use environment variables or Spring Cloud Config — never `application.properties` |\n\n### MUST NOT DO\n- Use field injection (`@Autowired` on fields)\n- Skip input validation on API endpoints\n- Use `@Component` when `@Service`/`@Repository`/`@Controller` applies\n- Mix blocking and reactive code (e.g., calling `.block()` inside a WebFlux chain)\n- Store secrets or credentials in `application.properties`/`application.yml`\n- Hardcode URLs, credentials, or environment-specific values\n- Use deprecated Spring Boot 2.x patterns (e.g., `WebSecurityConfigurerAdapter`)\n\n[Documentation](https://jeffallan.github.io/claude-skills/skills/backend/spring-boot-engineer/)","author":"@Jeffallan","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/Jeffallan/claude-skills/tree/main/skills/spring-boot-engineer","license":"MIT","category":"writing","lang":"en","tokens":1400,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[{"path":"references/cloud.md","size":12339,"sha256":"d2ae703cf2c9723d6e249b5dc149d825683e68b50aa5ba3343f4ac2a84976f51"},{"path":"references/data.md","size":11023,"sha256":"87bb091f484121d76bd25bef5e5b63c17a781c071f2498345af9b75c40ca9cdc"},{"path":"references/security.md","size":15275,"sha256":"aae4cf3e8f7e99298dc9f7b156dac97c6f656f89329ffdad8ba0bf5d3dfa684e"},{"path":"references/testing.md","size":14905,"sha256":"b3bc0fdbacf47a096fcf2b4ebdfba7c7e190f8fb8f312bf96c70313de7b28564"},{"path":"references/web.md","size":8990,"sha256":"670edd1b9e7807e0481b90f7df814b2bc3aa1b5f53b5b80908805dcd248fa844"}],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["api.example.com","auth.example.com","jeffallan.github.io"]}}