关于 spring-boot-crud-patterns
用于构建和诊断 Spring Boot 3.5+ CRUD 服务的技能,采用按功能组织、受 DDD 启发的分层结构。它指导实现领域模型、JPA 持久化、应用服务、DTO 和 REST 控制器,并提供验证步骤。
主要功能
- 01按功能划分包结构
- 02领域聚合与仓储端口
- 03JPA 持久化适配器
- 04事务型应用服务
- 05经过验证的 DTO 和 REST 控制器
- 06测试与迁移检查
适用场景
- 01创建 Spring Data JPA CRUD 接口
- 02围绕领域功能组织服务
- 03诊断仓储或事务回归问题
- 04为 REST 列表接口添加分页
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill spring-boot-crud-patterns安装后在 Claude Code 中输入/spring-boot-crud-patterns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/specs/product_with_rel.jsonproduct.jsonreferences/crud-reference.mdexamples-product-feature.mdgenerator-usage.mdspring-official-docs.mdreferences/templates/Controller.java.tplCreateService.java.tplDeleteService.java.tplDomainModel.java.tplDomainRepository.java.tplDomainService.java.tplDtoRequest.java.tplDtoResponse.java.tplEntityExceptionHandler.java.tplErrorResponse.java.tplExistException.java.tplGetService.java.tplGlobalExceptionHandler.java.tplJpaEntity.java.tplListService.java.tplMapper.java.tplNotFoundException.java.tplPageResponse.java.tplPersistenceAdapter.java.tplREADME.mdSpringDataRepository.java.tplUpdateService.java.tplscripts/examples/product_with_rel.jsonproduct.json详细介绍
Spring Boot CRUD 模式
概述
为使用以功能为中心架构的 Spring Boot 3.5+ 服务提供完整的 CRUD 工作流。创建并验证领域聚合、JPA 仓库、应用服务和 REST 控制器,并确保关注点得到合理分离。详细代码清单请参阅参考文件,以便逐步展开内容。
适用场景
- 为由 Spring Data JPA 支持的创建/读取/更新/删除工作流创建 REST 端点。
- 按照受 DDD 启发的架构实现功能包,包含聚合、仓库和应用服务。
- 为外部客户端定义 DTO record、请求验证和控制器映射。
- 诊断现有 Spring Boot 服务中的 CRUD 回归问题、仓库契约或事务边界。
- 触发短语:“实现 Spring CRUD 控制器”、“创建端点”、“添加数据库实体”、“改进基于功能的仓库”、“为 JPA 聚合映射 DTO”、“为 REST 列表端点添加分页”。
操作说明
按照以下精简工作流构建与功能相匹配的 CRUD 服务,并设置明确的验证关卡:
1. 建立功能结构
创建 feature/<name>/ 目录,并包含 domain、application、presentation 和 infrastructure 子包。
验证:继续之前,确认目录结构与功能边界一致。
2. 定义领域模型
创建实体类,并通过工厂方法(create、update)强制执行不变量。领域逻辑应与框架无关。
验证:进入下一步之前,确认所有不变量均已由单元测试覆盖。
3. 暴露领域端口
在 domain/repository 中声明仓库接口,描述持久化契约,但不包含实现细节。
验证:确认接口签名与领域操作一致。
4. 提供基础设施适配器
在 infrastructure/persistence 中创建映射到领域模型的 JPA 实体,并实现 Spring Data 仓库。
验证:运行 @DataJpaTest,验证实体映射和仓库集成。
5. 实现应用服务
创建用于编排领域操作和 DTO 映射的 @Transactional 服务类。
验证:确保事务边界正确,并在需要时应用乐观锁。
6. 定义 DTO 和控制器
使用带有 jakarta.validation 注解的 Java record 定义 API 契约。映射 REST 端点并设置正确的状态码。
验证:测试验证约束,并确认 HTTP 状态码(POST 为 201、GET 为 200、DELETE 为 204)。
7. 验证并部署
使用 Testcontainers 运行集成测试。确认迁移(Liquibase/Flyway)与聚合架构一致。 验证:部署前执行完整测试套件,并确认已应用架构迁移脚本。
完整的代码示例与各步骤对应,请参阅 references/examples-product-feature.md。
示例
Java 代码示例:Product 功能
// feature/product/domain/Product.java
package com.example.product.domain;
import java.math.BigDecimal;
import java.time.Instant;
public record Product(
String id,
String name,
String description,
BigDecimal price,
int stock,
Instant createdAt,
Instant updatedAt
) {
public static Product create(String name, String desc, BigDecimal price, int stock) {
if (name == null || name.isBlank()) throw new IllegalArgumentException("Name required");
if (price == null || price.compareTo(BigDecimal.ZERO) < 0) throw new IllegalArgumentException("Invalid price");
return new Product(null, name.trim(), desc, price, stock, Instant.now(), null);
}
public Product withPrice(BigDecimal newPrice) {
return new Product(id, name, description, newPrice, stock, createdAt, Instant.now());
}
}
// feature/product/domain/repository/ProductRepository.java
package com.example.product.domain.repository;
import com.example.product.domain.Product;
import java.util.Optional;
public interface ProductRepository {
Product save(Product product);
Optional<Product> findById(String id);
void deleteById(String id);
}
// feature/product/infrastructure/persistence/ProductJpaEntity.java
package com.example.product.infrastructure.persistence;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.Instant;
@Entity @Table(name = "products")
public class ProductJpaEntity {
@Id @GeneratedValue(strategy = GenerationType.UUID)
private String id;
private String name;
private String description;
private BigDecimal price;
private int stock;
private Instant createdAt;
private Instant updatedAt;
// getters, setters, constructor from domain (omitted for brevity)
}
// feature/product/infrastructure/persistence/JpaProductRepository.java
package com.example.product.infrastructure.persistence;
import com.example.product.domain.Product;
import com.example.product.domain.repository.ProductRepository;
import org.springframework.stereotype.Repository;
@Repository
public class JpaProductRepository implements ProductRepository {
private final SpringDataProductRepository springData;
public JpaProductRepository(SpringDataProductRepository springData) {
this.springData = springData;
}
@Override
public Product save(Product product) {
ProductJpaEntity entity = toEntity(product);
ProductJpaEntity saved = springData.save(entity);
return toDomain(saved);
}
// findById, deleteById implementations...
}
// feature/product/presentation/rest/ProductController.java
package com.example.product.presentation.rest;
import com.example.product.domain.Product;
import com.example.product.domain.repository.ProductRepository;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController @RequestMapping("/api/products")
public class ProductController {
private final ProductService service;
public ProductController(ProductService service) { this.service = service; }
@PostMapping
public ResponseEntity<ProductResponse> create(@Valid @RequestBody CreateProductRequest req) {
Product product = service.create(req.toDomain());
return ResponseEntity.status(201).body(ProductResponse.from(product));
}
@GetMapping("/{id}")
public ResponseEntity<ProductResponse> getById(@PathVariable String id) {
return service.findById(id)
.map(p -> ResponseEntity.ok(ProductResponse.from(p)))
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable String id) {
service.deleteById(id);
return ResponseEntity.noContent().build();
}
// record DTOs
public record CreateProductRequest(
@NotBlank String name,
String description,
@NotNull @DecimalMin("0.01") java.math.BigDecimal price,
@Min(0) int stock
) {
Product toDomain() { return Product.create(name, description, price, stock); }
}
public record ProductResponse(String id, String name, java.math.BigDecimal price) {
static ProductResponse from(Product p) { return new ProductResponse(p.id(), p.name(), p.price()); }
}
}
JSON 输入/输出示例
创建请求:
{
"name": "Wireless Keyboard",
"description": "Ergonomic keyboard",
"price": 79.99,
"stock": 50
}
创建成功响应(201):
{
"id": "prod-123",
"name": "Wireless Keyboard",
"price": 79.99,
"_links": { "self": "/api/products/prod-123" }
}
分页列表请求:
curl "http://localhost:8080/api/products?page=0&size=10&sort=name,asc"
最佳实践
- 在功能包中按聚合将领域、应用和展示代码放在一起。
- 使用 Java record 创建不可变 DTO;在服务边界转换领域类型。
- 对写操作应用事务和乐观锁。
- 规范分页默认值(page、size、sort),并记录查询参数。
- 在 info 级别记录 CRUD 生命周期事件(创建、更新、删除),并提供结构化审计跟踪。
- 通过 Spring Boot Actuator 提供健康状态和指标;监控吞吐量和错误率。
约束与警告
- 绝不在控制器中直接暴露 JPA 实体,以防止延迟加载泄漏和序列化问题。
- 绝不混用字段注入和构造函数注入;保持不可变性,以便于测试。
- 绝不在控制器或仓储适配器中嵌入业务逻辑;将其保留在领域层或应用层。
- 始终严格验证输入,以防止违反约束,并生成一致的错误负载。
- 始终确保在部署架构变更之前,迁移(Liquibase/Flyway)与聚合的演进保持一致。
- 始终在合并之前使用 Testcontainers 运行集成测试,以防止持久化回归。
参考资料
- HTTP 方法、注解、DTO 模式
- 从入门到进阶的渐进式示例
- Spring Boot 官方文档
- CRUD 生成器脚本 -
python scripts/generate_crud_boilerplate.py --spec entity.json --package com.example.product --output ./generated