本文へ移動
cccskills
無料GitHub で公開

spring-boot-kotlin

Provides expert knowledge of Spring Boot conventions, Kotlin idioms, and MVC patterns for backend API development.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md10.2 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Spring Boot + Kotlin

Project Structure

Use feature-oriented MVC. One package per feature, not one package per layer.

src/main/kotlin/{base-package}/{feature}/
  controller/       # HTTP layer only — no business logic
  service/          # Business logic
  repository/       # Data access — extends JpaRepository or CrudRepository
  domain/           # JPA entities
  dto/              # Request/response objects — no @Entity here

src/test/kotlin/{base-package}/{feature}/
  controller/       # MockMvc tests
  service/          # Unit tests with MockK
  repository/       # @DataJpaTest tests

src/main/resources/
  application.yml
  application-prod.yml    # NOT in repo — use env vars
  db/migration/           # Flyway scripts

Never use src/main/kotlin/controllers/, src/main/kotlin/services/, etc. That is layer-first structure and it does not scale.

Kotlin Idioms for Spring

Data classes for DTOs. No setters, no mutable state.

data class CreateUserRequest(
    @field:NotBlank val email: String,
    @field:Size(min = 2, max = 100) val name: String
)

data class UserResponse(
    val id: UUID,
    val email: String,
    val name: String
)

Sealed classes for domain results and errors.

sealed class UserResult {
    data class Found(val user: User) : UserResult()
    data class NotFound(val id: UUID) : UserResult()
    data class Conflict(val email: String) : UserResult()
}

Null safety. Never use !! unless you have already verified the value is non-null and can document why. Prefer:

// bad
val name = user!!.name

// good
val name = user?.name ?: throw UserNotFoundException(id)

// good — scoped null check
user?.let { sendWelcomeEmail(it.email) }

Extension functions instead of utility classes.

// bad — Java style utility class
object UserMapper {
    fun toResponse(user: User): UserResponse { ... }
}

// good — extension function
fun User.toResponse(): UserResponse = UserResponse(
    id = id,
    email = email,
    name = name
)

Coroutines: do not introduce coroutines unless the project already uses them. Adding coroutines requires a Task Card with explicit scope.

Spring Boot Patterns

Controller

@RestController
@RequestMapping("/api/v1/users")
class UserController(private val userService: UserService) {

    @GetMapping("/{id}")
    fun getUser(@PathVariable id: UUID): ResponseEntity<UserResponse> {
        return when (val result = userService.getById(id)) {
            is UserResult.Found -> ResponseEntity.ok(result.user.toResponse())
            is UserResult.NotFound -> ResponseEntity.notFound().build()
        }
    }

    @PostMapping
    fun createUser(
        @Valid @RequestBody request: CreateUserRequest
    ): ResponseEntity<UserResponse> {
        val result = userService.create(request)
        return ResponseEntity.status(HttpStatus.CREATED).body(result.toResponse())
    }
}

Rules:

  • One controller per feature
  • No business logic in controllers — delegate to service
  • Use @Valid on request body params
  • Return ResponseEntity<T> for explicit HTTP status control

Service

@Service
@Transactional
class UserService(private val userRepository: UserRepository) {

    @Transactional(readOnly = true)
    fun getById(id: UUID): UserResult {
        val user = userRepository.findById(id).orElse(null)
            ?: return UserResult.NotFound(id)
        return UserResult.Found(user)
    }

    fun create(request: CreateUserRequest): User {
        if (userRepository.existsByEmail(request.email)) {
            throw UserConflictException(request.email)
        }
        return userRepository.save(User(email = request.email, name = request.name))
    }
}

Rules:

  • All business logic lives here
  • @Transactional at class level, readOnly = true for queries
  • Never @Transactional on private methods — Spring proxy does not intercept them
  • Never inject repositories into controllers

Repository

@Repository
interface UserRepository : JpaRepository<User, UUID> {
    fun findByEmail(email: String): User?
    fun existsByEmail(email: String): Boolean

    @Query("SELECT u FROM User u WHERE u.role = :role AND u.active = true")
    fun findActiveByRole(@Param("role") role: UserRole): List<User>
}

Rules:

  • Extend JpaRepository<Entity, IdType> or CrudRepository
  • Derived query methods for simple lookups
  • @Query with JPQL for complex queries
  • Native SQL only when JPQL cannot express it

Entity

@Entity
@Table(
    name = "users",
    indexes = [Index(columnList = "email", unique = true)]
)
class User(
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    val id: UUID = UUID.randomUUID(),

    @Column(name = "email", nullable = false, unique = true)
    var email: String,

    @Column(name = "name", nullable = false)
    var name: String,

    @Column(name = "created_at", nullable = false, updatable = false)
    @CreatedDate
    val createdAt: Instant = Instant.now()
)

