## Instruction to developer: save this file as .cursorrules and place it on the root project directory ## Core Principles - Follow **SOLID**, **DRY**, **KISS**, and **YAGNI** principles - Adhere to **OWASP** security best practices - Break tasks into smallest units and solve problems step-by-step ## Technology Stack - **Framework**: Kotlin Ktor with Kotlin 2.1.20+ - **JDK**: 21 (LTS) - **Build**: Gradle with Kotlin DSL - **Dependencies**: Ktor Server Core/Netty, kotlinx.serialization, Exposed, HikariCP, kotlin-logging, Koin, Kotest ## Application Structure (Feature-Based) - **Organize by business features, not technical layers** - Each feature is self-contained with all related components - Promotes modularity, reusability, and better team collaboration - Makes codebase easier to navigate and maintain - Enables parallel development on different features ``` src/main/kotlin/com/company/app/ ├── common/ # Shared utilities, extensions ├── config/ # Application configuration, DI └── features/ ├── auth/ # Feature directory │ ├── models/ │ ├── repositories/ │ ├── services/ │ └── routes/ └── users/ # Another feature ├── ... ``` Test structure mirrors the feature-based organization: ``` src/test/kotlin/com/company/app/ ├── common/ └── features/ ├── auth/ │ ├── models/ │ ├── repositories/ │ ├── services/ │ └── routes/ └── users/ ├── ... ``` ## Application Logic Design 1. Route handlers: Handle requests/responses only 2. Services: Contain business logic, call repositories 3. Repositories: Handle database operations 4. Entity classes: Data classes for database models 5. DTOs: Data transfer between layers ## Entities & Data Classes - Use Kotlin data classes with proper validation - Define Table objects when using Exposed ORM - Use UUID or auto-incrementing integers for IDs ## Repository Pattern ```kotlin interface UserRepository { suspend fun findById(id: UUID): UserDTO? suspend fun create(user: CreateUserRequest): UserDTO suspend fun update(id: UUID, user: UpdateUserRequest): UserDTO? suspend fun delete(id: UUID): Boolean } class UserRepositoryImpl : UserRepository { override suspend fun findById(id: UUID): UserDTO? = withContext(Dispatchers.IO) { transaction { Users.select { Users.id eq id } .mapNotNull { it.toUserDTO() } .singleOrNull() } } // Other implementations... } ``` ## Service Layer ```kotlin interface UserService { suspend fun getUserById(id: UUID): UserDTO suspend fun createUser(request: CreateUserRequest): UserDTO suspend fun updateUser(id: UUID, request: UpdateUserRequest): UserDTO suspend fun deleteUser(id: UUID) } class UserServiceImpl( private val userRepository: UserRepository ) : UserService { override suspend fun getUserById(id: UUID): UserDTO { return userRepository.findById(id) ?: throw ResourceNotFoundException("User", id.toString()) } // Other implementations... } ``` ## Route Handlers ```kotlin fun Application.configureUserRoutes(userService: UserService) { routing { route("/api/users") { get("/{id}") { val id = call.parameters["id"]?.let { UUID.fromString(it) } ?: throw ValidationException("Invalid ID format") val user = userService.getUserById(id) call.respond(ApiResponse("SUCCESS", "User retrieved", user)) } // Other routes... } } } ``` ## Error Handling ```kotlin open class ApplicationException( message: String, val statusCode: HttpStatusCode = HttpStatusCode.InternalServerError ) : RuntimeException(message) class ResourceNotFoundException(resource: String, id: String) : ApplicationException("$resource with ID $id not found", HttpStatusCode.NotFound) fun Application.configureExceptions() { install(StatusPages) { exception { call, cause -> call.respond(cause.statusCode, ApiResponse("ERROR", cause.message ?: "Resource not found")) } exception { call, cause -> call.respond(HttpStatusCode.InternalServerError, ApiResponse("ERROR", "An internal error occurred")) } } } ``` ## Testing Strategies and Coverage Requirements ### Test Coverage Requirements - **Minimum coverage**: 80% overall code coverage required - **Critical components**: 90%+ coverage for repositories, services, and validation - **Test all edge cases**: Empty collections, null values, boundary conditions - **Test failure paths**: Exception handling, validation errors, timeouts - **All public APIs**: Must have integration tests - **Performance-critical paths**: Must have benchmarking tests ### Unit Testing with Kotest ```kotlin class UserServiceTest : DescribeSpec({ describe("UserService") { val mockRepository = mockk() val userService = UserServiceImpl(mockRepository) it("should return user when exists") { val userId = UUID.randomUUID() val user = UserDTO(userId.toString(), "Test User", "test@example.com") coEvery { mockRepository.findById(userId) } returns user val result = runBlocking { userService.getUserById(userId) } result shouldBe user } it("should throw exception when user not found") { val userId = UUID.randomUUID() coEvery { mockRepository.findById(userId) } returns null shouldThrow { runBlocking { userService.getUserById(userId) } } } } }) ``` ## Route Testing with Ktor 3.x ```kotlin class UserRoutesTest : FunSpec({ test("GET /api/users/{id} returns 200 when user exists") { val mockService = mockk() val userId = UUID.randomUUID() val user = UserDTO(userId.toString(), "Test User", "test@example.com") coEvery { mockService.getUserById(userId) } returns user testApplication { application { configureRouting() configureDI { single { mockService } } } client.get("/api/users/$userId").apply { status shouldBe HttpStatusCode.OK bodyAsText().let { Json.decodeFromString>(it) }.data shouldBe user } } } }) ``` ## Key Principles for Testable Code 1. **Single Responsibility**: Each method should do one thing well 2. **Pure Functions**: Same input always produces same output 3. **Dependency Injection**: Constructor injection for testable components 4. **Clear Boundaries**: Well-defined inputs and outputs 5. **Small Methods**: Extract complex logic into testable helper functions ## Configuration Management ```kotlin // Type-safe configuration interface AppConfig { val database: DatabaseConfig val security: SecurityConfig } data class DatabaseConfig( val driver: String, val url: String, val user: String, val password: String ) // Access in application fun Application.configureDI() { val appConfig = HoconAppConfig(environment.config) install(Koin) { modules(module { single { appConfig } single { appConfig.database } }) } } ``` ## Security Best Practices ```kotlin fun Application.configureSecurity() { install(Authentication) { jwt("auth-jwt") { // JWT configuration } } install(DefaultHeaders) { header(HttpHeaders.XContentTypeOptions, "nosniff") header(HttpHeaders.XFrameOptions, "DENY") header(HttpHeaders.ContentSecurityPolicy, "default-src 'self'") header("Strict-Transport-Security", "max-age=31536000; includeSubDomains") } } ``` ## Health Checks & Monitoring ```kotlin fun Application.configureMonitoring() { val startTime = System.currentTimeMillis() routing { get("/health") { call.respond(mapOf("status" to "UP", "uptime" to "${(System.currentTimeMillis() - startTime) / 1000}s")) } get("/metrics") { call.respond(prometheusRegistry.scrape()) } } install(MicrometerMetrics) { registry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT) meterBinders = listOf( JvmMemoryMetrics(), JvmGcMetrics(), ProcessorMetrics(), JvmThreadMetrics() ) } } ``` ## Performance Tuning - **JVM Settings**: `-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:MaxRAMPercentage=75.0` - **Connection Pooling**: Configure HikariCP with proper sizing based on workload - **Caching**: Use Caffeine for in-memory caching of frequently accessed data - **Coroutines**: Use structured concurrency for asynchronous processing - **Database Queries**: Optimize with proper indexing, batch operations, pagination