Kotlin-Project

Claude: Insurance API - Kotlin Project

I'll create a step-by-step procedure to initialize a Spring Boot project with Gradle and Kotlin on your MacOS M1 machine. Here's a CLI script that you can follow:

1. Prerequisites

First, ensure you have the necessary tools installed:

# Check if Java is installed (you should use JDK 17 or later)
java -version

# Install Java via Homebrew if needed
# brew install openjdk@17

# Check if SDKMAN is installed (recommended for managing JDK versions)
sdk version

# Install SDKMAN if needed
# curl -s "https://get.sdkman.io" | bash
# source "$HOME/.sdkman/bin/sdkman-init.sh"
# sdk install java 17.0.7-tem

2. Install Spring Boot CLI (optional but useful)

# Install Spring Boot CLI via SDKMAN
sdk install springboot

# Or via Homebrew
# brew install spring-boot

3. Initialize the project using Spring Initializr

# Create a directory for your project
mkdir insure-api
cd insurance-api

# Use curl to download a pre-configured project from Spring Initializr
curl https://start.spring.io/starter.zip \
  -d type=gradle-project \
  -d language=kotlin \
  -d bootVersion=3.4.3 \
  -d baseDir=insure-api \
  -d groupId=net.thava.insure \
  -d artifactId=insure-api \
  -d name=insure-api \
  -d description="Insurance Financial Products API" \
  -d packageName=net.thava.insure\
  -d packaging=jar \
  -d javaVersion=17 \
  -d dependencies=web,data-jpa,postgresql,validation,security,actuator,flyway,lombok,devtools \
  -o insure-api.zip

# Unzip the downloaded file
unzip insure-api.zip

# Remove the zip file
rm insure-api.zip

4. Configure the Gradle Kotlin DSL build script

# Open the project directory (if you're not already in it)
cd insurance-finance-api

# Create a settings file for Gradle version catalogs
mkdir -p gradle
cat << 'EOF' > gradle/libs.versions.toml
[versions]
kotlin = "1.9.22"
spring-boot = "3.2.0"
spring-cloud = "2023.0.0"
spring-dependency-management = "1.1.4"
testcontainers = "1.19.3"
mockk = "1.13.8"
kotest = "5.8.0"

[libraries]
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" }
spring-boot-starter-data-jpa = { module = "org.springframework.boot:spring-boot-starter-data-jpa" }
spring-boot-starter-validation = { module = "org.springframework.boot:spring-boot-starter-validation" }
spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security" }
spring-boot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator" }
spring-boot-devtools = { module = "org.springframework.boot:spring-boot-devtools" }
spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test" }

postgresql = { module = "org.postgresql:postgresql" }
flyway-core = { module = "org.flywaydb:flyway-core" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" }

mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
testcontainers = { module = "org.testcontainers:testcontainers", version.ref = "testcontainers" }
testcontainers-postgresql = { module = "org.testcontainers:postgresql", version.ref = "testcontainers" }

[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
spring-dependency-management = { id = "io.spring.dependency-management", version.ref = "spring-dependency-management" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" }
kotlin-jpa = { id = "org.jetbrains.kotlin.plugin.jpa", version.ref = "kotlin" }
EOF

# Update build.gradle.kts to use the version catalog
cat << 'EOF' > build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    alias(libs.plugins.spring.boot)
    alias(libs.plugins.spring.dependency.management)
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.kotlin.spring)
    alias(libs.plugins.kotlin.jpa)
}

group = "com.yourcompany.insurance"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
}

