kotlin-patterns について
kotlin-patterns は、Orca プロジェクトで使う Kotlin の実装パターンをまとめたリファレンスです。サーバーサイド開発者向けに、構成要素や例外処理、null 安全の規約を示します。
主な機能
- 01エンティティと DTO のパターン
- 02サービスとリポジトリのパターン
- 03コントローラーと API インターフェースのパターン
- 04型付き例外処理
- 05Kotlin の null 安全ガイドライン
活用シーン
- 01エンティティや API エンドポイントの追加
- 02サービスとリポジトリの実装
- 03検証と例外処理の統一
- 04null 安全な Kotlin コードの作成
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ ashchupliak/ dream-team --skill kotlin-patternsインストール後、Claude Code で/kotlin-patternsを入力して読み込まれたか確認します。
詳細
Orca 向け Kotlin パターン
エンティティパターン
data class EntityName(
val id: UUID,
val name: String,
val createdAt: Instant,
val updatedAt: Instant?
)
サービスパターン
@Service
class EntityNameService(
private val repository: EntityNameRepository,
private val relatedService: RelatedService
) {
@Transactional(propagation = Propagation.NEVER)
fun create(request: CreateRequest): Pair<EntityResponse, Boolean> {
// Check exists, validate, create
// Return Pair for idempotent operations
}
fun findById(id: UUID): EntityName? =
repository.findById(id)
fun findAll(): List<EntityName> =
repository.findAll()
}
リポジトリパターン(JOOQ)
@Repository
class EntityNameRepository(
private val dsl: DSLContext
) {
fun findById(id: UUID): EntityName? =
dsl.selectFrom(ENTITY_NAME)
.where(ENTITY_NAME.ID.eq(id))
.fetchOne()
?.toEntity()
fun findAll(): List<EntityName> =
dsl.selectFrom(ENTITY_NAME)
.fetch()
.map { it.toEntity() }
fun save(entity: EntityName): EntityName =
dsl.insertInto(ENTITY_NAME)
.set(ENTITY_NAME.ID, entity.id)
.set(ENTITY_NAME.NAME, entity.name)
.set(ENTITY_NAME.CREATED_AT, entity.createdAt)
.returning()
.fetchOne()!!
.toEntity()
private fun EntityNameRecord.toEntity() = EntityName(
id = id,
name = name,
createdAt = createdAt,
updatedAt = updatedAt
)
}
コントローラーパターン
@RestController
class EntityNameController(
private val service: EntityNameService
) : EntityNameApi {
override fun create(request: CreateRequest): ResponseEntity<EntityResponse> {
val (result, isNew) = service.create(request)
return if (isNew) ResponseEntity.status(201).body(result)
else ResponseEntity.ok(result)
}
override fun getById(id: UUID): ResponseEntity<EntityResponse> {
val entity = service.findById(id)
?: throw ResourceNotFoundRestException("EntityName", id)
return ResponseEntity.ok(entity.toResponse())
}
}
API インターフェースパターン
@Tag(name = "Entity Name")
interface EntityNameApi {
@Operation(summary = "Create entity")
@PostMapping("/api/v1/entities")
fun create(@RequestBody @Valid request: CreateRequest): ResponseEntity<EntityResponse>
@Operation(summary = "Get entity by ID")
@GetMapping("/api/v1/entities/{id}")
fun getById(@PathVariable id: UUID): ResponseEntity<EntityResponse>
}
DTO パターン
data class CreateRequest(
@field:NotBlank
val name: String,
@field:Size(max = 255)
val description: String?
)
data class EntityResponse(
val id: UUID,
val name: String,
val description: String?,
val createdAt: Instant
)
例外パターン
// Use typed exceptions
throw ResourceNotFoundRestException("EntityName", id)
throw ValidationRestException("Name cannot be empty")
throw ConflictRestException("Entity already exists")
Null 安全性のガイドライン
- オプションの操作には
?.let{}を使用します - 網羅的なマッチングには
whenを使用します !!は避け、代わりに.single()や.firstOrNull()を使用します- 冪等な操作では
Pair<Result, Boolean>を返します
コメント
Markdown 対応