Rules:

  • @Table with explicit name — never rely on inferred names
  • @Column(nullable = false) always explicit
  • UUIDs as PKs with GenerationType.UUID
  • Kotlin data class with JPA requires the Kotlin JPA plugin (adds no-arg constructor)
  • equals/hashCode based only on id, not all fields — or omit and use reference equality

DTO Separation

Never expose @Entity directly in API responses. Always map to a DTO.

// bad
@GetMapping("/{id}")
fun getUser(@PathVariable id: UUID): User  // exposes entity internals

// good
@GetMapping("/{id}")
fun getUser(@PathVariable id: UUID): UserResponse  // controlled surface

Validation

Validate at the DTO layer, not the service layer.

data class CreateUserRequest(
    @field:NotBlank(message = "Email is required")
    @field:Email(message = "Email must be valid")
    val email: String,

    @field:NotBlank(message = "Name is required")
    @field:Size(min = 2, max = 100, message = "Name must be between 2 and 100 characters")
    val name: String
)

Use @field: prefix on annotations — Kotlin applies annotations to the property by default, not the backing field.

Error Handling

@ControllerAdvice
class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException::class)
    fun handleUserNotFound(ex: UserNotFoundException): ResponseEntity<ErrorResponse> {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(ErrorResponse(error = ex.message ?: "User not found", code = "USER_NOT_FOUND"))
    }

    @ExceptionHandler(MethodArgumentNotValidException::class)
    fun handleValidation(ex: MethodArgumentNotValidException): ResponseEntity<ErrorResponse> {
        val message = ex.bindingResult.fieldErrors
            .joinToString(", ") { "${it.field}: ${it.defaultMessage}" }
        return ResponseEntity.badRequest()
            .body(ErrorResponse(error = message, code = "VALIDATION_ERROR"))
    }
}

data class ErrorResponse(val error: String, val code: String)

Rules:

  • One @ControllerAdvice — do not scatter @ExceptionHandler across controllers
  • Typed domain exceptions — never throw RuntimeException directly
  • Consistent error body: { "error": "...", "code": "..." }
  • Never expose stack traces in responses

Testing

Unit tests with MockK.

@ExtendWith(MockKExtension::class)
class UserServiceTest {

    @MockK
    private lateinit var userRepository: UserRepository

    private lateinit var userService: UserService

    @BeforeEach
    fun setUp() {
        userService = UserService(userRepository)
    }

    @Test
    fun `should return NotFound when user does not exist`() {
        // Arrange
        val id = UUID.randomUUID()
        every { userRepository.findById(id) } returns Optional.empty()

        // Act
        val result = userService.getById(id)

        // Assert
        assertThat(result).isInstanceOf(UserResult.NotFound::class.java)
        verify(exactly = 1) { userRepository.findById(id) }
    }
}

Integration tests with MockMvc.

@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {

    @Autowired
    private lateinit var mockMvc: MockMvc

    @Test
    fun `should return 404 when user is not found`() {
        mockMvc.perform(get("/api/v1/users/${UUID.randomUUID()}"))
            .andExpect(status().isNotFound)
    }
}

Repository tests.

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private lateinit var userRepository: UserRepository

    @Test
    fun `should find user by email`() {
        val user = userRepository.save(User(email = "test@example.com", name = "Test"))
        val found = userRepository.findByEmail("test@example.com")
        assertThat(found).isEqualTo(user)
    }
}

Rules:

  • Use MockK, not Mockito — the Kotlin API is cleaner and null-safe
  • Do not use @MockBean where mockk<T>() is sufficient
  • @DataJpaTest with H2 for repository tests (fast)
  • @SpringBootTest + @AutoConfigureMockMvc for controller tests

Security Rules

  • No stack traces in API responses — only @ControllerAdvice with controlled messages
  • Validate all input in DTO layer — service layer should receive already-validated data
  • application-prod.yml must not be committed — use environment variables or a secrets manager
  • Credentials, tokens, and keys are never hardcoded — use @Value with externalized config

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Use when adding monitoring, metrics, logging, or tracing to Spring AI or LLM integration code. Covers token tracking, latency measurement, cost estimation, and prompt/response logging. Use when user mentions AI monitoring, token costs, or LLM observability.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

android

無料

Provides expert knowledge for building modern native Android apps using Kotlin, Jetpack Compose, Material Design 3, MVI architecture, Hilt dependency injection, and Media3/ExoPlayer.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

Design, implement, review, and audit Android Jetpack Compose UI/UX for phone interfaces. Use when a request concerns concrete Compose layout, components, interaction, accessibility, or visual behavior. Do not activate for framework or dependency presence alone; non-UI Android work is excluded.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

Provides expert knowledge for designing, implementing, and managing REST API versioning strategies with deprecation workflows.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

astro

無料

Provides expert knowledge for building Astro 5+ sites with Islands Architecture, Content Collections, TypeScript, and performance-first rendering strategies.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

Audits token storage mechanisms to prevent XSS-based JWT theft.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

lgzarturo のスキルをすべて見る

このスキルの問題を報告する