kotlin-spring-boot

安装量: 108
排名: #7850

安装

npx skills add https://github.com/ashchupliak/dream-team --skill kotlin-spring-boot

Kotlin Spring Boot Patterns Project Configuration // build.gradle.kts plugins { kotlin("jvm") version "2.2.21" kotlin("plugin.spring") version "2.2.21" id("org.springframework.boot") version "3.5.7" }

dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") }

Entity Pattern data class Environment( val id: UUID, val name: String, val status: EnvironmentStatus, val createdAt: Instant, val updatedAt: Instant? )

enum class EnvironmentStatus { PENDING, RUNNING, STOPPED, FAILED }

Service Pattern @Service class EnvironmentService( private val repository: EnvironmentRepository, private val computeClient: ComputeClient ) { // Use NEVER propagation - let caller control transaction @Transactional(propagation = Propagation.NEVER) fun create(request: CreateEnvironmentRequest): Pair { // Check for existing (idempotency) repository.findByName(request.name)?.let { return Pair(it.toResponse(), false) // existing }

    // Create new
    val environment = Environment(
        id = UUID.randomUUID(),
        name = request.name,
        status = EnvironmentStatus.PENDING,
        createdAt = Instant.now(),
        updatedAt = null
    )

    val saved = repository.save(environment)
    return Pair(saved.toResponse(), true) // created
}

fun findById(id: UUID): Environment =
    repository.findById(id)
        ?: throw ResourceNotFoundRestException("Environment", id)

fun findAll(): List<Environment> =
    repository.findAll()

}

Controller Pattern @RestController class EnvironmentController( private val service: EnvironmentService ) : EnvironmentApi {

override fun create(request: CreateEnvironmentRequest): ResponseEntity<EnvironmentResponse> {
    val (result, isNew) = service.create(request)
    return if (isNew) {
        ResponseEntity.status(HttpStatus.CREATED).body(result)
    } else {
        ResponseEntity.ok(result)
    }
}

override fun getById(id: UUID): ResponseEntity<EnvironmentResponse> =
    ResponseEntity.ok(service.findById(id).toResponse())

override fun list(): ResponseEntity<List<EnvironmentResponse>> =
    ResponseEntity.ok(service.findAll().map { it.toResponse() })

}

API Interface Pattern (OpenAPI) @Tag(name = "Environments", description = "Environment management") interface EnvironmentApi {

@Operation(summary = "Create environment")
@ApiResponses(
    ApiResponse(responseCode = "201", description = "Created"),
    ApiResponse(responseCode = "200", description = "Already exists"),
    ApiResponse(responseCode = "400", description = "Validation error")
)
@PostMapping("/api/v1/environments")
fun create(
    @RequestBody @Valid request: CreateEnvironmentRequest
): ResponseEntity<EnvironmentResponse>

@Operation(summary = "Get environment by ID")
@GetMapping("/api/v1/environments/{id}")
fun getById(@PathVariable id: UUID): ResponseEntity<EnvironmentResponse>

@Operation(summary = "List all environments")
@GetMapping("/api/v1/environments")
fun list(): ResponseEntity<List<EnvironmentResponse>>

}

DTO Pattern data class CreateEnvironmentRequest( @field:NotBlank(message = "Name is required") @field:Size(max = 100, message = "Name must be <= 100 chars") val name: String,

@field:Size(max = 500)
val description: String? = null

)

data class EnvironmentResponse( val id: UUID, val name: String, val status: String, val createdAt: Instant )

// Extension function for mapping fun Environment.toResponse() = EnvironmentResponse( id = id, name = name, status = status.name, createdAt = createdAt )

Exception Handling // Typed exceptions throw ResourceNotFoundRestException("Environment", id) throw ValidationRestException("Name cannot be empty") throw ConflictRestException("Environment already exists")

// Global handler @RestControllerAdvice class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundRestException::class)
fun handleNotFound(ex: ResourceNotFoundRestException): ResponseEntity<ErrorResponse> =
    ResponseEntity.status(HttpStatus.NOT_FOUND)
        .body(ErrorResponse(ex.message ?: "Not found"))

@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidation(ex: MethodArgumentNotValidException): ResponseEntity<ErrorResponse> {
    val errors = ex.bindingResult.fieldErrors.map { "${it.field}: ${it.defaultMessage}" }
    return ResponseEntity.badRequest()
        .body(ErrorResponse("Validation failed", errors))
}

}

Kotlin Idioms // Use ?.let for optional operations user?.let { repository.save(it) }

// Use when for exhaustive matching when (status) { EnvironmentStatus.PENDING -> startEnvironment() EnvironmentStatus.RUNNING -> return // already running EnvironmentStatus.STOPPED -> restartEnvironment() EnvironmentStatus.FAILED -> throw IllegalStateException("Cannot start failed env") }

// Avoid !! - use alternatives repository.findById(id).single() // throws if not exactly one repository.findById(id).firstOrNull() // returns null if none

// Data class copy for immutable updates val updated = environment.copy( status = EnvironmentStatus.RUNNING, updatedAt = Instant.now() )

Configuration Properties @ConfigurationProperties(prefix = "orca") data class OrcaProperties( val compute: ComputeProperties, val timeouts: TimeoutProperties ) { data class ComputeProperties( val url: String, val timeout: Duration = Duration.ofSeconds(30) )

data class TimeoutProperties(
    val creation: Duration = Duration.ofMinutes(5),
    val termination: Duration = Duration.ofMinutes(2)
)

}

返回排行榜