Skip to content

Commit a7c76c6

Browse files
committed
Kotlin template simplified and updated
1 parent 3a0921f commit a7c76c6

20 files changed

+117
-228
lines changed

README.md

+19-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
# Шаблон приложения Kora Kotlin CRUD
44

5-
Пример Kotlin сервиса реализованного на Kora с HTTP [CRUD](https://github.com/swagger-api/swagger-petstore) API,
6-
в качестве базы данных выступает Postgres, используется кэш Caffeine, а также другие модули которые использовались бы в реальном приложении в бою.
5+
Шаблон для быстрого старта нового проекта на Kotlin и Kora с базовым настроенным HTTP [CRUD](https://github.com/swagger-api/swagger-petstore) API для одной сущности.
6+
В качестве базы данных выступает Postgres, используется кэш Caffeine,
7+
а также другие модули которые использовались бы в реальном приложении в бою.
78

8-
В примере использовались модули:
9+
В шаблоне используются модули:
910
- [HTTP сервер](https://kora-projects.github.io/kora-docs/ru/documentation/http-server/)
1011
- [OpenAPI HTTP серверная генерация](https://kora-projects.github.io/kora-docs/ru/documentation/openapi-codegen/)
1112
- [Пробы](https://kora-projects.github.io/kora-docs/ru/documentation/probes/)
@@ -18,7 +19,7 @@
1819

1920
## Build
2021

21-
Собрать классы (может потребоваться запустить 2 раза из-за APT):
22+
Собрать классы (может потребоваться запустить 2 раза из-за Kotlin APT & KSP):
2223

2324
```shell
2425
./gradlew classes
@@ -37,13 +38,27 @@
3738
./gradlew openApiGenerateHttpServer
3839
```
3940

41+
### Image
42+
43+
Собрать образ приложения:
44+
```shell
45+
docker build -t kora-kotlin-crud .
46+
```
47+
4048
## Run
4149

4250
Запустить локально:
4351
```shell
4452
./gradlew run
4553
```
4654

55+
## Migration
56+
57+
Миграции вызываются с помощью Flyway Gradle Plugin:
58+
```shell
59+
./gradlew flywayMigrate
60+
```
61+
4762
## Test
4863

4964
Тесты используют [Testcontainers](https://java.testcontainers.org/), требуется [Docker](https://docs.docker.com/engine/install/) окружение для запуска тестов или аналогичные контейнерные окружения ([colima](https://github.com/abiosoft/colima) / итп)

build.gradle.kts

+41-33
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import com.google.devtools.ksp.gradle.KspTask
2+
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
23
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
34
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask
45

@@ -21,11 +22,6 @@ plugins {
2122
group = property("groupId")!!
2223
version = property("koraVersion")!!
2324

24-
application {
25-
applicationName = "application"
26-
mainClass.set("ru.tinkoff.kora.kotlin.crud.ApplicationKt")
27-
}
28-
2925
kotlin {
3026
jvmToolchain { languageVersion.set(JavaLanguageVersion.of(17)) }
3127
sourceSets.main { kotlin.srcDir("build/generated/openapi") }
@@ -37,13 +33,13 @@ kotlin {
3733
val koraBom: Configuration by configurations.creating
3834
configurations {
3935
ksp.get().extendsFrom(koraBom)
36+
compileOnly.get().extendsFrom(koraBom)
4037
api.get().extendsFrom(koraBom)
4138
implementation.get().extendsFrom(koraBom)
4239
}
4340

4441
repositories {
4542
mavenCentral()
46-
maven("https://oss.sonatype.org/content/repositories/snapshots")
4743
}
4844

4945
dependencies {
@@ -73,8 +69,31 @@ dependencies {
7369

7470
testImplementation("io.mockk:mockk:1.13.8")
7571
testImplementation("ru.tinkoff.kora:test-junit5")
76-
testImplementation("io.goodforgod:testcontainers-extensions-postgres:0.11.0")
77-
testImplementation("org.testcontainers:junit-jupiter:1.17.6")
72+
testImplementation("io.goodforgod:testcontainers-extensions-postgres:0.12.0")
73+
testImplementation("org.testcontainers:junit-jupiter:1.19.8")
74+
}
75+
76+
application {
77+
applicationName = "application"
78+
mainClass.set("ru.tinkoff.kora.kotlin.crud.ApplicationKt")
79+
applicationDefaultJvmArgs = listOf("-Dfile.encoding=UTF-8")
80+
}
81+
82+
tasks.distTar {
83+
archiveFileName.set("application.tar")
84+
}
85+
86+
val postgresHost: String by project
87+
val postgresPort: String by project
88+
val postgresDatabase: String by project
89+
val postgresUser: String by project
90+
val postgresPassword: String by project
91+
tasks.withType<JavaExec> {
92+
environment(
93+
"POSTGRES_JDBC_URL" to "jdbc:postgresql://${postgresHost}:${postgresPort}/${postgresDatabase}",
94+
"POSTGRES_USER" to postgresUser,
95+
"POSTGRES_PASS" to postgresPassword,
96+
)
7897
}
7998

8099
tasks.register("openApiGenerateHttpServer", GenerateTask::class) {
@@ -101,38 +120,20 @@ tasks.withType<KspTask> {
101120
tasks.withType<KotlinCompile>().configureEach {
102121
dependsOn(tasks.named("openApiGenerateHttpServer"))
103122
}
104-
tasks.named("test") {
123+
124+
tasks.test {
105125
dependsOn("distTar")
106-
}
107126

108-
val postgresHost: String by project
109-
val postgresPort: String by project
110-
val postgresDatabase: String by project
111-
val postgresUser: String by project
112-
val postgresPassword: String by project
113-
tasks.withType<JavaExec> {
114-
environment(
115-
"POSTGRES_JDBC_URL" to "jdbc:postgresql://${postgresHost}:${postgresPort}/${postgresDatabase}",
116-
"POSTGRES_USER" to postgresUser,
117-
"POSTGRES_PASS" to postgresPassword,
127+
jvmArgs(
128+
"-XX:+TieredCompilation",
129+
"-XX:TieredStopAtLevel=1",
118130
)
119-
}
120-
121-
flyway {
122-
url = "jdbc:postgresql://$postgresHost:$postgresPort/$postgresDatabase"
123-
user = postgresUser
124-
password = postgresPassword
125-
locations = arrayOf("classpath:db/migration")
126-
}
127-
128-
tasks.distTar {
129-
archiveFileName.set("application.tar")
130-
}
131131

132-
tasks.test {
133132
useJUnitPlatform()
134133
testLogging {
134+
showStandardStreams = true
135135
events("passed", "skipped", "failed")
136+
exceptionFormat = TestExceptionFormat.FULL
136137
}
137138

138139
reports {
@@ -147,3 +148,10 @@ tasks.jacocoTestReport {
147148
html.outputLocation = layout.buildDirectory.dir("jacocoHtml")
148149
}
149150
}
151+
152+
flyway {
153+
url = "jdbc:postgresql://$postgresHost:$postgresPort/$postgresDatabase"
154+
user = postgresUser
155+
password = postgresPassword
156+
locations = arrayOf("classpath:db/migration")
157+
}

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
groupId=ru.tinkoff.kora
2-
koraVersion=1.1.9
2+
koraVersion=1.1.11
33

44

55
##### GRADLE #####

settings.gradle.kts

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ pluginManagement {
22
repositories {
33
gradlePluginPortal()
44
mavenCentral()
5-
maven("https://oss.sonatype.org/content/repositories/snapshots")
65
}
76
}
87

src/main/java/ru/tinkoff/kora/kotlin/crud/controller/HttpExceptionHandler.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package ru.tinkoff.kora.kotlin.example.crud.controller
1+
package ru.tinkoff.kora.kotlin.crud.controller
22

33
import io.micrometer.core.instrument.config.validate.ValidationException
44
import ru.tinkoff.kora.common.Component

src/main/java/ru/tinkoff/kora/kotlin/crud/controller/PetDelegate.kt

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
package ru.tinkoff.kora.kotlin.example.crud.controller
1+
package ru.tinkoff.kora.kotlin.crud.controller
22

33
import ru.tinkoff.kora.common.Component
4+
import ru.tinkoff.kora.kotlin.crud.model.PetMapper
45
import ru.tinkoff.kora.kotlin.crud.openapi.http.server.api.PetApiDelegate
56
import ru.tinkoff.kora.kotlin.crud.openapi.http.server.api.PetApiResponses
67
import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.MessageTO
78
import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.PetCreateTO
89
import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.PetUpdateTO
9-
import ru.tinkoff.kora.kotlin.example.crud.model.PetMapper
10-
import ru.tinkoff.kora.kotlin.example.crud.model.PetWithCategory
11-
import ru.tinkoff.kora.kotlin.example.crud.service.PetService
10+
import ru.tinkoff.kora.kotlin.crud.service.PetService
1211

1312
@Component
1413
class PetDelegate(
@@ -23,16 +22,16 @@ class PetDelegate(
2322

2423
val pet = petService.findByID(id)
2524
if (pet != null) {
26-
val body = petMapper.petWithCategoryToPetTO(pet)
25+
val body = petMapper.asDTO(pet)
2726
return PetApiResponses.GetPetByIdApiResponse.GetPetById200ApiResponse(body)
2827
} else {
2928
return PetApiResponses.GetPetByIdApiResponse.GetPetById404ApiResponse(notFound(id))
3029
}
3130
}
3231

3332
override fun addPet(petCreateTO: PetCreateTO): PetApiResponses.AddPetApiResponse {
34-
val pet: PetWithCategory = petService.add(petCreateTO)
35-
val body = petMapper.petWithCategoryToPetTO(pet)
33+
val pet = petService.add(petCreateTO)
34+
val body = petMapper.asDTO(pet)
3635
return PetApiResponses.AddPetApiResponse.AddPet200ApiResponse(body)
3736
}
3837

@@ -43,7 +42,7 @@ class PetDelegate(
4342

4443
val updated = petService.update(id, petUpdateTO)
4544
if (updated != null) {
46-
val body = petMapper.petWithCategoryToPetTO(updated)
45+
val body = petMapper.asDTO(updated)
4746
return PetApiResponses.UpdatePetApiResponse.UpdatePet200ApiResponse(body)
4847
} else {
4948
return PetApiResponses.UpdatePetApiResponse.UpdatePet404ApiResponse(notFound(id))
@@ -57,7 +56,7 @@ class PetDelegate(
5756

5857
return if (petService.delete(id)) {
5958
PetApiResponses.DeletePetApiResponse.DeletePet200ApiResponse(
60-
MessageTO("Successfully deleted pet with ID: $id")
59+
MessageTO("Successfully deleted Pet with ID: $id")
6160
)
6261
} else {
6362
PetApiResponses.DeletePetApiResponse.DeletePet404ApiResponse(notFound(id))
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
package ru.tinkoff.kora.kotlin.example.crud.model
1+
package ru.tinkoff.kora.kotlin.crud.model
22

33
import ru.tinkoff.kora.database.common.annotation.Column
4-
import ru.tinkoff.kora.database.common.annotation.Embedded
54
import ru.tinkoff.kora.database.common.annotation.Id
65
import ru.tinkoff.kora.database.common.annotation.Table
76

@@ -10,27 +9,10 @@ data class Pet(
109
@field:Column("id") @field:Id val id: Long,
1110
@field:Column("name") val name: String,
1211
@field:Column("status") val status: Status,
13-
@field:Column("category_id") val categoryId: Long
1412
) {
1513
enum class Status(val code: Int) {
1614
AVAILABLE(0),
1715
PENDING(10),
1816
SOLD(20)
1917
}
2018
}
21-
22-
@Table("categories")
23-
data class PetCategory(
24-
@field:Id val id: Long,
25-
val name: String
26-
)
27-
28-
data class PetWithCategory(
29-
@field:Column("id") val id: Long,
30-
@field:Column("name") val name: String,
31-
@field:Column("status") val status: Pet.Status,
32-
@field:Embedded("category_") val category: PetCategory
33-
) {
34-
35-
fun getPet(): Pet = Pet(id, name, status, category.id)
36-
}
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
package ru.tinkoff.kora.kotlin.example.crud.model
1+
package ru.tinkoff.kora.kotlin.crud.model
22

33
import org.mapstruct.Mapper
4-
import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.CategoryTO
54
import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.PetTO
65

76
@Mapper
87
interface PetMapper {
98

10-
fun petWithCategoryToPetTO(pet: PetWithCategory): PetTO
11-
12-
fun petCategoryToCategoryTO(category: PetCategory): CategoryTO
9+
fun asDTO(pet: Pet): PetTO
1310
}

src/main/java/ru/tinkoff/kora/kotlin/crud/repository/CategoryRepository.kt

-21
This file was deleted.

src/main/java/ru/tinkoff/kora/kotlin/crud/repository/Mappers.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
package ru.tinkoff.kora.kotlin.example.crud.repository
1+
package ru.tinkoff.kora.kotlin.crud.repository
22

33
import ru.tinkoff.kora.common.Component
44
import ru.tinkoff.kora.database.jdbc.mapper.parameter.JdbcParameterColumnMapper
55
import ru.tinkoff.kora.database.jdbc.mapper.result.JdbcResultColumnMapper
6-
import ru.tinkoff.kora.kotlin.example.crud.model.Pet
6+
import ru.tinkoff.kora.kotlin.crud.model.Pet
77
import java.sql.PreparedStatement
88
import java.sql.ResultSet
99
import java.sql.SQLException

src/main/java/ru/tinkoff/kora/kotlin/crud/repository/PetRepository.kt

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
1-
package ru.tinkoff.kora.kotlin.example.crud.repository
1+
package ru.tinkoff.kora.kotlin.crud.repository
22

33
import ru.tinkoff.kora.database.common.UpdateCount
44
import ru.tinkoff.kora.database.common.annotation.Id
55
import ru.tinkoff.kora.database.common.annotation.Query
66
import ru.tinkoff.kora.database.common.annotation.Repository
77
import ru.tinkoff.kora.database.jdbc.JdbcRepository
8-
import ru.tinkoff.kora.kotlin.example.crud.model.Pet
9-
import ru.tinkoff.kora.kotlin.example.crud.model.PetWithCategory
8+
import ru.tinkoff.kora.kotlin.crud.model.Pet
109

1110
@Repository
1211
interface PetRepository : JdbcRepository {
1312

14-
@Query(
15-
"""
16-
SELECT p.id, p.name, p.status, p.category_id, c.name as category_name
17-
FROM pets p
18-
JOIN categories c on c.id = p.category_id
19-
WHERE p.id = :id
20-
"""
21-
)
22-
fun findById(id: Long): PetWithCategory?
13+
@Query("SELECT %{return#selects} FROM %{return#table} WHERE id = :id")
14+
fun findById(id: Long): Pet?
2315

2416
@Id
2517
@Query("INSERT INTO %{entity#inserts -= id}")
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
package ru.tinkoff.kora.kotlin.example.crud.service
1+
package ru.tinkoff.kora.kotlin.crud.service
22

33
import ru.tinkoff.kora.cache.annotation.Cache
44
import ru.tinkoff.kora.cache.caffeine.CaffeineCache
5-
import ru.tinkoff.kora.kotlin.example.crud.model.PetWithCategory
5+
import ru.tinkoff.kora.kotlin.crud.model.Pet
66

77
@Cache("pet-cache")
8-
interface PetCache : CaffeineCache<Long, PetWithCategory>
8+
interface PetCache : CaffeineCache<Long, Pet>

0 commit comments

Comments
 (0)