dependencies {
    implementation(libs.spring.boot.starter.web)
    implementation(libs.spring.boot.starter.data.jpa)
    implementation(libs.spring.boot.starter.validation)
    implementation(libs.spring.boot.starter.security)
    implementation(libs.spring.boot.starter.actuator)
    implementation(libs.flyway.core)
    implementation(libs.kotlin.reflect)
    implementation(libs.kotlin.stdlib)
    developmentOnly(libs.spring.boot.devtools)
    runtimeOnly(libs.postgresql)
    
    testImplementation(libs.spring.boot.starter.test)
    testImplementation(libs.mockk)
    testImplementation(libs.kotest.runner.junit5)
    testImplementation(libs.kotest.assertions.core)
    testImplementation(libs.testcontainers)
    testImplementation(libs.testcontainers.postgresql)
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "17"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

tasks.bootJar {
    archiveFileName.set("${archiveBaseName.get()}.${archiveExtension.get()}")
}

// For direct dependency declarations without version catalog
dependencies {
    // Additional test dependencies to ensure they're properly resolved
    testImplementation("io.mockk:mockk:1.13.8")
    testImplementation("io.kotest:kotest-runner-junit5:5.8.0")
    testImplementation("io.kotest:kotest-assertions-core:5.8.0")
}

EOF

### 5. Create an application.yml file
```bash
# Create a proper YAML configuration file instead of properties
mkdir -p src/main/resources
cat << 'EOF' > src/main/resources/application.yml
spring:
  application:
    name: insurance-finance-api
  datasource:
    url: jdbc:postgresql://localhost:5432/insurance_finance
    username: postgres
    password: postgres
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.PostgreSQLDialect
  flyway:
    enabled: true
    baseline-on-migrate: true
    locations: classpath:db/migration
  security:
    user:
      name: admin
      password: admin

server:
  port: 8080
  servlet:
    context-path: /api/v1

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always

logging:
  level:
    com.yourcompany.insurance: DEBUG
    org.springframework: INFO
    org.hibernate: INFO
EOF

6. Create a sample domain structure

# Create package structure
mkdir -p src/main/kotlin/net/thava/insure/{controller,service,repository,model,config,exception}

# Create a basic entity class
cat << 'EOF' > src/main/kotlin/net/thava/insure/model/InsuranceProduct.kt
package net.thava.insure.finance.model

import jakarta.persistence.*
import java.math.BigDecimal
import java.time.LocalDateTime

@Entity
@Table(name = "insurance_products")
data class InsuranceProduct(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
    
    @Column(nullable = false)
    val name: String,
    
    @Column(nullable = false)
    val code: String,
    
    @Column(nullable = false, length = 1000)
    val description: String,
    
    @Column(nullable = false)
    val premiumAmount: BigDecimal,
    
    @Column(nullable = false)
    val coverageAmount: BigDecimal,
    
    @Column(nullable = false)
    val term: Int, // in months
    
    @Column(nullable = false)
    val riskCategory: String,
    
    @Column(nullable = false)
    val isActive: Boolean = true,
    
    @Column(nullable = false)
    val createdAt: LocalDateTime = LocalDateTime.now(),
    
    @Column(nullable = true)
    val updatedAt: LocalDateTime? = null
)
EOF

# Create a repository interface
cat << 'EOF' > src/main/kotlin/com/yourcompany/insurance/finance/repository/InsuranceProductRepository.kt
package com.yourcompany.insurance.finance.repository

import com.yourcompany.insurance.finance.model.InsuranceProduct
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.math.BigDecimal

@Repository
interface InsuranceProductRepository : JpaRepository<InsuranceProduct, Long> {
    fun findByCode(code: String): InsuranceProduct?
    fun findByIsActiveTrue(): List<InsuranceProduct>
    fun findByPremiumAmountLessThan(amount: BigDecimal): List<InsuranceProduct>
}
EOF

# Create a service class
cat << 'EOF' > src/main/kotlin/com/yourcompany/insurance/finance/service/InsuranceProductService.kt
package com.yourcompany.insurance.finance.service

import com.yourcompany.insurance.finance.model.InsuranceProduct
import com.yourcompany.insurance.finance.repository.InsuranceProductRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.math.BigDecimal
import java.time.LocalDateTime

@Service
class InsuranceProductService(private val insuranceProductRepository: InsuranceProductRepository) {

    fun getAllProducts(): List<InsuranceProduct> {
        return insuranceProductRepository.findAll()
    }

    fun getActiveProducts(): List<InsuranceProduct> {
        return insuranceProductRepository.findByIsActiveTrue()
    }

    fun getProductById(id: Long): InsuranceProduct {
        return insuranceProductRepository.findById(id)
            .orElseThrow { RuntimeException("Insurance product not found with id: $id") }
    }

    fun getProductByCode(code: String): InsuranceProduct? {
        return insuranceProductRepository.findByCode(code)
    }

    @Transactional
    fun createProduct(product: InsuranceProduct): InsuranceProduct {
        return insuranceProductRepository.save(product)
    }

    @Transactional
    fun updateProduct(id: Long, updatedProduct: InsuranceProduct): InsuranceProduct {
        val existingProduct = getProductById(id)
        
        val productToUpdate = existingProduct.copy(
            name = updatedProduct.name,
            code = updatedProduct.code,
            description = updatedProduct.description,
            premiumAmount = updatedProduct.premiumAmount,
            coverageAmount = updatedProduct.coverageAmount,
            term = updatedProduct.term,
            riskCategory = updatedProduct.riskCategory,
            isActive = updatedProduct.isActive,
            updatedAt = LocalDateTime.now()
        )
        
        return insuranceProductRepository.save(productToUpdate)
    }

    @Transactional
    fun deleteProduct(id: Long) {
        insuranceProductRepository.deleteById(id)
    }

    fun getProductsUnderPremium(amount: BigDecimal): List<InsuranceProduct> {
        return insuranceProductRepository.findByPremiumAmountLessThan(amount)
    }
}
EOF

# Create a controller
cat << 'EOF' > src/main/kotlin/com/yourcompany/insurance/finance/controller/InsuranceProductController.kt
package com.yourcompany.insurance.finance.controller

import com.yourcompany.insurance.finance.model.InsuranceProduct
import com.yourcompany.insurance.finance.service.InsuranceProductService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import java.math.BigDecimal

@RestController
@RequestMapping("/products")
class InsuranceProductController(private val insuranceProductService: InsuranceProductService) {

    @GetMapping
    fun getAllProducts(): ResponseEntity<List<InsuranceProduct>> {
        return ResponseEntity.ok(insuranceProductService.getAllProducts())
    }

    @GetMapping("/active")
    fun getActiveProducts(): ResponseEntity<List<InsuranceProduct>> {
        return ResponseEntity.ok(insuranceProductService.getActiveProducts())
    }

    @GetMapping("/{id}")
    fun getProductById(@PathVariable id: Long): ResponseEntity<InsuranceProduct> {
        return ResponseEntity.ok(insuranceProductService.getProductById(id))
    }

    @GetMapping("/code/{code}")
    fun getProductByCode(@PathVariable code: String): ResponseEntity<InsuranceProduct> {
        val product = insuranceProductService.getProductByCode(code)
        return if (product != null) {
            ResponseEntity.ok(product)
        } else {
            ResponseEntity.notFound().build()
        }
    }

    @GetMapping("/premium-under/{amount}")
    fun getProductsUnderPremium(@PathVariable amount: BigDecimal): ResponseEntity<List<InsuranceProduct>> {
        return ResponseEntity.ok(insuranceProductService.getProductsUnderPremium(amount))
    }

    @PostMapping
    fun createProduct(@RequestBody product: InsuranceProduct): ResponseEntity<InsuranceProduct> {
        return ResponseEntity.status(HttpStatus.CREATED).body(insuranceProductService.createProduct(product))
    }

    @PutMapping("/{id}")
    fun updateProduct(
        @PathVariable id: Long,
        @RequestBody product: InsuranceProduct
    ): ResponseEntity<InsuranceProduct> {
        return ResponseEntity.ok(insuranceProductService.updateProduct(id, product))
    }

    @DeleteMapping("/{id}")
    fun deleteProduct(@PathVariable id: Long): ResponseEntity<Unit> {
        insuranceProductService.deleteProduct(id)
        return ResponseEntity.noContent().build()
    }
}
EOF

# Create a database migration script
mkdir -p src/main/resources/db/migration
cat << 'EOF' > src/main/resources/db/migration/V1__Create_insurance_products_table.sql
CREATE TABLE insurance_products (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    code VARCHAR(50) NOT NULL UNIQUE,
    description VARCHAR(1000) NOT NULL,
    premium_amount DECIMAL(19, 2) NOT NULL,
    coverage_amount DECIMAL(19, 2) NOT NULL,
    term INT NOT NULL,
    risk_category VARCHAR(50) NOT NULL,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP
);

-- Add index for commonly queried fields
CREATE INDEX idx_insurance_products_code ON insurance_products(code);
CREATE INDEX idx_insurance_products_is_active ON insurance_products(is_active);
EOF

# Create the main application class
cat << 'EOF' > src/main/kotlin/com/yourcompany/insurance/finance/InsuranceFinanceApiApplication.kt
package com.yourcompany.insurance.finance

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class InsuranceFinanceApiApplication

fun main(args: Array<String>) {
    runApplication<InsuranceFinanceApiApplication>(*args)
}
EOF

7. Add basic test classes

# Create test directory structure
mkdir -p src/test/kotlin/com/yourcompany/insurance/finance/{controller,service,repository}

# Create a service test
cat << 'EOF' > src/test/kotlin/com/yourcompany/insurance/finance/service/InsuranceProductServiceTest.kt
package com.yourcompany.insurance.finance.service

import com.yourcompany.insurance.finance.model.InsuranceProduct
import com.yourcompany.insurance.finance.repository.InsuranceProductRepository
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.Test
import java.math.BigDecimal
import java.util.Optional

class InsuranceProductServiceTest {

    private val repository: InsuranceProductRepository = mockk()
    private val service = InsuranceProductService(repository)

    @Test
    fun `should return all products`() {
        // Given
        val products = listOf(
            InsuranceProduct(
                id = 1,
                name = "Term Life Insurance",
                code = "TLI001",
                description = "Basic term life insurance product",
                premiumAmount = BigDecimal("100.00"),
                coverageAmount = BigDecimal("100000.00"),
                term = 12,
                riskCategory = "LOW"
            )
        )
        every { repository.findAll() } returns products

        // When
        val result = service.getAllProducts()

        // Then
        result shouldBe products
        verify(exactly = 1) { repository.findAll() }
    }

    @Test
    fun `should return product by id`() {
        // Given
        val product = InsuranceProduct(
            id = 1,
            name = "Term Life Insurance",
            code = "TLI001",
            description = "Basic term life insurance product",
            premiumAmount = BigDecimal("100.00"),
            coverageAmount = BigDecimal("100000.00"),
            term = 12,
            riskCategory = "LOW"
        )
        every { repository.findById(1) } returns Optional.of(product)

        // When
        val result = service.getProductById(1)

        // Then
        result shouldBe product
        verify(exactly = 1) { repository.findById(1) }
    }

    @Test
    fun `should throw exception when product not found`() {
        // Given
        every { repository.findById(any()) } returns Optional.empty()

        // When/Then
        shouldThrow<RuntimeException> {
            service.getProductById(1)
        }
        verify(exactly = 1) { repository.findById(1) }
    }
}
EOF

8. Build and test the application

# Test the application
./gradlew test

# Build the application
./gradlew build

# Run the application (you'll need PostgreSQL running on your machine)
# ./gradlew bootRun

9. Set up Docker for development environment

# Create a docker-compose file for local development
cat << 'EOF' > docker-compose.yml
version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    container_name: insurance-finance-postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: insurance_finance
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  postgres-data:
EOF

# Start the PostgreSQL container
docker-compose up -d

10. Start the application

# Run the Spring Boot application
./gradlew bootRun

That's it! You've initialized a modern Spring Boot project with:

  • Kotlin as the programming language
  • Gradle (with Kotlin DSL) as the build tool
  • PostgreSQL for the database
  • Flyway for database migrations
  • Spring Data JPA for data access
  • Spring Security for authentication and authorization
  • Spring Actuator for monitoring and metrics
  • A basic domain model with controller, service, and repository layers
  • Testing with JUnit 5, Mockk, and Kotest

The project follows modern practices including:

  • YAML configuration instead of properties
  • Gradle version catalogs for dependency management
  • Containerized development environment
  • Proper package structure
  • RESTful API design

You can access your API at http://localhost:8080/api/v1/products once it's running.