diff --git a/.github/workflows/test-master.yml b/.github/workflows/test-master.yml index 06dcbc3..2a76840 100644 --- a/.github/workflows/test-master.yml +++ b/.github/workflows/test-master.yml @@ -23,4 +23,4 @@ jobs: run: "./gradlew testClasses" - name: Test - run: "./gradlew test" + run: "./gradlew test jacocoTestReport --no-parallel" diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index 717277f..1bf0de3 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -34,7 +34,7 @@ jobs: run: "./gradlew testClasses" - name: Test - run: "./gradlew test jacocoTestReport" + run: "./gradlew test jacocoTestReport --no-parallel" - name: Test Report if: matrix.java == '17' diff --git a/build.gradle b/build.gradle index 0726a35..d6ad895 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id "com.diffplug.spotless" version "6.19.0" + id "org.graalvm.buildtools.native" version "0.10.1" // or dependsOn fails in graalvm examples id "com.asarkar.gradle.build-time-tracker" version "4.3.0" } @@ -9,6 +10,7 @@ version = koraVersion repositories { mavenLocal() mavenCentral() + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } } subprojects { @@ -22,6 +24,7 @@ subprojects { repositories { mavenLocal() mavenCentral() + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } } spotless { @@ -35,7 +38,7 @@ subprojects { } configurations { - all { + configureEach { resolutionStrategy { cacheChangingModulesFor 0, "seconds" // check for updates every build } diff --git a/gradle.properties b/gradle.properties index fdec7f2..2eecb93 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ groupId=ru.tinkoff.kora -koraVersion=1.0.9 +koraVersion=1.1.1 ##### GRADLE ##### diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cb..d64cd49 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 17655d0..a80b22c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..25da30d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/kora-java-cache-caffeine/build.gradle b/kora-java-cache-caffeine/build.gradle index d23a0e8..c455d8f 100644 --- a/kora-java-cache-caffeine/build.gradle +++ b/kora-java-cache-caffeine/build.gradle @@ -68,13 +68,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-cache-caffeine/src/main/resources/application.conf b/kora-java-cache-caffeine/src/main/resources/application.conf index 1cb02f8..81540ee 100644 --- a/kora-java-cache-caffeine/src/main/resources/application.conf +++ b/kora-java-cache-caffeine/src/main/resources/application.conf @@ -5,6 +5,6 @@ my-cache { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-cache-caffeine/src/main/resources/logback.xml b/kora-java-cache-caffeine/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-cache-caffeine/src/main/resources/logback.xml +++ b/kora-java-cache-caffeine/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-cache-caffeine/src/test/resources/logback-test.xml b/kora-java-cache-caffeine/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-cache-caffeine/src/test/resources/logback-test.xml +++ b/kora-java-cache-caffeine/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-cache-redis/build.gradle b/kora-java-cache-redis/build.gradle index 108fa48..0690ced 100644 --- a/kora-java-cache-redis/build.gradle +++ b/kora-java-cache-redis/build.gradle @@ -32,7 +32,7 @@ dependencies { implementation "ru.tinkoff.kora:config-hocon" testImplementation "ru.tinkoff.kora:test-junit5" - testImplementation "io.goodforgod:testcontainers-extensions-redis:0.9.6" + testImplementation "io.goodforgod:testcontainers-extensions-redis:0.11.0" testImplementation "redis.clients:jedis:4.4.3" } @@ -70,13 +70,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-cache-redis/src/main/resources/application.conf b/kora-java-cache-redis/src/main/resources/application.conf index 757489c..8b29348 100644 --- a/kora-java-cache-redis/src/main/resources/application.conf +++ b/kora-java-cache-redis/src/main/resources/application.conf @@ -16,6 +16,6 @@ lettuce { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-cache-redis/src/main/resources/logback.xml b/kora-java-cache-redis/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-cache-redis/src/main/resources/logback.xml +++ b/kora-java-cache-redis/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-cache-redis/src/test/java/ru/tinkoff/kora/example/cache/caffeine/RedisCachedServiceTests.java b/kora-java-cache-redis/src/test/java/ru/tinkoff/kora/example/cache/caffeine/RedisCachedServiceTests.java index 5769b5d..91e6a8b 100644 --- a/kora-java-cache-redis/src/test/java/ru/tinkoff/kora/example/cache/caffeine/RedisCachedServiceTests.java +++ b/kora-java-cache-redis/src/test/java/ru/tinkoff/kora/example/cache/caffeine/RedisCachedServiceTests.java @@ -4,13 +4,13 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.redis.*; +import io.goodforgod.testcontainers.extensions.redis.ConnectionRedis; +import io.goodforgod.testcontainers.extensions.redis.RedisConnection; +import io.goodforgod.testcontainers.extensions.redis.TestcontainersRedis; import java.math.BigDecimal; -import java.time.Duration; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.testcontainers.utility.DockerImageName; import ru.tinkoff.kora.test.extension.junit5.KoraAppTest; import ru.tinkoff.kora.test.extension.junit5.KoraAppTestConfigModifier; import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; @@ -20,11 +20,7 @@ @KoraAppTest(Application.class) class RedisCachedServiceTests implements KoraAppTestConfigModifier { - @ContainerRedis - private static final RedisContainer CONTAINER = new RedisContainer<>(DockerImageName.parse("redis:7.2-alpine")) - .waitAfterStart(Duration.ofSeconds(1)); - - @ContainerRedisConnection + @ConnectionRedis private RedisConnection connection; @TestComponent @@ -42,7 +38,8 @@ public KoraConfigModification config() { } @BeforeEach - void cleanup() { + void cleanup() throws Exception { + Thread.sleep(150); cache.invalidateAll(); } diff --git a/kora-java-cache-redis/src/test/resources/logback-test.xml b/kora-java-cache-redis/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-cache-redis/src/test/resources/logback-test.xml +++ b/kora-java-cache-redis/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-config-hocon/build.gradle b/kora-java-config-hocon/build.gradle index d0ca9be..0eb3772 100644 --- a/kora-java-config-hocon/build.gradle +++ b/kora-java-config-hocon/build.gradle @@ -68,13 +68,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-config-hocon/src/main/java/ru/tinkoff/kora/example/config/hocon/FooConfig.java b/kora-java-config-hocon/src/main/java/ru/tinkoff/kora/example/config/hocon/FooConfig.java index bed9a6d..9489f54 100644 --- a/kora-java-config-hocon/src/main/java/ru/tinkoff/kora/example/config/hocon/FooConfig.java +++ b/kora-java-config-hocon/src/main/java/ru/tinkoff/kora/example/config/hocon/FooConfig.java @@ -1,6 +1,8 @@ package ru.tinkoff.kora.example.config.hocon; import jakarta.annotation.Nullable; +import java.math.BigDecimal; +import java.math.BigInteger; import java.time.*; import java.util.*; import java.util.regex.Pattern; @@ -46,13 +48,11 @@ public interface FooConfig { long valueLong(); - // TODO 1.0.5 - // BigInteger valueBigInt(); + BigInteger valueBigInt(); double valueDouble(); - // TODO 1.0.5 - // BigDecimal valueBigDecimal(); + BigDecimal valueBigDecimal(); boolean valueBoolean(); diff --git a/kora-java-config-hocon/src/main/resources/application.conf b/kora-java-config-hocon/src/main/resources/application.conf index 40b842b..e60e9ee 100644 --- a/kora-java-config-hocon/src/main/resources/application.conf +++ b/kora-java-config-hocon/src/main/resources/application.conf @@ -18,11 +18,9 @@ foo { valueDuration = "250s" valueInt = 1 valueLong = 2 - //TODO 1.0.5 -// valueBigInt = 3 + valueBigInt = 3 valueDouble = 4.1 - //TODO 1.0.5 -// valueBigDecimal = 5.1 + valueBigDecimal = 5.1 valueBoolean = true valueListAsString = "v1,v2" valueListAsArray = ["v1", "v2"] diff --git a/kora-java-config-hocon/src/main/resources/logback.xml b/kora-java-config-hocon/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-config-hocon/src/main/resources/logback.xml +++ b/kora-java-config-hocon/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-config-hocon/src/test/resources/logback-test.xml b/kora-java-config-hocon/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-config-hocon/src/test/resources/logback-test.xml +++ b/kora-java-config-hocon/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-config-yaml/build.gradle b/kora-java-config-yaml/build.gradle index 7526cc5..61cb4de 100644 --- a/kora-java-config-yaml/build.gradle +++ b/kora-java-config-yaml/build.gradle @@ -68,13 +68,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-config-yaml/src/main/java/ru/tinkoff/kora/example/config/hocon/FooConfig.java b/kora-java-config-yaml/src/main/java/ru/tinkoff/kora/example/config/hocon/FooConfig.java index bed9a6d..9489f54 100644 --- a/kora-java-config-yaml/src/main/java/ru/tinkoff/kora/example/config/hocon/FooConfig.java +++ b/kora-java-config-yaml/src/main/java/ru/tinkoff/kora/example/config/hocon/FooConfig.java @@ -1,6 +1,8 @@ package ru.tinkoff.kora.example.config.hocon; import jakarta.annotation.Nullable; +import java.math.BigDecimal; +import java.math.BigInteger; import java.time.*; import java.util.*; import java.util.regex.Pattern; @@ -46,13 +48,11 @@ public interface FooConfig { long valueLong(); - // TODO 1.0.5 - // BigInteger valueBigInt(); + BigInteger valueBigInt(); double valueDouble(); - // TODO 1.0.5 - // BigDecimal valueBigDecimal(); + BigDecimal valueBigDecimal(); boolean valueBoolean(); diff --git a/kora-java-config-yaml/src/main/resources/application.yaml b/kora-java-config-yaml/src/main/resources/application.yaml index 84a11b3..e276c32 100644 --- a/kora-java-config-yaml/src/main/resources/application.yaml +++ b/kora-java-config-yaml/src/main/resources/application.yaml @@ -18,11 +18,9 @@ foo: valueDuration: "250s" valueInt: 1 valueLong: 2 - #TODO 1.0.5 -# valueBigInt: 3 + valueBigInt: 3 valueDouble: 4.1 - #TODO 1.0.5 -# valueBigDecimal: 5.1 + valueBigDecimal: 5.1 valueBoolean: true valueListAsString: "v1,v2" valueListAsArray: ["v1", "v2"] diff --git a/kora-java-config-yaml/src/main/resources/logback.xml b/kora-java-config-yaml/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-config-yaml/src/main/resources/logback.xml +++ b/kora-java-config-yaml/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-config-yaml/src/test/resources/logback-test.xml b/kora-java-config-yaml/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-config-yaml/src/test/resources/logback-test.xml +++ b/kora-java-config-yaml/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-crud/README.md b/kora-java-crud/README.md index a833051..d986333 100644 --- a/kora-java-crud/README.md +++ b/kora-java-crud/README.md @@ -1,6 +1,6 @@ # Kora Java CRUD Service -Пример сервиса реализованного на Kora с HTTP [CRUD](https://appmaster.io/ru/blog/grubye-operatsii-chto-takoe-grubye-operatsii) API, +Пример сервиса реализованного на Kora с HTTP [CRUD](https://github.com/swagger-api/swagger-petstore) API, в качестве базы данных выступает Postgres, используется кэш Caffeine, а также другие модули которые использовались бы в реальном приложении в бою. В примере использовались модули: diff --git a/kora-java-crud/build.gradle b/kora-java-crud/build.gradle index 5fa2863..a24676d 100644 --- a/kora-java-crud/build.gradle +++ b/kora-java-crud/build.gradle @@ -42,7 +42,6 @@ dependencies { implementation "ru.tinkoff.kora:micrometer-module" implementation "ru.tinkoff.kora:json-module" implementation "ru.tinkoff.kora:validation-module" - implementation "ru.tinkoff.kora:validation-common" implementation "ru.tinkoff.kora:cache-caffeine" implementation "ru.tinkoff.kora:resilient-kora" implementation "ru.tinkoff.kora:config-hocon" @@ -50,19 +49,18 @@ dependencies { implementation "ru.tinkoff.kora:logging-logback" implementation "org.mapstruct:mapstruct:1.5.5.Final" - implementation "org.postgresql:postgresql:42.6.0" + implementation "org.postgresql:postgresql:42.7.2" testImplementation "org.json:json:20231013" testImplementation "org.skyscreamer:jsonassert:1.5.1" testImplementation "org.mockito:mockito-core:5.6.0" testImplementation "ru.tinkoff.kora:test-junit5" - testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.9.6" + testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.11.0" testImplementation "org.testcontainers:junit-jupiter:1.17.6" } -// OpenAPI Generator for HTTP Server -tasks.register("openApiGenerateHttpServer", GenerateTask) { +openApiGenerate { generatorName = "kora" group = "openapi tools" inputSpec = "$projectDir/src/main/resources/openapi/http-server.yaml" @@ -71,12 +69,12 @@ tasks.register("openApiGenerateHttpServer", GenerateTask) { modelPackage = "ru.tinkoff.kora.example.crud.openapi.http.server.model" invokerPackage = "ru.tinkoff.kora.example.crud.openapi.http.server.invoker" configOptions = [ - mode : "java-server", // так же есть java_server вариация HTTP Server"а + mode : "java-server", // так же есть java-server вариация HTTP Server"а enableServerValidation: "true" ] } -compileJava.dependsOn tasks.openApiGenerateHttpServer +compileJava.dependsOn tasks.openApiGenerate test.dependsOn tasks.shadowJar //noinspection GroovyAssignabilityCheck @@ -128,13 +126,12 @@ flyway { locations = ["classpath:db/migration"] } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-crud/src/main/resources/application.conf b/kora-java-crud/src/main/resources/application.conf index 6047c88..08f5f45 100644 --- a/kora-java-crud/src/main/resources/application.conf +++ b/kora-java-crud/src/main/resources/application.conf @@ -9,7 +9,7 @@ db { username = ${POSTGRES_USER} password = ${POSTGRES_PASS} maxPoolSize = 10 - poolName = "example" + poolName = "kora" initializationFailTimeout = "10s" } @@ -55,6 +55,6 @@ resilient { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-crud/src/main/resources/logback.xml b/kora-java-crud/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-crud/src/main/resources/logback.xml +++ b/kora-java-crud/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-crud/src/main/resources/openapi/http-server.yaml b/kora-java-crud/src/main/resources/openapi/http-server.yaml index 8851744..8d99f49 100644 --- a/kora-java-crud/src/main/resources/openapi/http-server.yaml +++ b/kora-java-crud/src/main/resources/openapi/http-server.yaml @@ -79,6 +79,7 @@ paths: type: integer format: int64 nullable: false + minimum: 1 responses: '200': description: successful operation diff --git a/kora-java-crud/src/test/java/ru/tinkoff/kora/example/crud/AppContainer.java b/kora-java-crud/src/test/java/ru/tinkoff/kora/example/crud/AppContainer.java index a222a98..6d1e115 100644 --- a/kora-java-crud/src/test/java/ru/tinkoff/kora/example/crud/AppContainer.java +++ b/kora-java-crud/src/test/java/ru/tinkoff/kora/example/crud/AppContainer.java @@ -2,6 +2,7 @@ import java.net.URI; import java.nio.file.Paths; +import java.time.Duration; import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; @@ -31,6 +32,7 @@ public static AppContainer build() { protected void configure() { super.configure(); withExposedPorts(8080, 8085); + withStartupTimeout(Duration.ofSeconds(120)); withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(AppContainer.class))); waitingFor(Wait.forHttp("/system/readiness").forPort(8085).forStatusCode(200)); } diff --git a/kora-java-crud/src/test/java/ru/tinkoff/kora/example/crud/PetControllerTests.java b/kora-java-crud/src/test/java/ru/tinkoff/kora/example/crud/PetControllerTests.java index 4bd0700..fa979dc 100644 --- a/kora-java-crud/src/test/java/ru/tinkoff/kora/example/crud/PetControllerTests.java +++ b/kora-java-crud/src/test/java/ru/tinkoff/kora/example/crud/PetControllerTests.java @@ -4,10 +4,10 @@ import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.Network; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -20,7 +20,7 @@ import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( network = @Network(shared = true), mode = ContainerMode.PER_RUN, migration = @Migration( @@ -32,11 +32,11 @@ class PetControllerTests { private static final AppContainer container = AppContainer.build() .withNetwork(org.testcontainers.containers.Network.SHARED); - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @BeforeAll - public static void setup(@ContainerPostgresConnection JdbcConnection connection) { + public static void setup(@ConnectionPostgreSQL JdbcConnection connection) { var params = connection.paramsInNetwork().orElseThrow(); container.withEnv(Map.of( "POSTGRES_JDBC_URL", params.jdbcUrl(), diff --git a/kora-java-crud/src/test/resources/logback-test.xml b/kora-java-crud/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-crud/src/test/resources/logback-test.xml +++ b/kora-java-crud/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-database-cassandra/build.gradle b/kora-java-database-cassandra/build.gradle index 6fb03fb..410e88c 100644 --- a/kora-java-database-cassandra/build.gradle +++ b/kora-java-database-cassandra/build.gradle @@ -27,13 +27,13 @@ dependencies { annotationProcessor "ru.tinkoff.kora:annotation-processors" implementation "ru.tinkoff.kora:database-cassandra" - implementation "io.projectreactor:reactor-core:3.5.10" // For reactive examples (optional) + implementation "io.projectreactor:reactor-core:3.6.3" // For reactive examples (optional) implementation "ru.tinkoff.kora:logging-logback" implementation "ru.tinkoff.kora:config-hocon" testImplementation "ru.tinkoff.kora:test-junit5" - testImplementation "io.goodforgod:testcontainers-extensions-cassandra:0.9.6" + testImplementation "io.goodforgod:testcontainers-extensions-cassandra:0.11.0" } //noinspection GroovyAssignabilityCheck @@ -74,13 +74,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-database-cassandra/src/main/resources/application.conf b/kora-java-database-cassandra/src/main/resources/application.conf index 4e30017..1438826 100644 --- a/kora-java-database-cassandra/src/main/resources/application.conf +++ b/kora-java-database-cassandra/src/main/resources/application.conf @@ -1,12 +1,12 @@ cassandra { auth { - login = ${?CASSANDRA_USER} - password = ${?CASSANDRA_PASS} + login = ${CASSANDRA_USER} + password = ${CASSANDRA_PASS} } basic { - contactPoints = ${?CASSANDRA_CONTACT_POINTS} - dc = ${?CASSANDRA_DC} - sessionKeyspace = ${?CASSANDRA_KEYSPACE} + contactPoints = ${CASSANDRA_CONTACT_POINTS} + dc = ${CASSANDRA_DC} + sessionKeyspace = ${CASSANDRA_KEYSPACE} request { timeout = 5s } @@ -16,6 +16,6 @@ cassandra { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-database-cassandra/src/main/resources/logback.xml b/kora-java-database-cassandra/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-database-cassandra/src/main/resources/logback.xml +++ b/kora-java-database-cassandra/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-database-cassandra/src/main/resources/migrations/setup.cql b/kora-java-database-cassandra/src/main/resources/migrations/setup.cql index 87fe9d3..68acc94 100644 --- a/kora-java-database-cassandra/src/main/resources/migrations/setup.cql +++ b/kora-java-database-cassandra/src/main/resources/migrations/setup.cql @@ -1,6 +1,4 @@ -CREATE KEYSPACE IF NOT EXISTS cassandra WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}; - -CREATE TABLE IF NOT EXISTS cassandra.entities +CREATE TABLE IF NOT EXISTS entities ( id VARCHAR, value1 INT, @@ -10,10 +8,10 @@ CREATE TABLE IF NOT EXISTS cassandra.entities ); -CREATE TYPE IF NOT EXISTS cassandra.username(first text, last text); +CREATE TYPE IF NOT EXISTS username(first text, last text); -CREATE TABLE IF NOT EXISTS cassandra.entities_udt +CREATE TABLE IF NOT EXISTS entities_udt ( id VARCHAR, name FROZEN, diff --git a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraCrudReactorTests.java b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraCrudReactorTests.java index 8c8ea1e..90ac21d 100644 --- a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraCrudReactorTests.java +++ b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraCrudReactorTests.java @@ -4,7 +4,7 @@ import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.cassandra.CassandraConnection; -import io.goodforgod.testcontainers.extensions.cassandra.ContainerCassandraConnection; +import io.goodforgod.testcontainers.extensions.cassandra.ConnectionCassandra; import io.goodforgod.testcontainers.extensions.cassandra.Migration; import io.goodforgod.testcontainers.extensions.cassandra.TestcontainersCassandra; import java.time.Duration; @@ -20,13 +20,13 @@ mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.SCRIPTS, - migrations = { "migrations" }, + locations = { "migrations" }, apply = Migration.Mode.PER_METHOD, drop = Migration.Mode.PER_METHOD)) @KoraAppTest(Application.class) class CassandraCrudReactorTests implements KoraAppTestConfigModifier { - @ContainerCassandraConnection + @ConnectionCassandra private CassandraConnection connection; @TestComponent @@ -36,11 +36,11 @@ class CassandraCrudReactorTests implements KoraAppTestConfigModifier { @Override public KoraConfigModification config() { return KoraConfigModification - .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().host() + ":" + connection.params().port()) + .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().contactPoint()) .withSystemProperty("CASSANDRA_USER", connection.params().username()) .withSystemProperty("CASSANDRA_PASS", connection.params().password()) .withSystemProperty("CASSANDRA_DC", connection.params().datacenter()) - .withSystemProperty("CASSANDRA_KEYSPACE", "cassandra"); + .withSystemProperty("CASSANDRA_KEYSPACE", connection.params().keyspace()); } @Test diff --git a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraCrudSyncTests.java b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraCrudSyncTests.java index baf3ac9..31b4488 100644 --- a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraCrudSyncTests.java +++ b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraCrudSyncTests.java @@ -4,7 +4,7 @@ import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.cassandra.CassandraConnection; -import io.goodforgod.testcontainers.extensions.cassandra.ContainerCassandraConnection; +import io.goodforgod.testcontainers.extensions.cassandra.ConnectionCassandra; import io.goodforgod.testcontainers.extensions.cassandra.Migration; import io.goodforgod.testcontainers.extensions.cassandra.TestcontainersCassandra; import java.util.List; @@ -19,13 +19,13 @@ mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.SCRIPTS, - migrations = { "migrations" }, + locations = { "migrations" }, apply = Migration.Mode.PER_METHOD, drop = Migration.Mode.PER_METHOD)) @KoraAppTest(Application.class) class CassandraCrudSyncTests implements KoraAppTestConfigModifier { - @ContainerCassandraConnection + @ConnectionCassandra private CassandraConnection connection; @TestComponent @@ -35,11 +35,11 @@ class CassandraCrudSyncTests implements KoraAppTestConfigModifier { @Override public KoraConfigModification config() { return KoraConfigModification - .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().host() + ":" + connection.params().port()) + .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().contactPoint()) .withSystemProperty("CASSANDRA_USER", connection.params().username()) .withSystemProperty("CASSANDRA_PASS", connection.params().password()) .withSystemProperty("CASSANDRA_DC", connection.params().datacenter()) - .withSystemProperty("CASSANDRA_KEYSPACE", "cassandra"); + .withSystemProperty("CASSANDRA_KEYSPACE", connection.params().keyspace()); } @Test diff --git a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperParameterTests.java b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperParameterTests.java index d4a30bc..8dd0fcd 100644 --- a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperParameterTests.java +++ b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperParameterTests.java @@ -5,7 +5,7 @@ import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.cassandra.CassandraConnection; -import io.goodforgod.testcontainers.extensions.cassandra.ContainerCassandraConnection; +import io.goodforgod.testcontainers.extensions.cassandra.ConnectionCassandra; import io.goodforgod.testcontainers.extensions.cassandra.Migration; import io.goodforgod.testcontainers.extensions.cassandra.TestcontainersCassandra; import java.util.List; @@ -20,13 +20,13 @@ mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.SCRIPTS, - migrations = { "migrations" }, + locations = { "migrations" }, apply = Migration.Mode.PER_METHOD, drop = Migration.Mode.PER_METHOD)) @KoraAppTest(Application.class) class CassandraMapperParameterTests implements KoraAppTestConfigModifier { - @ContainerCassandraConnection + @ConnectionCassandra private CassandraConnection connection; @TestComponent @@ -38,11 +38,11 @@ class CassandraMapperParameterTests implements KoraAppTestConfigModifier { @Override public KoraConfigModification config() { return KoraConfigModification - .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().host() + ":" + connection.params().port()) + .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().contactPoint()) .withSystemProperty("CASSANDRA_USER", connection.params().username()) .withSystemProperty("CASSANDRA_PASS", connection.params().password()) .withSystemProperty("CASSANDRA_DC", connection.params().datacenter()) - .withSystemProperty("CASSANDRA_KEYSPACE", "cassandra"); + .withSystemProperty("CASSANDRA_KEYSPACE", connection.params().keyspace()); } @Test diff --git a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperResultSetReactiveTests.java b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperResultSetReactiveTests.java index 87672d8..ab079df 100644 --- a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperResultSetReactiveTests.java +++ b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperResultSetReactiveTests.java @@ -5,7 +5,7 @@ import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.cassandra.CassandraConnection; -import io.goodforgod.testcontainers.extensions.cassandra.ContainerCassandraConnection; +import io.goodforgod.testcontainers.extensions.cassandra.ConnectionCassandra; import io.goodforgod.testcontainers.extensions.cassandra.Migration; import io.goodforgod.testcontainers.extensions.cassandra.TestcontainersCassandra; import java.util.List; @@ -20,13 +20,13 @@ mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.SCRIPTS, - migrations = { "migrations" }, + locations = { "migrations" }, apply = Migration.Mode.PER_METHOD, drop = Migration.Mode.PER_METHOD)) @KoraAppTest(Application.class) class CassandraMapperResultSetReactiveTests implements KoraAppTestConfigModifier { - @ContainerCassandraConnection + @ConnectionCassandra private CassandraConnection connection; @TestComponent @@ -38,11 +38,11 @@ class CassandraMapperResultSetReactiveTests implements KoraAppTestConfigModifier @Override public KoraConfigModification config() { return KoraConfigModification - .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().host() + ":" + connection.params().port()) + .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().contactPoint()) .withSystemProperty("CASSANDRA_USER", connection.params().username()) .withSystemProperty("CASSANDRA_PASS", connection.params().password()) .withSystemProperty("CASSANDRA_DC", connection.params().datacenter()) - .withSystemProperty("CASSANDRA_KEYSPACE", "cassandra"); + .withSystemProperty("CASSANDRA_KEYSPACE", connection.params().keyspace()); } @Test diff --git a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperResultSetTests.java b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperResultSetTests.java index f298ea9..f407209 100644 --- a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperResultSetTests.java +++ b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperResultSetTests.java @@ -5,7 +5,7 @@ import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.cassandra.CassandraConnection; -import io.goodforgod.testcontainers.extensions.cassandra.ContainerCassandraConnection; +import io.goodforgod.testcontainers.extensions.cassandra.ConnectionCassandra; import io.goodforgod.testcontainers.extensions.cassandra.Migration; import io.goodforgod.testcontainers.extensions.cassandra.TestcontainersCassandra; import java.util.List; @@ -20,13 +20,13 @@ mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.SCRIPTS, - migrations = { "migrations" }, + locations = { "migrations" }, apply = Migration.Mode.PER_METHOD, drop = Migration.Mode.PER_METHOD)) @KoraAppTest(Application.class) class CassandraMapperResultSetTests implements KoraAppTestConfigModifier { - @ContainerCassandraConnection + @ConnectionCassandra private CassandraConnection connection; @TestComponent @@ -38,11 +38,11 @@ class CassandraMapperResultSetTests implements KoraAppTestConfigModifier { @Override public KoraConfigModification config() { return KoraConfigModification - .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().host() + ":" + connection.params().port()) + .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().contactPoint()) .withSystemProperty("CASSANDRA_USER", connection.params().username()) .withSystemProperty("CASSANDRA_PASS", connection.params().password()) .withSystemProperty("CASSANDRA_DC", connection.params().datacenter()) - .withSystemProperty("CASSANDRA_KEYSPACE", "cassandra"); + .withSystemProperty("CASSANDRA_KEYSPACE", connection.params().keyspace()); } @Test diff --git a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperRowColumnTests.java b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperRowColumnTests.java index 9ef000c..7c52f79 100644 --- a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperRowColumnTests.java +++ b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperRowColumnTests.java @@ -4,7 +4,7 @@ import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.cassandra.CassandraConnection; -import io.goodforgod.testcontainers.extensions.cassandra.ContainerCassandraConnection; +import io.goodforgod.testcontainers.extensions.cassandra.ConnectionCassandra; import io.goodforgod.testcontainers.extensions.cassandra.Migration; import io.goodforgod.testcontainers.extensions.cassandra.TestcontainersCassandra; import java.util.List; @@ -20,13 +20,13 @@ mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.SCRIPTS, - migrations = { "migrations" }, + locations = { "migrations" }, apply = Migration.Mode.PER_METHOD, drop = Migration.Mode.PER_METHOD)) @KoraAppTest(Application.class) class CassandraMapperRowColumnTests implements KoraAppTestConfigModifier { - @ContainerCassandraConnection + @ConnectionCassandra private CassandraConnection connection; @TestComponent @@ -38,11 +38,11 @@ class CassandraMapperRowColumnTests implements KoraAppTestConfigModifier { @Override public KoraConfigModification config() { return KoraConfigModification - .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().host() + ":" + connection.params().port()) + .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().contactPoint()) .withSystemProperty("CASSANDRA_USER", connection.params().username()) .withSystemProperty("CASSANDRA_PASS", connection.params().password()) .withSystemProperty("CASSANDRA_DC", connection.params().datacenter()) - .withSystemProperty("CASSANDRA_KEYSPACE", "cassandra"); + .withSystemProperty("CASSANDRA_KEYSPACE", connection.params().keyspace()); } @Test diff --git a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperRowTests.java b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperRowTests.java index a959d94..fef8d97 100644 --- a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperRowTests.java +++ b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraMapperRowTests.java @@ -4,7 +4,7 @@ import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.cassandra.CassandraConnection; -import io.goodforgod.testcontainers.extensions.cassandra.ContainerCassandraConnection; +import io.goodforgod.testcontainers.extensions.cassandra.ConnectionCassandra; import io.goodforgod.testcontainers.extensions.cassandra.Migration; import io.goodforgod.testcontainers.extensions.cassandra.TestcontainersCassandra; import java.util.List; @@ -19,13 +19,13 @@ mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.SCRIPTS, - migrations = { "migrations" }, + locations = { "migrations" }, apply = Migration.Mode.PER_METHOD, drop = Migration.Mode.PER_METHOD)) @KoraAppTest(Application.class) class CassandraMapperRowTests implements KoraAppTestConfigModifier { - @ContainerCassandraConnection + @ConnectionCassandra private CassandraConnection connection; @TestComponent @@ -37,11 +37,11 @@ class CassandraMapperRowTests implements KoraAppTestConfigModifier { @Override public KoraConfigModification config() { return KoraConfigModification - .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().host() + ":" + connection.params().port()) + .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().contactPoint()) .withSystemProperty("CASSANDRA_USER", connection.params().username()) .withSystemProperty("CASSANDRA_PASS", connection.params().password()) .withSystemProperty("CASSANDRA_DC", connection.params().datacenter()) - .withSystemProperty("CASSANDRA_KEYSPACE", "cassandra"); + .withSystemProperty("CASSANDRA_KEYSPACE", connection.params().keyspace()); } @Test diff --git a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraUdtTests.java b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraUdtTests.java index ecd630f..cd6e99a 100644 --- a/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraUdtTests.java +++ b/kora-java-database-cassandra/src/test/java/ru/tinkoff/kora/example/cassandra/CassandraUdtTests.java @@ -5,7 +5,7 @@ import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.cassandra.CassandraConnection; -import io.goodforgod.testcontainers.extensions.cassandra.ContainerCassandraConnection; +import io.goodforgod.testcontainers.extensions.cassandra.ConnectionCassandra; import io.goodforgod.testcontainers.extensions.cassandra.Migration; import io.goodforgod.testcontainers.extensions.cassandra.TestcontainersCassandra; import org.jetbrains.annotations.NotNull; @@ -19,13 +19,13 @@ mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.SCRIPTS, - migrations = { "migrations" }, + locations = { "migrations" }, apply = Migration.Mode.PER_METHOD, drop = Migration.Mode.PER_METHOD)) @KoraAppTest(Application.class) class CassandraUdtTests implements KoraAppTestConfigModifier { - @ContainerCassandraConnection + @ConnectionCassandra private CassandraConnection connection; @TestComponent @@ -35,11 +35,11 @@ class CassandraUdtTests implements KoraAppTestConfigModifier { @Override public KoraConfigModification config() { return KoraConfigModification - .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().host() + ":" + connection.params().port()) + .ofSystemProperty("CASSANDRA_CONTACT_POINTS", connection.params().contactPoint()) .withSystemProperty("CASSANDRA_USER", connection.params().username()) .withSystemProperty("CASSANDRA_PASS", connection.params().password()) .withSystemProperty("CASSANDRA_DC", connection.params().datacenter()) - .withSystemProperty("CASSANDRA_KEYSPACE", "cassandra"); + .withSystemProperty("CASSANDRA_KEYSPACE", connection.params().keyspace()); } @Test diff --git a/kora-java-database-cassandra/src/test/resources/logback-test.xml b/kora-java-database-cassandra/src/test/resources/logback-test.xml index e730b42..adccdab 100644 --- a/kora-java-database-cassandra/src/test/resources/logback-test.xml +++ b/kora-java-database-cassandra/src/test/resources/logback-test.xml @@ -16,11 +16,9 @@ - - + - - + diff --git a/kora-java-database-jdbc/build.gradle b/kora-java-database-jdbc/build.gradle index 21027ec..4f5382a 100644 --- a/kora-java-database-jdbc/build.gradle +++ b/kora-java-database-jdbc/build.gradle @@ -26,16 +26,15 @@ dependencies { koraBom platform("ru.tinkoff.kora:kora-parent:$koraVersion") annotationProcessor "ru.tinkoff.kora:annotation-processors" - implementation "org.postgresql:postgresql:42.6.0" + implementation "org.postgresql:postgresql:42.7.2" implementation "ru.tinkoff.kora:database-jdbc" - implementation "io.projectreactor:reactor-core:3.5.10" // For reactive examples (optional) - implementation "io.azam.ulidj:ulidj:1.0.4" // For sortable UUID (optional) + implementation "io.projectreactor:reactor-core:3.6.3" // For reactive examples (optional) implementation "ru.tinkoff.kora:logging-logback" implementation "ru.tinkoff.kora:config-hocon" testImplementation "ru.tinkoff.kora:test-junit5" - testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.9.6" + testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.11.0" } //noinspection GroovyAssignabilityCheck @@ -74,13 +73,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-database-jdbc/src/main/resources/application.conf b/kora-java-database-jdbc/src/main/resources/application.conf index 0906df7..b367d0e 100644 --- a/kora-java-database-jdbc/src/main/resources/application.conf +++ b/kora-java-database-jdbc/src/main/resources/application.conf @@ -3,12 +3,12 @@ db { username = ${POSTGRES_USER} password = ${POSTGRES_PASS} maxPoolSize = 10 - poolName = "example" + poolName = "kora" } logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-database-jdbc/src/main/resources/logback.xml b/kora-java-database-jdbc/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-database-jdbc/src/main/resources/logback.xml +++ b/kora-java-database-jdbc/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudMacrosTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudMacrosTests.java index 142ceb8..b4348f2 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudMacrosTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudMacrosTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class JdbcCrudMacrosTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudReactorTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudReactorTests.java index f690faf..a8b8dc1 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudReactorTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudReactorTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.time.Duration; import java.util.List; import org.jetbrains.annotations.NotNull; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class JdbcCrudReactorTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudSyncTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudSyncTests.java index bcf591c..19e6f96 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudSyncTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcCrudSyncTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class JdbcCrudSyncTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdRandomCompositeTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdRandomCompositeTests.java index ed8dde3..eb43c63 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdRandomCompositeTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdRandomCompositeTests.java @@ -3,10 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; -import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; -import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.*; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -17,7 +14,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -26,7 +23,7 @@ @KoraAppTest(Application.class) class JdbcIdRandomCompositeTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdRandomTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdRandomTests.java index a5035bf..a6b4107 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdRandomTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdRandomTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import ru.tinkoff.kora.example.jdbc.JdbcIdRandomRepository.Entity; @@ -15,7 +15,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class JdbcIdRandomTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent @@ -41,7 +41,7 @@ public KoraConfigModification config() { @Test void insertOne() { // given - var entityCreate = new Entity("Bob"); + var entityCreate = new Entity("Ivan"); // when repository.insert(entityCreate); diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdSequenceCompositeTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdSequenceCompositeTests.java index 233b000..77e8378 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdSequenceCompositeTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdSequenceCompositeTests.java @@ -3,10 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; -import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; -import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.*; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -17,7 +14,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -26,7 +23,7 @@ @KoraAppTest(Application.class) class JdbcIdSequenceCompositeTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdSequenceTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdSequenceTests.java index 38853a1..9fb4e37 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdSequenceTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcIdSequenceTests.java @@ -4,10 +4,10 @@ import static ru.tinkoff.kora.example.jdbc.JdbcIdSequenceRepository.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class JdbcIdSequenceTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcJsonbTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcJsonbTests.java index 21df0a3..59695a3 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcJsonbTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcJsonbTests.java @@ -4,10 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.UUID; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class JdbcJsonbTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperColumnTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperColumnTests.java index 7e7deb6..ba063a6 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperColumnTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperColumnTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class JdbcMapperColumnTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperParameterTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperParameterTests.java index e42d714..67b6bd2 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperParameterTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperParameterTests.java @@ -4,10 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class JdbcMapperParameterTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperResultSetTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperResultSetTests.java index e9c378d..0a259cd 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperResultSetTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperResultSetTests.java @@ -4,10 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class JdbcMapperResultSetTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperRowTests.java b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperRowTests.java index 522e26e..e18fec2 100644 --- a/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperRowTests.java +++ b/kora-java-database-jdbc/src/test/java/ru/tinkoff/kora/example/jdbc/JdbcMapperRowTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class JdbcMapperRowTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-jdbc/src/test/resources/logback-test.xml b/kora-java-database-jdbc/src/test/resources/logback-test.xml index e730b42..eb35050 100644 --- a/kora-java-database-jdbc/src/test/resources/logback-test.xml +++ b/kora-java-database-jdbc/src/test/resources/logback-test.xml @@ -16,11 +16,10 @@ - - + - + diff --git a/kora-java-database-r2dbc/build.gradle b/kora-java-database-r2dbc/build.gradle index 450c8ef..1e7021a 100644 --- a/kora-java-database-r2dbc/build.gradle +++ b/kora-java-database-r2dbc/build.gradle @@ -26,16 +26,16 @@ dependencies { koraBom platform("ru.tinkoff.kora:kora-parent:$koraVersion") annotationProcessor "ru.tinkoff.kora:annotation-processors" - runtimeOnly "io.netty:netty-transport-native-epoll:4.1.100.Final:linux-x86_64" - implementation "org.postgresql:r2dbc-postgresql:1.0.0.RELEASE" + runtimeOnly "io.netty:netty-transport-native-epoll:4.1.109.Final:linux-x86_64" + implementation "org.postgresql:r2dbc-postgresql:1.0.4.RELEASE" implementation "ru.tinkoff.kora:database-r2dbc" implementation "ru.tinkoff.kora:logging-logback" implementation "ru.tinkoff.kora:config-hocon" testImplementation "ru.tinkoff.kora:test-junit5" - testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.9.6" - testImplementation "org.postgresql:postgresql:42.6.0" + testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.11.0" + testImplementation "org.postgresql:postgresql:42.7.2" } //noinspection GroovyAssignabilityCheck @@ -74,13 +74,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-database-r2dbc/src/main/resources/application.conf b/kora-java-database-r2dbc/src/main/resources/application.conf index aa5039d..e1e3829 100644 --- a/kora-java-database-r2dbc/src/main/resources/application.conf +++ b/kora-java-database-r2dbc/src/main/resources/application.conf @@ -3,12 +3,12 @@ db { username = ${POSTGRES_USER} password = ${POSTGRES_PASS} maxPoolSize = 10 - poolName = "example" + poolName = "kora" } logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-database-r2dbc/src/main/resources/logback.xml b/kora-java-database-r2dbc/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-database-r2dbc/src/main/resources/logback.xml +++ b/kora-java-database-r2dbc/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudMacrosTests.java b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudMacrosTests.java index 64b5f19..f95bc75 100644 --- a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudMacrosTests.java +++ b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudMacrosTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.time.Duration; import java.util.List; import org.jetbrains.annotations.NotNull; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class R2dbcCrudMacrosTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudSyncTests.java b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudSyncTests.java index 446b3d9..05f90d5 100644 --- a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudSyncTests.java +++ b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudSyncTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class R2dbcCrudSyncTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudTests.java b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudTests.java index e308b11..794d3b7 100644 --- a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudTests.java +++ b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcCrudTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.time.Duration; import java.util.List; import org.jetbrains.annotations.NotNull; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class R2dbcCrudTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcIdRandomTests.java b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcIdRandomTests.java index fba9fee..32f7318 100644 --- a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcIdRandomTests.java +++ b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcIdRandomTests.java @@ -4,10 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import ru.tinkoff.kora.test.extension.junit5.KoraAppTest; @@ -15,7 +15,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class R2dbcIdRandomTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent @@ -43,7 +43,7 @@ public KoraConfigModification config() { @Test void syncSingleSuccess() { // given - var entityCreate = new R2dbcIdRandomRepository.Entity("Bob"); + var entityCreate = new R2dbcIdRandomRepository.Entity("Ivan"); // when repository.insert(entityCreate).block(); diff --git a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcIdSequenceTests.java b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcIdSequenceTests.java index fa981cd..b24f13e 100644 --- a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcIdSequenceTests.java +++ b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcIdSequenceTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class R2dbcIdSequenceTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent @@ -43,7 +43,7 @@ public KoraConfigModification config() { @Test void syncSingleSuccess() { // given - var entityCreate = new R2dbcIdSequenceRepository.Entity("Bob"); + var entityCreate = new R2dbcIdSequenceRepository.Entity("Ivan"); // when long id = repository.insert(entityCreate).block(); diff --git a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperColumnTests.java b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperColumnTests.java index a6e78d0..ae01003 100644 --- a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperColumnTests.java +++ b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperColumnTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class R2dbcMapperColumnTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperParameterTests.java b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperParameterTests.java index b316a92..e323b72 100644 --- a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperParameterTests.java +++ b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperParameterTests.java @@ -4,10 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class R2dbcMapperParameterTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperRowTests.java b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperRowTests.java index 5eb9f48..dc50ff1 100644 --- a/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperRowTests.java +++ b/kora-java-database-r2dbc/src/test/java/ru/tinkoff/kora/example/r2dbc/R2dbcMapperRowTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class R2dbcMapperRowTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-r2dbc/src/test/resources/logback-test.xml b/kora-java-database-r2dbc/src/test/resources/logback-test.xml index e730b42..eb35050 100644 --- a/kora-java-database-r2dbc/src/test/resources/logback-test.xml +++ b/kora-java-database-r2dbc/src/test/resources/logback-test.xml @@ -16,11 +16,10 @@ - - + - + diff --git a/kora-java-database-vertx/build.gradle b/kora-java-database-vertx/build.gradle index a157b9a..eb9e138 100644 --- a/kora-java-database-vertx/build.gradle +++ b/kora-java-database-vertx/build.gradle @@ -29,14 +29,14 @@ dependencies { implementation "ru.tinkoff.kora:database-vertx" implementation "io.vertx:vertx-pg-client:4.3.8" implementation "com.ongres.scram:client:2.1" - implementation "io.projectreactor:reactor-core:3.5.10" // For reactive examples (optional) + implementation "io.projectreactor:reactor-core:3.6.3" // For reactive examples (optional) implementation "ru.tinkoff.kora:logging-logback" implementation "ru.tinkoff.kora:config-hocon" testImplementation "ru.tinkoff.kora:test-junit5" - testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.9.6" - testImplementation "org.postgresql:postgresql:42.6.0" + testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.11.0" + testImplementation "org.postgresql:postgresql:42.7.2" } //noinspection GroovyAssignabilityCheck @@ -75,13 +75,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-database-vertx/src/main/resources/application.conf b/kora-java-database-vertx/src/main/resources/application.conf index 48fb0b7..861ab14 100644 --- a/kora-java-database-vertx/src/main/resources/application.conf +++ b/kora-java-database-vertx/src/main/resources/application.conf @@ -3,12 +3,12 @@ db { username = ${POSTGRES_USER} password = ${POSTGRES_PASS} maxPoolSize = 10 - poolName = "example" + poolName = "kora" } logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-database-vertx/src/main/resources/logback.xml b/kora-java-database-vertx/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-database-vertx/src/main/resources/logback.xml +++ b/kora-java-database-vertx/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxCrudReactorTests.java b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxCrudReactorTests.java index 056550d..a8039f1 100644 --- a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxCrudReactorTests.java +++ b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxCrudReactorTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class VertxCrudReactorTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxCrudSyncTests.java b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxCrudSyncTests.java index ec58839..cd64fc3 100644 --- a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxCrudSyncTests.java +++ b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxCrudSyncTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class VertxCrudSyncTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperColumnTests.java b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperColumnTests.java index 1435361..ec1eadb 100644 --- a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperColumnTests.java +++ b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperColumnTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class VertxMapperColumnTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperParameterTests.java b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperParameterTests.java index e9af4ba..8c422a6 100644 --- a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperParameterTests.java +++ b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperParameterTests.java @@ -4,10 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; @@ -17,7 +17,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -26,7 +26,7 @@ @KoraAppTest(Application.class) class VertxMapperParameterTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperResultSetTests.java b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperResultSetTests.java index c8df504..3dc943d 100644 --- a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperResultSetTests.java +++ b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperResultSetTests.java @@ -4,10 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -16,7 +16,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class VertxMapperResultSetTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperRowTests.java b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperRowTests.java index 9b2c036..97b2342 100644 --- a/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperRowTests.java +++ b/kora-java-database-vertx/src/test/java/ru/tinkoff/kora/example/vertx/VertxMapperRowTests.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersPostgres( +@TestcontainersPostgreSQL( mode = ContainerMode.PER_RUN, migration = @Migration( engine = Migration.Engines.FLYWAY, @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class VertxMapperRowTests implements KoraAppTestConfigModifier { - @ContainerPostgresConnection + @ConnectionPostgreSQL private JdbcConnection connection; @TestComponent diff --git a/kora-java-database-vertx/src/test/resources/logback-test.xml b/kora-java-database-vertx/src/test/resources/logback-test.xml index e730b42..eb35050 100644 --- a/kora-java-database-vertx/src/test/resources/logback-test.xml +++ b/kora-java-database-vertx/src/test/resources/logback-test.xml @@ -16,11 +16,10 @@ - - + - + diff --git a/kora-java-graalvm-crud-cassandra/Dockerfile b/kora-java-graalvm-crud-cassandra/Dockerfile new file mode 100644 index 0000000..de40743 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/Dockerfile @@ -0,0 +1,25 @@ +FROM ghcr.io/graalvm/native-image-community:21 as builder + +ARG APP_DIR=/opt/application +ARG JAR_DIR=build/libs +WORKDIR $APP_DIR + +ADD $JAR_DIR/*.jar $APP_DIR/application.jar + +RUN native-image --no-fallback -classpath $APP_DIR/application.jar + +FROM ubuntu:noble-20240212 as runner + +ARG APP_DIR=/opt/application +WORKDIR $APP_DIR + +COPY --from=builder $APP_DIR/application $APP_DIR/application + +ARG DOCKER_USER=app +RUN groupadd -r $DOCKER_USER && useradd -rg $DOCKER_USER $DOCKER_USER +RUN chmod +x application +USER $DOCKER_USER + +EXPOSE 8080/tcp +EXPOSE 8085/tcp +CMD "/opt/application/application" \ No newline at end of file diff --git a/kora-java-graalvm-crud-cassandra/README.md b/kora-java-graalvm-crud-cassandra/README.md new file mode 100644 index 0000000..d440c92 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/README.md @@ -0,0 +1,49 @@ +# Kora Java GraalVM CRUD Cassandra Service + +Пример сервиса реализованного на Kora с HTTP [CRUD](https://github.com/swagger-api/swagger-petstore) API, +в качестве базы данных выступает Cassandra, используется кэш Redis, а также другие модули которые использовались бы в реальном приложении в бою. + +В примере использовались модули: +- [HTTP Server](https://kora-projects.github.io/kora-docs/ru/documentation/http-server/) +- [HTTP Server OpenAPI Generation](https://kora-projects.github.io/kora-docs/ru/documentation/openapi-codegen/) +- [Probes](https://kora-projects.github.io/kora-docs/ru/documentation/probes/) +- [Metrics](https://kora-projects.github.io/kora-docs/ru/documentation/metrics/) +- [Database Cassandra](https://kora-projects.github.io/kora-docs/ru/documentation/database-cassandra/) +- [JSON](https://kora-projects.github.io/kora-docs/ru/documentation/json/) +- [Resilient](https://kora-projects.github.io/kora-docs/ru/documentation/resilient/) +- [Validation](https://kora-projects.github.io/kora-docs/ru/documentation/validation/) +- [Cache Redis](https://kora-projects.github.io/kora-docs/ru/documentation/cache/#redis) + +Скомпилирован с помощью [GraalVM](https://www.graalvm.org/release-notes/JDK_21/) + +## Build + +Собрать артефакт: + +```shell +./gradlew shadowJar +docker build -t kora-java-graalvm-crud-cassandra . +``` + +### Generate + +Сгенерировать API для HTTP Server: +```shell +./gradlew openApiGenerateHttpServer +``` + +## Run + +Запустить локально: +```shell +./gradlew run +``` + +## Test + +Тесты используют [Testcontainers](https://java.testcontainers.org/), требуется [Docker](https://docs.docker.com/engine/install/) окружение для запуска тестов или аналогичные контейнерные окружения ([colima](https://github.com/abiosoft/colima) / итп) + +Протестировать локально: +```shell +./gradlew test +``` diff --git a/kora-java-graalvm-crud-cassandra/build.gradle b/kora-java-graalvm-crud-cassandra/build.gradle new file mode 100644 index 0000000..8c57607 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/build.gradle @@ -0,0 +1,179 @@ +buildscript { + dependencies { + classpath("ru.tinkoff.kora:openapi-generator:$koraVersion") + } +} + +plugins { + id "java" + id "jacoco" + id "application" + + id "org.openapi.generator" version "7.1.0" + id "com.github.johnrengelman.shadow" version "8.1.1" + id "org.graalvm.buildtools.native" version "0.10.1" +} + +repositories { + mavenLocal() + mavenCentral() +} + +mainClassName = "ru.tinkoff.kora.example.graalvm.crud.cassandra.Application" + +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 + +configurations { + koraBom + implementation.extendsFrom(koraBom) + annotationProcessor.extendsFrom(koraBom) +} + +dependencies { + koraBom platform("ru.tinkoff.kora:kora-parent:$koraVersion") + annotationProcessor "org.mapstruct:mapstruct-processor:1.5.5.Final" + annotationProcessor "ru.tinkoff.kora:annotation-processors" + annotationProcessor "io.goodforgod:graalvm-hint-processor:1.2.0" + compileOnly "io.goodforgod:graalvm-hint-annotations:1.2.0" + + implementation "ru.tinkoff.kora:http-server-undertow" + implementation "ru.tinkoff.kora:database-cassandra" + implementation "ru.tinkoff.kora:micrometer-module" + implementation "ru.tinkoff.kora:json-module" + implementation "ru.tinkoff.kora:validation-module" + implementation "ru.tinkoff.kora:cache-redis" + implementation "ru.tinkoff.kora:resilient-kora" + implementation "ru.tinkoff.kora:config-hocon" + implementation "ru.tinkoff.kora:openapi-management" + implementation "ru.tinkoff.kora:logging-logback" + + implementation "io.projectreactor:reactor-core:3.6.3" // For reactive examples (optional) + implementation "org.mapstruct:mapstruct:1.5.5.Final" + + testImplementation "org.json:json:20231013" + testImplementation "org.skyscreamer:jsonassert:1.5.1" + testImplementation "redis.clients:jedis:4.4.3" + + testImplementation "org.mockito:mockito-core:5.6.0" + testImplementation "ru.tinkoff.kora:test-junit5" + testImplementation "io.goodforgod:testcontainers-extensions-cassandra:0.11.0" + testImplementation "io.goodforgod:testcontainers-extensions-redis:0.11.0" + testImplementation "org.testcontainers:junit-jupiter:1.17.6" +} + +openApiGenerate { + generatorName = "kora" + group = "openapi tools" + inputSpec = "$projectDir/src/main/resources/openapi/http-server.yaml" + outputDir = "$buildDir/generated/openapi" + apiPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.api" + modelPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.model" + invokerPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.invoker" + configOptions = [ + mode : "java-reactive-server", // так же есть java-server вариация HTTP Server"а + enableServerValidation: "true" + ] +} + +graalvmNative { + binaries { + main { + imageName = "$project.name" + mainClass = "$mainClassName" + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(21) + vendor = JvmVendorSpec.matching("GraalVM Community") + } + } + } + metadataRepository { + enabled = true + } +} + +compileJava.dependsOn tasks.openApiGenerate +processResources.dependsOn tasks.collectReachabilityMetadata +test.dependsOn tasks.shadowJar + +//noinspection GroovyAssignabilityCheck +run { + environment([ + "CASSANDRA_CONTACT_POINTS": "$cassandraHost:$cassandraPort", + "CASSANDRA_USER" : "$cassandraUser", + "CASSANDRA_PASS" : "$cassandraPassword", + "CASSANDRA_DC" : "$cassandraDatacenter", + "CASSANDRA_KEYSPACE" : "$cassandraKeyspace", + "REDIS_URL" : "redis://$redisHost:$redisPort/$redisDatabase", + "REDIS_USER" : "$redisUser", + "REDIS_PASS" : "$redisPassword", + ]) +} + +test { + jvmArgs += [ + "-XX:+TieredCompilation", + "-XX:TieredStopAtLevel=1", + ] + + environment([ + "": "" + ]) + + useJUnitPlatform() + testLogging { + showStandardStreams(true) + events("passed", "skipped", "failed") + exceptionFormat("full") + } + + jacoco { + excludes += ["**/Application*"] + } + + reports { + html.required = false + junitXml.required = false + } +} + +sourceSets { + main { + java.srcDirs += "$buildDir/generated/openapi" + resources.srcDirs += "$buildDir/native-reachability-metadata" + } +} + +jar.enabled = false +shadowJar { + mergeServiceFiles() + manifest { + attributes "Main-Class": mainClassName + attributes "Implementation-Version": koraVersion + } +} + +artifacts { + archives shadowJar +} + +compileJava { + options.encoding("UTF-8") + options.incremental(true) + options.fork = true +} + +check.dependsOn jacocoTestReport +jacocoTestReport { + reports { + xml.required = true + html.outputLocation = layout.buildDirectory.dir("jacocoHtml") + } +} + +javadoc { + options.encoding = "UTF-8" + if (JavaVersion.current().isJava9Compatible()) { + options.addBooleanOption("html5", true) + } +} \ No newline at end of file diff --git a/kora-java-graalvm-crud-cassandra/gradle.properties b/kora-java-graalvm-crud-cassandra/gradle.properties new file mode 100644 index 0000000..ed8614b --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/gradle.properties @@ -0,0 +1,16 @@ +##### CASSANDRA ##### +cassandraHost=localhost +cassandraPort=9042 +cassandraUser=cassandra +cassandraPassword=cassandra +cassandraDatacenter=datacenter1 +cassandraKeyspace=petshop + + +##### REDIS ##### +redisHost=localhost +redisPort=6379 +redisDatabase=0 +redisUser=default +redisPassword=redis + diff --git a/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/Application.java b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/Application.java new file mode 100644 index 0000000..edd762a --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/Application.java @@ -0,0 +1,36 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra; + +import io.goodforgod.graalvm.hint.annotation.NativeImageHint; +import io.goodforgod.graalvm.hint.annotation.ResourceHint; +import ru.tinkoff.kora.application.graph.KoraApplication; +import ru.tinkoff.kora.cache.redis.RedisCacheModule; +import ru.tinkoff.kora.common.KoraApp; +import ru.tinkoff.kora.config.hocon.HoconConfigModule; +import ru.tinkoff.kora.database.cassandra.CassandraDatabaseModule; +import ru.tinkoff.kora.http.server.undertow.UndertowHttpServerModule; +import ru.tinkoff.kora.json.module.JsonModule; +import ru.tinkoff.kora.logging.logback.LogbackModule; +import ru.tinkoff.kora.micrometer.module.MetricsModule; +import ru.tinkoff.kora.openapi.management.OpenApiManagementModule; +import ru.tinkoff.kora.resilient.ResilientModule; +import ru.tinkoff.kora.validation.module.ValidationModule; + +@ResourceHint(include = { "openapi/http-server.yaml" }) +@NativeImageHint(name = "application", entrypoint = Application.class) +@KoraApp +public interface Application extends + HoconConfigModule, + LogbackModule, + CassandraDatabaseModule, + ValidationModule, + JsonModule, + RedisCacheModule, + ResilientModule, + MetricsModule, + OpenApiManagementModule, + UndertowHttpServerModule { + + static void main(String[] args) { + KoraApplication.run(ApplicationGraph::graph); + } +} diff --git a/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/controller/HttpExceptionHandler.java b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/controller/HttpExceptionHandler.java new file mode 100644 index 0000000..7c0085c --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/controller/HttpExceptionHandler.java @@ -0,0 +1,42 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra.controller; + +import io.micrometer.core.instrument.config.validate.ValidationException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeoutException; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.common.Context; +import ru.tinkoff.kora.common.Tag; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.MessageTO; +import ru.tinkoff.kora.http.common.body.HttpBody; +import ru.tinkoff.kora.http.server.common.*; +import ru.tinkoff.kora.json.common.JsonWriter; + +@Tag(HttpServerModule.class) +@Component +public final class HttpExceptionHandler implements HttpServerInterceptor { + + private final JsonWriter errorJsonWriter; + + public HttpExceptionHandler(JsonWriter errorJsonWriter) { + this.errorJsonWriter = errorJsonWriter; + } + + @Override + public CompletionStage intercept(Context context, HttpServerRequest request, InterceptChain chain) + throws Exception { + return chain.process(context, request).exceptionally(e -> { + if (e instanceof HttpServerResponseException ex) { + return ex; + } + + var body = HttpBody.json(errorJsonWriter.toByteArrayUnchecked(new MessageTO(e.getMessage()))); + if (e instanceof IllegalArgumentException || e instanceof ValidationException) { + return HttpServerResponse.of(400, body); + } else if (e instanceof TimeoutException) { + return HttpServerResponse.of(408, body); + } else { + return HttpServerResponse.of(500, body); + } + }); + } +} diff --git a/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/controller/PetDelegate.java b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/controller/PetDelegate.java new file mode 100644 index 0000000..0cf73f4 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/controller/PetDelegate.java @@ -0,0 +1,86 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra.controller; + +import static ru.tinkoff.kora.example.graalvm.crud.openapi.server.api.PetApiResponses.*; + +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.model.mapper.PetMapper; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.service.PetService; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.api.PetApiDelegate; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.MessageTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetUpdateTO; + +@Component +public final class PetDelegate implements PetApiDelegate { + + private final PetMapper petMapper; + private final PetService petService; + + public PetDelegate(PetMapper petMapper, PetService petService) { + this.petMapper = petMapper; + this.petService = petService; + } + + @Override + public Mono getPetById(long petId) { + if (petId < 0) { + return Mono.just(new GetPetByIdApiResponse.GetPetById400ApiResponse(malformedId(petId))); + } + + return petService.findByID(petId) + .map(pet -> { + var body = petMapper.asDTO(pet); + return ((GetPetByIdApiResponse) new GetPetByIdApiResponse.GetPetById200ApiResponse(body)); + }) + .switchIfEmpty(Mono.fromSupplier(() -> new GetPetByIdApiResponse.GetPetById404ApiResponse(notFound(petId)))); + } + + @Override + public Mono addPet(PetCreateTO petCreateTO) { + return petService.add(petCreateTO) + .map(pet -> { + var body = petMapper.asDTO(pet); + return new AddPetApiResponse.AddPet200ApiResponse(body); + }); + } + + @Override + public Mono updatePet(long petId, PetUpdateTO petUpdateTO) { + if (petId < 0) { + return Mono.just(new UpdatePetApiResponse.UpdatePet400ApiResponse(malformedId(petId))); + } + + return petService.update(petId, petUpdateTO) + .map(updated -> { + var body = petMapper.asDTO(updated); + return ((UpdatePetApiResponse) new UpdatePetApiResponse.UpdatePet200ApiResponse(body)); + }) + .switchIfEmpty(Mono.fromSupplier(() -> new UpdatePetApiResponse.UpdatePet404ApiResponse(notFound(petId)))); + } + + @Override + public Mono deletePet(long petId) { + if (petId < 0) { + return Mono.just(new DeletePetApiResponse.DeletePet400ApiResponse(malformedId(petId))); + } + + return petService.delete(petId) + .map(isDeleted -> { + if (isDeleted) { + return new DeletePetApiResponse.DeletePet200ApiResponse( + new MessageTO("Successfully deleted pet with ID: " + petId)); + } else { + return new DeletePetApiResponse.DeletePet404ApiResponse(notFound(petId)); + } + }); + } + + private static MessageTO notFound(long petId) { + return new MessageTO("Pet not found for ID: " + petId); + } + + private static MessageTO malformedId(long petId) { + return new MessageTO("Pet malformed ID: " + petId); + } +} diff --git a/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/model/dao/Pet.java b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/model/dao/Pet.java new file mode 100644 index 0000000..3d97b86 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/model/dao/Pet.java @@ -0,0 +1,27 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra.model.dao; + +import ru.tinkoff.kora.database.common.annotation.Column; +import ru.tinkoff.kora.database.common.annotation.Id; +import ru.tinkoff.kora.database.common.annotation.Table; +import ru.tinkoff.kora.json.common.annotation.Json; + +@Json +@Table("pets") +public record Pet(@Id @Column("id") long id, + @Column("name") String name, + @Column("status") Status status, + @Column("category") String category) { + + public enum Status { + + AVAILABLE(0), + PENDING(10), + SOLD(20); + + public final int code; + + Status(int code) { + this.code = code; + } + } +} diff --git a/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/model/mapper/PetMapper.java b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/model/mapper/PetMapper.java new file mode 100644 index 0000000..651b5fc --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/model/mapper/PetMapper.java @@ -0,0 +1,18 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra.model.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.model.dao.Pet; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.CategoryTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetTO; + +@Mapper +public interface PetMapper { + + @Mapping(source = "pet", target = "category") + PetTO asDTO(Pet pet); + + @Mapping(source = "id", target = "id") + @Mapping(source = "category", target = "name") + CategoryTO asCategoryTO(Pet pet); +} diff --git a/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/repository/PetRepository.java b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/repository/PetRepository.java new file mode 100644 index 0000000..0a29fb1 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/repository/PetRepository.java @@ -0,0 +1,27 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra.repository; + +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.database.cassandra.CassandraRepository; +import ru.tinkoff.kora.database.common.annotation.Query; +import ru.tinkoff.kora.database.common.annotation.Repository; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.model.dao.Pet; + +@Repository +public interface PetRepository extends CassandraRepository { + + @Query(""" + SELECT %{return#selects} + FROM %{return#table} + WHERE id = :id + """) + Mono findById(long id); + + @Query("INSERT INTO %{entity#inserts}") + Mono insert(Pet entity); + + @Query("UPDATE %{entity#table} SET %{entity#updates} WHERE %{entity#where = @id}") + Mono update(Pet entity); + + @Query("DELETE FROM pets WHERE id = :id") + Mono deleteById(long id); +} diff --git a/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/repository/mapper/PetStatusParameterMapper.java b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/repository/mapper/PetStatusParameterMapper.java new file mode 100644 index 0000000..4545540 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/repository/mapper/PetStatusParameterMapper.java @@ -0,0 +1,20 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra.repository.mapper; + +import com.datastax.oss.driver.api.core.data.SettableByName; +import jakarta.annotation.Nullable; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.database.cassandra.mapper.parameter.CassandraParameterColumnMapper; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.model.dao.Pet; + +@Component +public final class PetStatusParameterMapper implements CassandraParameterColumnMapper { + + @Override + public void apply(SettableByName stmt, int index, @Nullable Pet.Status value) { + if (value == null) { + stmt.setToNull(index); + } else { + stmt.set(index, value.code, Integer.class); + } + } +} diff --git a/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/repository/mapper/PetStatusResultMapper.java b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/repository/mapper/PetStatusResultMapper.java new file mode 100644 index 0000000..d61b1f7 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/repository/mapper/PetStatusResultMapper.java @@ -0,0 +1,24 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra.repository.mapper; + +import com.datastax.oss.driver.api.core.data.GettableByName; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.database.cassandra.mapper.result.CassandraRowColumnMapper; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.model.dao.Pet; + +@Component +public final class PetStatusResultMapper implements CassandraRowColumnMapper { + + private final Pet.Status[] statuses = Pet.Status.values(); + + @Override + public Pet.Status apply(GettableByName row, int index) { + final int code = row.get(index, Integer.class); + for (Pet.Status status : statuses) { + if (code == status.code) { + return status; + } + } + + throw new IllegalStateException("Unknown code: " + code); + } +} diff --git a/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/service/PetCache.java b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/service/PetCache.java new file mode 100644 index 0000000..b3dd4e9 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/service/PetCache.java @@ -0,0 +1,11 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra.service; + +import ru.tinkoff.kora.cache.annotation.Cache; +import ru.tinkoff.kora.cache.redis.RedisCache; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.model.dao.Pet; +import ru.tinkoff.kora.json.common.annotation.Json; + +@Cache("pet-cache") +public interface PetCache extends RedisCache { + +} diff --git a/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/service/PetService.java b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/service/PetService.java new file mode 100644 index 0000000..558a105 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/service/PetService.java @@ -0,0 +1,75 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra.service; + +import java.util.concurrent.ThreadLocalRandom; +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.cache.annotation.CacheInvalidate; +import ru.tinkoff.kora.cache.annotation.CachePut; +import ru.tinkoff.kora.cache.annotation.Cacheable; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.model.dao.Pet; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.repository.PetRepository; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetUpdateTO; +import ru.tinkoff.kora.resilient.circuitbreaker.annotation.CircuitBreaker; +import ru.tinkoff.kora.resilient.retry.annotation.Retry; +import ru.tinkoff.kora.resilient.timeout.annotation.Timeout; + +@Component +public class PetService { + + private final PetRepository petRepository; + + public PetService(PetRepository petRepository) { + this.petRepository = petRepository; + } + + @Cacheable(PetCache.class) + @CircuitBreaker("pet") + @Retry("pet") + @Timeout("pet") + public Mono findByID(long petId) { + return petRepository.findById(petId); + } + + @CircuitBreaker("pet") + @Timeout("pet") + public Mono add(PetCreateTO createTO) { + final long petId = ThreadLocalRandom.current().nextLong(0, Long.MAX_VALUE); + final Pet pet = new Pet(petId, createTO.name(), Pet.Status.AVAILABLE, createTO.category().name()); + return petRepository.insert(pet).then(Mono.just(pet)); + } + + @CircuitBreaker("pet") + @Timeout("pet") + @CachePut(value = PetCache.class, parameters = "id") + public Mono update(long id, PetUpdateTO updateTO) { + return petRepository.findById(id) + .flatMap(pet -> { + var status = (updateTO.status() == null) + ? pet.status() + : toStatus(updateTO.status()); + + var category = (updateTO.category() == null) + ? pet.category() + : updateTO.category().name(); + + var petUpdate = new Pet(pet.id(), updateTO.name(), status, category); + return petRepository.update(petUpdate).then(Mono.just(petUpdate)); + }); + } + + @CircuitBreaker("pet") + @Timeout("pet") + @CacheInvalidate(PetCache.class) + public Mono delete(long petId) { + return petRepository.deleteById(petId).thenReturn(true); + } + + private static Pet.Status toStatus(PetUpdateTO.StatusEnum statusEnum) { + return switch (statusEnum) { + case AVAILABLE -> Pet.Status.AVAILABLE; + case PENDING -> Pet.Status.PENDING; + case SOLD -> Pet.Status.SOLD; + }; + } +} diff --git a/kora-java-graalvm-crud-cassandra/src/main/resources/application.conf b/kora-java-graalvm-crud-cassandra/src/main/resources/application.conf new file mode 100644 index 0000000..1c2e776 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/resources/application.conf @@ -0,0 +1,75 @@ +httpServer { + publicApiHttpPort = 8080 + privateApiHttpPort = 8085 +} + + +cassandra { + auth { + login = ${CASSANDRA_USER} + password = ${CASSANDRA_PASS} + } + basic { + contactPoints = ${CASSANDRA_CONTACT_POINTS} + dc = ${CASSANDRA_DC} + sessionKeyspace = ${CASSANDRA_KEYSPACE} + request { + timeout = 5s + } + } +} + +pet-cache { + maximumSize = 1000 + expireAfterWrite = ${?CACHE_EXPIRE_WRITE} + keyPrefix = "pet-" +} + + +lettuce { + uri = ${REDIS_URL} + user = ${REDIS_USER} + password = ${REDIS_PASS} + socketTimeout = 15s + commandTimeout = 15s +} + + +openapi { + management { + enabled = true + file = "openapi/http-server.yaml" + swaggerui { + enabled = true + } + rapidoc { + enabled = true + } + } +} + + +resilient { + circuitbreaker.pet { + slidingWindowSize = 50 + minimumRequiredCalls = 25 + failureRateThreshold = 50 + permittedCallsInHalfOpenState = 10 + waitDurationInOpenState = 15s + } + timeout.pet { + duration = 5000ms + } + retry.pet { + delay = 100ms + attempts = 2 + } +} + + +logging.level { + "root": "WARN" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" + "ru.tinkoff.kora.application.graph.internal.loom.VirtualThreadExecutorHolder": "DEBUG" +} diff --git a/kora-java-graalvm-crud-cassandra/src/main/resources/logback.xml b/kora-java-graalvm-crud-cassandra/src/main/resources/logback.xml new file mode 100644 index 0000000..745e83f --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n + + + + + + + + + + + + + diff --git a/kora-java-graalvm-crud-cassandra/src/main/resources/migrations/setup.cql b/kora-java-graalvm-crud-cassandra/src/main/resources/migrations/setup.cql new file mode 100644 index 0000000..52d87ac --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/resources/migrations/setup.cql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS pets +( + id BIGINT, + name VARCHAR, + status INT, + category VARCHAR, + PRIMARY KEY (id) +); diff --git a/kora-java-graalvm-crud-cassandra/src/main/resources/openapi/http-server.yaml b/kora-java-graalvm-crud-cassandra/src/main/resources/openapi/http-server.yaml new file mode 100644 index 0000000..8d99f49 --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/main/resources/openapi/http-server.yaml @@ -0,0 +1,315 @@ +openapi: 3.0.3 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.11 +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io +paths: + /v3/pets: + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + required: true + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/PetCreateTO' + responses: + '200': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + /v3/pets/{id}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: id + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + nullable: false + minimum: 1 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + parameters: + - name: id + in: path + description: Pet id to update + required: true + schema: + type: integer + format: int64 + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/PetUpdateTO' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + delete: + tags: + - pet + summary: Deletes a pet + description: delete a pet + operationId: deletePet + parameters: + - name: id + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' +components: + schemas: + MessageTO: + type: object + properties: + message: + type: string + CategoryTO: + required: + - id + - name + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + CategoryCreateTO: + required: + - name + type: object + properties: + name: + type: string + example: Dogs + PetStatusTO: + properties: + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + PetTO: + allOf: + - $ref: '#/components/schemas/PetStatusTO' + - type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + example: 10 + nullable: false + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryTO' + PetCreateTO: + required: + - name + - category + type: object + properties: + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryCreateTO' + PetUpdateTO: + allOf: + - $ref: '#/components/schemas/PetStatusTO' + - type: object + required: + - name + properties: + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryCreateTO' diff --git a/kora-java-graalvm-crud-cassandra/src/test/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/AppContainer.java b/kora-java-graalvm-crud-cassandra/src/test/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/AppContainer.java new file mode 100644 index 0000000..a61700a --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/test/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/AppContainer.java @@ -0,0 +1,47 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra; + +import java.net.URI; +import java.nio.file.Paths; +import java.time.Duration; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.utility.DockerImageName; + +public final class AppContainer extends GenericContainer { + + private AppContainer() { + super(new ImageFromDockerfile("kora-java-graalvm-crud-cassandra") + .withDockerfile(Paths.get("Dockerfile").toAbsolutePath())); + } + + private AppContainer(DockerImageName image) { + super(image); + } + + public static AppContainer build() { + final String appImage = System.getenv("IMAGE_KORA_JAVA_GRAALVM_CRUD_CASSANDRA"); + return (appImage != null && !appImage.isBlank()) + ? new AppContainer(DockerImageName.parse(appImage)) + : new AppContainer(); + } + + @Override + protected void configure() { + super.configure(); + withExposedPorts(8080, 8085); + withStartupTimeout(Duration.ofSeconds(120)); + withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(AppContainer.class))); + waitingFor(Wait.forHttp("/system/readiness").forPort(8085).forStatusCode(200)); + } + + public int getPort() { + return getMappedPort(8080); + } + + public URI getURI() { + return URI.create(String.format("http://%s:%s", getHost(), getPort())); + } +} diff --git a/kora-java-graalvm-crud-cassandra/src/test/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/PetControllerTests.java b/kora-java-graalvm-crud-cassandra/src/test/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/PetControllerTests.java new file mode 100644 index 0000000..522ae7a --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/test/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/PetControllerTests.java @@ -0,0 +1,220 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra; + +import static org.junit.jupiter.api.Assertions.*; + +import io.goodforgod.testcontainers.extensions.ContainerMode; +import io.goodforgod.testcontainers.extensions.Network; +import io.goodforgod.testcontainers.extensions.cassandra.CassandraConnection; +import io.goodforgod.testcontainers.extensions.cassandra.ConnectionCassandra; +import io.goodforgod.testcontainers.extensions.cassandra.Migration; +import io.goodforgod.testcontainers.extensions.cassandra.TestcontainersCassandra; +import io.goodforgod.testcontainers.extensions.redis.ConnectionRedis; +import io.goodforgod.testcontainers.extensions.redis.RedisConnection; +import io.goodforgod.testcontainers.extensions.redis.TestcontainersRedis; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Map; +import org.json.JSONObject; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; + +@TestcontainersCassandra( + network = @Network(shared = true), + mode = ContainerMode.PER_RUN, + migration = @Migration( + locations = "migrations", + engine = Migration.Engines.SCRIPTS, + apply = Migration.Mode.PER_METHOD, + drop = Migration.Mode.PER_METHOD)) +@TestcontainersRedis( + network = @Network(shared = true), + mode = ContainerMode.PER_RUN) +class PetControllerTests { + + private static final AppContainer container = AppContainer.build() + .withNetwork(org.testcontainers.containers.Network.SHARED); + + @ConnectionCassandra + private CassandraConnection connection; + + @BeforeEach + public void setup(@ConnectionCassandra CassandraConnection cassandraConnection, + @ConnectionRedis RedisConnection redisConnection) { + if (!container.isRunning()) { + var paramsCassandra = cassandraConnection.paramsInNetwork().orElseThrow(); + var paramsRedis = redisConnection.paramsInNetwork().orElseThrow(); + container.withEnv(Map.of( + "CASSANDRA_CONTACT_POINTS", paramsCassandra.contactPoint(), + "CASSANDRA_USER", paramsCassandra.username(), + "CASSANDRA_PASS", paramsCassandra.password(), + "CASSANDRA_DC", paramsCassandra.datacenter(), + "CASSANDRA_KEYSPACE", paramsCassandra.keyspace(), + "CACHE_EXPIRE_WRITE", "0s", + "REDIS_URL", paramsRedis.uri().toString(), + "REDIS_USER", paramsRedis.username(), + "REDIS_PASS", paramsRedis.password())); + + container.start(); + } + } + + @AfterAll + public static void cleanup() { + container.stop(); + } + + @Test + void addPet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var requestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + // when + var request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, response.statusCode(), response.body()); + + // then + connection.assertCountsEquals(1, "pets"); + var responseBody = new JSONObject(response.body()); + assertNotNull(responseBody.query("/id")); + assertNotEquals(0L, responseBody.query("/id")); + assertNotNull(responseBody.query("/status")); + assertEquals(requestBody.query("/name"), responseBody.query("/name")); + assertNotNull(responseBody.query("/category/id")); + assertEquals(requestBody.query("/category/name"), responseBody.query("/category/name")); + } + + @Test + void getPet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + // when + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + var createResponseBody = new JSONObject(createResponse.body()); + + // then + var getRequest = HttpRequest.newBuilder() + .GET() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var getResponse = httpClient.send(getRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, getResponse.statusCode(), getResponse.body()); + + var getResponseBody = new JSONObject(getResponse.body()); + JSONAssert.assertEquals(createResponseBody.toString(), getResponseBody.toString(), JSONCompareMode.LENIENT); + } + + @Test + void updatePet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + var createResponseBody = new JSONObject(createResponse.body()); + + // when + var updateRequestBody = new JSONObject() + .put("name", "doggie2") + .put("status", "pending") + .put("category", new JSONObject() + .put("name", "Dogs2")); + + var updateRequest = HttpRequest.newBuilder() + .PUT(HttpRequest.BodyPublishers.ofString(updateRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var updateResponse = httpClient.send(updateRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, updateResponse.statusCode(), updateResponse.body()); + var updateResponseBody = new JSONObject(updateResponse.body()); + + // then + var getRequest = HttpRequest.newBuilder() + .GET() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var getResponse = httpClient.send(getRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), getResponse.body()); + + var getResponseBody = new JSONObject(getResponse.body()); + JSONAssert.assertEquals(updateResponseBody.toString(), getResponseBody.toString(), JSONCompareMode.LENIENT); + } + + @Test + void deletePet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + var createResponseBody = new JSONObject(createResponse.body()); + + // when + var deleteRequest = HttpRequest.newBuilder() + .DELETE() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var deleteResponse = httpClient.send(deleteRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, deleteResponse.statusCode(), deleteResponse.body()); + + // then + connection.assertCountsEquals(0, "pets"); + } +} diff --git a/kora-java-graalvm-crud-cassandra/src/test/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/PetServiceTests.java b/kora-java-graalvm-crud-cassandra/src/test/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/PetServiceTests.java new file mode 100644 index 0000000..393cfcd --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/test/java/ru/tinkoff/kora/example/graalvm/crud/cassandra/PetServiceTests.java @@ -0,0 +1,112 @@ +package ru.tinkoff.kora.example.graalvm.crud.cassandra; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; + +import java.util.Collections; +import java.util.concurrent.CompletableFuture; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.repository.PetRepository; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.service.PetCache; +import ru.tinkoff.kora.example.graalvm.crud.cassandra.service.PetService; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.CategoryCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetUpdateTO; +import ru.tinkoff.kora.test.extension.junit5.KoraAppTest; +import ru.tinkoff.kora.test.extension.junit5.KoraAppTestConfigModifier; +import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; +import ru.tinkoff.kora.test.extension.junit5.TestComponent; + +@KoraAppTest(Application.class) +class PetServiceTests implements KoraAppTestConfigModifier { + + @Mock + @TestComponent + private PetCache petCache; + @Mock + @TestComponent + private PetRepository petRepository; + + @TestComponent + private PetService petService; + + @NotNull + @Override + public KoraConfigModification config() { + return KoraConfigModification.ofString(""" + resilient { + circuitbreaker.pet { + slidingWindowSize = 2 + minimumRequiredCalls = 2 + failureRateThreshold = 100 + permittedCallsInHalfOpenState = 1 + waitDurationInOpenState = 15s + } + timeout.pet { + duration = 5000ms + } + retry.pet { + delay = 100ms + attempts = 2 + } + } + """); + } + + @Test + void updatePetWithNewCategoryCreated() { + // given + mockCache(); + mockRepository(); + + var added = petService.add(new PetCreateTO("dog", new CategoryCreateTO("dog"))).block(); + assertNotEquals(0L, added.id()); + + // when + Mockito.when(petRepository.findById(anyLong())).thenReturn(Mono.just(added)); + Mockito.when(petRepository.update(any())).thenReturn(Mono.empty()); + var updated = petService.update(added.id(), + new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat", new CategoryCreateTO("cat"))).blockOptional(); + assertTrue(updated.isPresent()); + assertNotEquals(0L, updated.get().id()); + + // then + Mockito.verify(petRepository).insert(any()); + } + + @Test + void updatePetWithSameCategory() { + // given + mockCache(); + mockRepository(); + + var added = petService.add(new PetCreateTO("dog", new CategoryCreateTO("dog"))).block(); + assertNotEquals(0L, added.id()); + + // when + Mockito.when(petRepository.findById(anyLong())).thenReturn(Mono.just(added)); + Mockito.when(petRepository.update(any())).thenReturn(Mono.empty()); + var updated = petService.update(added.id(), + new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat", new CategoryCreateTO("dog"))).blockOptional(); + assertTrue(updated.isPresent()); + assertNotEquals(0L, updated.get().id()); + + // then + Mockito.verify(petRepository).insert(any()); + } + + private void mockCache() { + Mockito.when(petCache.getAsync(anyLong())).thenReturn(CompletableFuture.completedFuture(null)); + Mockito.when(petCache.put(anyLong(), any())).then(invocation -> invocation.getArguments()[1]); + Mockito.when(petCache.getAsync(anyCollection())).thenReturn(CompletableFuture.completedFuture(Collections.emptyMap())); + } + + private void mockRepository() { + Mockito.when(petRepository.insert(any())).thenReturn(Mono.empty()); + Mockito.when(petRepository.findById(anyLong())).thenReturn(Mono.empty()); + } +} diff --git a/kora-java-graalvm-crud-cassandra/src/test/resources/logback-test.xml b/kora-java-graalvm-crud-cassandra/src/test/resources/logback-test.xml new file mode 100644 index 0000000..adccdab --- /dev/null +++ b/kora-java-graalvm-crud-cassandra/src/test/resources/logback-test.xml @@ -0,0 +1,24 @@ + + + + + + UTF-8 + %cyan(%d{HH:mm:ss.SSS}) %highlight(%-5level) [%thread] %logger{36} - %msg%n + + + + + + + + + + + + + + + + + diff --git a/kora-java-graalvm-crud-jdbc/Dockerfile b/kora-java-graalvm-crud-jdbc/Dockerfile new file mode 100644 index 0000000..de40743 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/Dockerfile @@ -0,0 +1,25 @@ +FROM ghcr.io/graalvm/native-image-community:21 as builder + +ARG APP_DIR=/opt/application +ARG JAR_DIR=build/libs +WORKDIR $APP_DIR + +ADD $JAR_DIR/*.jar $APP_DIR/application.jar + +RUN native-image --no-fallback -classpath $APP_DIR/application.jar + +FROM ubuntu:noble-20240212 as runner + +ARG APP_DIR=/opt/application +WORKDIR $APP_DIR + +COPY --from=builder $APP_DIR/application $APP_DIR/application + +ARG DOCKER_USER=app +RUN groupadd -r $DOCKER_USER && useradd -rg $DOCKER_USER $DOCKER_USER +RUN chmod +x application +USER $DOCKER_USER + +EXPOSE 8080/tcp +EXPOSE 8085/tcp +CMD "/opt/application/application" \ No newline at end of file diff --git a/kora-java-graalvm-crud-jdbc/README.md b/kora-java-graalvm-crud-jdbc/README.md new file mode 100644 index 0000000..86b668c --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/README.md @@ -0,0 +1,49 @@ +# Kora Java GraalVM CRUD JDBC Service + +Пример сервиса реализованного на Kora с HTTP [CRUD](https://github.com/swagger-api/swagger-petstore) API, +в качестве базы данных выступает Postgres, используется кэш Caffeine, а также другие модули которые использовались бы в реальном приложении в бою. + +В примере использовались модули: +- [HTTP Server](https://kora-projects.github.io/kora-docs/ru/documentation/http-server/) +- [HTTP Server OpenAPI Generation](https://kora-projects.github.io/kora-docs/ru/documentation/openapi-codegen/) +- [Probes](https://kora-projects.github.io/kora-docs/ru/documentation/probes/) +- [Metrics](https://kora-projects.github.io/kora-docs/ru/documentation/metrics/) +- [Database JDBC](https://kora-projects.github.io/kora-docs/ru/documentation/database-jdbc/) +- [JSON](https://kora-projects.github.io/kora-docs/ru/documentation/json/) +- [Resilient](https://kora-projects.github.io/kora-docs/ru/documentation/resilient/) +- [Validation](https://kora-projects.github.io/kora-docs/ru/documentation/validation/) +- [Cache Caffeine](https://kora-projects.github.io/kora-docs/ru/documentation/cache/#caffeine) + +Скомпилирован с помощью [GraalVM](https://www.graalvm.org/release-notes/JDK_21/) + +## Build + +Собрать артефакт: + +```shell +./gradlew shadowJar +docker build -t kora-java-graalvm-crud-jdbc . +``` + +### Generate + +Сгенерировать API для HTTP Server: +```shell +./gradlew openApiGenerateHttpServer +``` + +## Run + +Запустить локально: +```shell +./gradlew run +``` + +## Test + +Тесты используют [Testcontainers](https://java.testcontainers.org/), требуется [Docker](https://docs.docker.com/engine/install/) окружение для запуска тестов или аналогичные контейнерные окружения ([colima](https://github.com/abiosoft/colima) / итп) + +Протестировать локально: +```shell +./gradlew test +``` diff --git a/kora-java-graalvm-crud-jdbc/build.gradle b/kora-java-graalvm-crud-jdbc/build.gradle new file mode 100644 index 0000000..99501a7 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/build.gradle @@ -0,0 +1,180 @@ +buildscript { + dependencies { + classpath("ru.tinkoff.kora:openapi-generator:$koraVersion") + } +} + +plugins { + id "java" + id "jacoco" + id "application" + + id "org.openapi.generator" version "7.1.0" + id "com.github.johnrengelman.shadow" version "8.1.1" + id "org.flywaydb.flyway" version "8.4.2" + id "org.graalvm.buildtools.native" version "0.10.1" +} + +repositories { + mavenLocal() + mavenCentral() +} + +mainClassName = "ru.tinkoff.kora.example.graalvm.crud.jdbc.Application" + +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 + +configurations { + koraBom + implementation.extendsFrom(koraBom) + annotationProcessor.extendsFrom(koraBom) +} + +dependencies { + koraBom platform("ru.tinkoff.kora:kora-parent:$koraVersion") + annotationProcessor "org.mapstruct:mapstruct-processor:1.5.5.Final" + annotationProcessor "ru.tinkoff.kora:annotation-processors" + annotationProcessor "io.goodforgod:graalvm-hint-processor:1.2.0" + compileOnly "io.goodforgod:graalvm-hint-annotations:1.2.0" + + implementation "ru.tinkoff.kora:http-server-undertow" + implementation "ru.tinkoff.kora:database-jdbc" + implementation "ru.tinkoff.kora:micrometer-module" + implementation "ru.tinkoff.kora:json-module" + implementation "ru.tinkoff.kora:validation-module" + implementation "ru.tinkoff.kora:cache-caffeine" + implementation "ru.tinkoff.kora:resilient-kora" + implementation "ru.tinkoff.kora:config-hocon" + implementation "ru.tinkoff.kora:openapi-management" + implementation "ru.tinkoff.kora:logging-logback" + + runtimeOnly "org.postgresql:postgresql:42.7.2" + implementation "org.mapstruct:mapstruct:1.5.5.Final" + + testImplementation "org.json:json:20231013" + testImplementation "org.skyscreamer:jsonassert:1.5.1" + + testImplementation "org.mockito:mockito-core:5.6.0" + testImplementation "ru.tinkoff.kora:test-junit5" + testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.11.0" + testImplementation "org.testcontainers:junit-jupiter:1.17.6" +} + +openApiGenerate { + generatorName = "kora" + group = "openapi tools" + inputSpec = "$projectDir/src/main/resources/openapi/http-server.yaml" + outputDir = "$buildDir/generated/openapi" + apiPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.api" + modelPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.model" + invokerPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.invoker" + configOptions = [ + mode : "java-server", // так же есть java-server вариация HTTP Server"а + enableServerValidation: "true" + ] +} + +graalvmNative { + binaries { + main { + imageName = "$project.name" + mainClass = "$mainClassName" + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(21) + vendor = JvmVendorSpec.matching("GraalVM Community") + } + } + } + metadataRepository { + enabled = true + } +} + +compileJava.dependsOn tasks.openApiGenerate +processResources.dependsOn tasks.collectReachabilityMetadata +test.dependsOn tasks.shadowJar + +//noinspection GroovyAssignabilityCheck +run { + environment([ + "POSTGRES_JDBC_URL": "jdbc:postgresql://$postgresHost:$postgresPort/$postgresDatabase", + "POSTGRES_USER" : "$postgresUser", + "POSTGRES_PASS" : "$postgresPassword", + ]) +} + +test { + jvmArgs += [ + "-XX:+TieredCompilation", + "-XX:TieredStopAtLevel=1", + ] + + environment([ + "": "" + ]) + + useJUnitPlatform() + testLogging { + showStandardStreams(true) + events("passed", "skipped", "failed") + exceptionFormat("full") + } + + jacoco { + excludes += ["**/Application*"] + } + + reports { + html.required = false + junitXml.required = false + } +} + +sourceSets { + main { + java.srcDirs += "$buildDir/generated/openapi" + resources.srcDirs += "$buildDir/native-reachability-metadata" + } +} + +flyway { + url = "jdbc:postgresql://$postgresHost:$postgresPort/$postgresDatabase" + user = "$postgresUser" + password = "$postgresPassword" + locations = ["classpath:db/migration"] +} + +jar.enabled = false +shadowJar { + mergeServiceFiles() + manifest { + attributes "Main-Class": mainClassName + attributes "Implementation-Version": koraVersion + } +} + +artifacts { + archives shadowJar +} + +compileJava { + options.encoding("UTF-8") + options.incremental(true) + options.fork = true +} + +check.dependsOn jacocoTestReport +jacocoTestReport { + reports { + xml.required = true + html.outputLocation = layout.buildDirectory.dir("jacocoHtml") + } +} + +javadoc { + options.encoding = "UTF-8" + if (JavaVersion.current().isJava9Compatible()) { + options.addBooleanOption("html5", true) + } +} \ No newline at end of file diff --git a/kora-java-graalvm-crud-jdbc/gradle.properties b/kora-java-graalvm-crud-jdbc/gradle.properties new file mode 100644 index 0000000..8e7a509 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/gradle.properties @@ -0,0 +1,6 @@ +##### POSTGRES ##### +postgresHost=localhost +postgresPort=5432 +postgresUser=postgres +postgresPassword=postgres +postgresDatabase=postgres diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/Application.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/Application.java new file mode 100644 index 0000000..e7870e5 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/Application.java @@ -0,0 +1,36 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc; + +import io.goodforgod.graalvm.hint.annotation.NativeImageHint; +import io.goodforgod.graalvm.hint.annotation.ResourceHint; +import ru.tinkoff.kora.application.graph.KoraApplication; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule; +import ru.tinkoff.kora.common.KoraApp; +import ru.tinkoff.kora.config.hocon.HoconConfigModule; +import ru.tinkoff.kora.database.jdbc.JdbcDatabaseModule; +import ru.tinkoff.kora.http.server.undertow.UndertowHttpServerModule; +import ru.tinkoff.kora.json.module.JsonModule; +import ru.tinkoff.kora.logging.logback.LogbackModule; +import ru.tinkoff.kora.micrometer.module.MetricsModule; +import ru.tinkoff.kora.openapi.management.OpenApiManagementModule; +import ru.tinkoff.kora.resilient.ResilientModule; +import ru.tinkoff.kora.validation.module.ValidationModule; + +@ResourceHint(include = { "openapi/http-server.yaml" }) +@NativeImageHint(name = "application", entrypoint = Application.class) +@KoraApp +public interface Application extends + HoconConfigModule, + LogbackModule, + JdbcDatabaseModule, + ValidationModule, + JsonModule, + CaffeineCacheModule, + ResilientModule, + MetricsModule, + OpenApiManagementModule, + UndertowHttpServerModule { + + static void main(String[] args) { + KoraApplication.run(ApplicationGraph::graph); + } +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/controller/HttpExceptionHandler.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/controller/HttpExceptionHandler.java new file mode 100644 index 0000000..5e3358c --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/controller/HttpExceptionHandler.java @@ -0,0 +1,42 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.controller; + +import io.micrometer.core.instrument.config.validate.ValidationException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeoutException; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.common.Context; +import ru.tinkoff.kora.common.Tag; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.MessageTO; +import ru.tinkoff.kora.http.common.body.HttpBody; +import ru.tinkoff.kora.http.server.common.*; +import ru.tinkoff.kora.json.common.JsonWriter; + +@Tag(HttpServerModule.class) +@Component +public final class HttpExceptionHandler implements HttpServerInterceptor { + + private final JsonWriter errorJsonWriter; + + public HttpExceptionHandler(JsonWriter errorJsonWriter) { + this.errorJsonWriter = errorJsonWriter; + } + + @Override + public CompletionStage intercept(Context context, HttpServerRequest request, InterceptChain chain) + throws Exception { + return chain.process(context, request).exceptionally(e -> { + if (e instanceof HttpServerResponseException ex) { + return ex; + } + + var body = HttpBody.json(errorJsonWriter.toByteArrayUnchecked(new MessageTO(e.getMessage()))); + if (e instanceof IllegalArgumentException || e instanceof ValidationException) { + return HttpServerResponse.of(400, body); + } else if (e instanceof TimeoutException) { + return HttpServerResponse.of(408, body); + } else { + return HttpServerResponse.of(500, body); + } + }); + } +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/controller/PetDelegate.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/controller/PetDelegate.java new file mode 100644 index 0000000..ee8a82f --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/controller/PetDelegate.java @@ -0,0 +1,79 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.controller; + +import static ru.tinkoff.kora.example.graalvm.crud.openapi.server.api.PetApiResponses.*; + +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.mapper.PetMapper; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.service.PetService; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.api.PetApiDelegate; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.*; + +@Component +public final class PetDelegate implements PetApiDelegate { + + private final PetMapper petMapper; + private final PetService petService; + + public PetDelegate(PetMapper petMapper, PetService petService) { + this.petMapper = petMapper; + this.petService = petService; + } + + @Override + public GetPetByIdApiResponse getPetById(long petId) { + if (petId < 0) { + return new GetPetByIdApiResponse.GetPetById400ApiResponse(malformedId(petId)); + } + + var pet = petService.findByID(petId); + if (pet.isPresent()) { + var body = petMapper.asDTO(pet.get()); + return new GetPetByIdApiResponse.GetPetById200ApiResponse(body); + } else { + return new GetPetByIdApiResponse.GetPetById404ApiResponse(notFound(petId)); + } + } + + @Override + public AddPetApiResponse addPet(PetCreateTO petCreateTO) { + var pet = petService.add(petCreateTO); + var body = petMapper.asDTO(pet); + return new AddPetApiResponse.AddPet200ApiResponse(body); + } + + @Override + public UpdatePetApiResponse updatePet(long petId, PetUpdateTO petUpdateTO) { + if (petId < 0) { + return new UpdatePetApiResponse.UpdatePet400ApiResponse(malformedId(petId)); + } + + var updated = petService.update(petId, petUpdateTO); + if (updated.isPresent()) { + var body = petMapper.asDTO(updated.get()); + return new UpdatePetApiResponse.UpdatePet200ApiResponse(body); + } else { + return new UpdatePetApiResponse.UpdatePet404ApiResponse(notFound(petId)); + } + } + + @Override + public DeletePetApiResponse deletePet(long petId) { + if (petId < 0) { + return new DeletePetApiResponse.DeletePet400ApiResponse(malformedId(petId)); + } + + if (petService.delete(petId)) { + return new DeletePetApiResponse.DeletePet200ApiResponse(new MessageTO("Successfully deleted pet with ID: " + petId)); + } else { + return new DeletePetApiResponse.DeletePet404ApiResponse(notFound(petId)); + } + } + + private static MessageTO notFound(long petId) { + return new MessageTO("Pet not found for ID: " + petId); + } + + private static MessageTO malformedId(long petId) { + return new MessageTO("Pet malformed ID: " + petId); + } +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/dao/Pet.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/dao/Pet.java new file mode 100644 index 0000000..1c4f907 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/dao/Pet.java @@ -0,0 +1,25 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao; + +import ru.tinkoff.kora.database.common.annotation.Column; +import ru.tinkoff.kora.database.common.annotation.Id; +import ru.tinkoff.kora.database.common.annotation.Table; + +@Table("pets") +public record Pet(@Id @Column("id") long id, + @Column("name") String name, + @Column("status") Status status, + @Column("category_id") long categoryId) { + + public enum Status { + + AVAILABLE(0), + PENDING(10), + SOLD(20); + + public final int code; + + Status(int code) { + this.code = code; + } + } +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/dao/PetCategory.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/dao/PetCategory.java new file mode 100644 index 0000000..98ab510 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/dao/PetCategory.java @@ -0,0 +1,8 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao; + +import ru.tinkoff.kora.database.common.annotation.Id; +import ru.tinkoff.kora.database.common.annotation.Table; + +@Table("categories") +public record PetCategory(@Id long id, + String name) {} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/dao/PetWithCategory.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/dao/PetWithCategory.java new file mode 100644 index 0000000..bc7966b --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/dao/PetWithCategory.java @@ -0,0 +1,14 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao; + +import ru.tinkoff.kora.database.common.annotation.Column; +import ru.tinkoff.kora.database.common.annotation.Embedded; + +public record PetWithCategory(@Column("id") long id, + @Column("name") String name, + @Column("status") Pet.Status status, + @Embedded("category_") PetCategory category) { + + public Pet getPet() { + return new Pet(id, name, status, category.id()); + } +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/mapper/PetMapper.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/mapper/PetMapper.java new file mode 100644 index 0000000..b32b4b0 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/model/mapper/PetMapper.java @@ -0,0 +1,15 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.model.mapper; + +import org.mapstruct.Mapper; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao.PetCategory; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao.PetWithCategory; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.CategoryTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetTO; + +@Mapper +public interface PetMapper { + + PetTO asDTO(PetWithCategory pet); + + CategoryTO asDTO(PetCategory category); +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/CategoryRepository.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/CategoryRepository.java new file mode 100644 index 0000000..14a85e5 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/CategoryRepository.java @@ -0,0 +1,22 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.repository; + +import java.util.Optional; +import ru.tinkoff.kora.database.common.annotation.Id; +import ru.tinkoff.kora.database.common.annotation.Query; +import ru.tinkoff.kora.database.common.annotation.Repository; +import ru.tinkoff.kora.database.jdbc.JdbcRepository; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao.PetCategory; + +@Repository +public interface CategoryRepository extends JdbcRepository { + + @Query("SELECT %{return#selects} FROM %{return#table} WHERE name = :name") + Optional findByName(String name); + + @Id + @Query("INSERT INTO categories(name) VALUES (:categoryName)") + long insert(String categoryName); + + @Query("DELETE FROM categories WHERE id = :id") + void deleteById(long id); +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/PetRepository.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/PetRepository.java new file mode 100644 index 0000000..af5be34 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/PetRepository.java @@ -0,0 +1,32 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.repository; + +import java.util.Optional; +import ru.tinkoff.kora.database.common.UpdateCount; +import ru.tinkoff.kora.database.common.annotation.Id; +import ru.tinkoff.kora.database.common.annotation.Query; +import ru.tinkoff.kora.database.common.annotation.Repository; +import ru.tinkoff.kora.database.jdbc.JdbcRepository; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao.Pet; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao.PetWithCategory; + +@Repository +public interface PetRepository extends JdbcRepository { + + @Query(""" + SELECT p.id, p.name, p.status, p.category_id, c.name as category_name + FROM pets p + JOIN categories c on c.id = p.category_id + WHERE p.id = :id + """) + Optional findById(long id); + + @Id + @Query("INSERT INTO %{entity#inserts -= id}") + long insert(Pet entity); + + @Query("UPDATE %{entity#table} SET %{entity#updates} WHERE %{entity#where = @id}") + void update(Pet entity); + + @Query("DELETE FROM pets WHERE id = :id") + UpdateCount deleteById(long id); +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/mapper/PetStatusParameterMapper.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/mapper/PetStatusParameterMapper.java new file mode 100644 index 0000000..cec0871 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/mapper/PetStatusParameterMapper.java @@ -0,0 +1,21 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.repository.mapper; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.database.jdbc.mapper.parameter.JdbcParameterColumnMapper; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao.Pet; + +@Component +public final class PetStatusParameterMapper implements JdbcParameterColumnMapper { + + @Override + public void set(PreparedStatement stmt, int index, Pet.Status value) throws SQLException { + if (value == null) { + stmt.setNull(index, Types.INTEGER); + } else { + stmt.setInt(index, value.code); + } + } +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/mapper/PetStatusResultMapper.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/mapper/PetStatusResultMapper.java new file mode 100644 index 0000000..62fb611 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/repository/mapper/PetStatusResultMapper.java @@ -0,0 +1,25 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.repository.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.database.jdbc.mapper.result.JdbcResultColumnMapper; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao.Pet; + +@Component +public final class PetStatusResultMapper implements JdbcResultColumnMapper { + + private final Pet.Status[] statuses = Pet.Status.values(); + + @Override + public Pet.Status apply(ResultSet row, int index) throws SQLException { + final int code = row.getInt(index); + for (Pet.Status status : statuses) { + if (code == status.code) { + return status; + } + } + + throw new IllegalStateException("Unknown code: " + code); + } +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/service/PetCache.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/service/PetCache.java new file mode 100644 index 0000000..cc9e795 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/service/PetCache.java @@ -0,0 +1,10 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.service; + +import ru.tinkoff.kora.cache.annotation.Cache; +import ru.tinkoff.kora.cache.caffeine.CaffeineCache; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao.PetWithCategory; + +@Cache("pet-cache") +public interface PetCache extends CaffeineCache { + +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/service/PetService.java b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/service/PetService.java new file mode 100644 index 0000000..28acd83 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/service/PetService.java @@ -0,0 +1,94 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc.service; + +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; +import ru.tinkoff.kora.cache.annotation.CacheInvalidate; +import ru.tinkoff.kora.cache.annotation.CachePut; +import ru.tinkoff.kora.cache.annotation.Cacheable; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao.Pet; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao.PetCategory; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.model.dao.PetWithCategory; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.repository.CategoryRepository; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.repository.PetRepository; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetUpdateTO; +import ru.tinkoff.kora.resilient.circuitbreaker.annotation.CircuitBreaker; +import ru.tinkoff.kora.resilient.retry.annotation.Retry; +import ru.tinkoff.kora.resilient.timeout.annotation.Timeout; + +@Component +public class PetService { + + private final PetRepository petRepository; + private final CategoryRepository categoryRepository; + + public PetService(PetRepository petRepository, CategoryRepository categoryRepository) { + this.petRepository = petRepository; + this.categoryRepository = categoryRepository; + } + + @Cacheable(PetCache.class) + @CircuitBreaker("pet") + @Retry("pet") + @Timeout("pet") + public Optional findByID(long petId) { + return petRepository.findById(petId); + } + + @CircuitBreaker("pet") + @Timeout("pet") + public PetWithCategory add(PetCreateTO createTO) { + final long petCategoryId = categoryRepository.findByName(createTO.category().name()) + .map(PetCategory::id) + .orElseGet(() -> categoryRepository.insert(createTO.category().name())); + + var pet = new Pet(ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE), createTO.name(), Pet.Status.AVAILABLE, + petCategoryId); + var petId = petRepository.insert(pet); + + return new PetWithCategory(petId, pet.name(), pet.status(), + new PetCategory(petCategoryId, createTO.category().name())); + } + + @CircuitBreaker("pet") + @Timeout("pet") + @CachePut(value = PetCache.class, parameters = "id") + public Optional update(long id, PetUpdateTO updateTO) { + final Optional existing = petRepository.findById(id); + if (existing.isEmpty()) { + return Optional.empty(); + } + + var category = existing.get().category(); + if (updateTO.category() != null) { + category = categoryRepository.findByName(updateTO.category().name()).orElseGet(() -> { + final long newCategoryId = categoryRepository.insert(updateTO.category().name()); + return new PetCategory(newCategoryId, updateTO.category().name()); + }); + } + + var status = (updateTO.status() == null) + ? existing.get().status() + : toStatus(updateTO.status()); + var result = new PetWithCategory(existing.get().id(), updateTO.name(), status, category); + + petRepository.update(result.getPet()); + return Optional.of(result); + } + + @CircuitBreaker("pet") + @Timeout("pet") + @CacheInvalidate(PetCache.class) + public boolean delete(long petId) { + return petRepository.deleteById(petId).value() == 1; + } + + private static Pet.Status toStatus(PetUpdateTO.StatusEnum statusEnum) { + return switch (statusEnum) { + case AVAILABLE -> Pet.Status.AVAILABLE; + case PENDING -> Pet.Status.PENDING; + case SOLD -> Pet.Status.SOLD; + }; + } +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/resources/application.conf b/kora-java-graalvm-crud-jdbc/src/main/resources/application.conf new file mode 100644 index 0000000..b36f0c4 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/resources/application.conf @@ -0,0 +1,60 @@ +httpServer { + publicApiHttpPort = 8080 + privateApiHttpPort = 8085 +} + + +db { + jdbcUrl = ${POSTGRES_JDBC_URL} + username = ${POSTGRES_USER} + password = ${POSTGRES_PASS} + maxPoolSize = 10 + poolName = "kora" + initializationFailTimeout = "10s" +} + + +pet-cache { + maximumSize = 1000 + expireAfterWrite = ${?CACHE_EXPIRE_WRITE} +} + + +openapi { + management { + enabled = true + file = "openapi/http-server.yaml" + swaggerui { + enabled = true + } + rapidoc { + enabled = true + } + } +} + + +resilient { + circuitbreaker.pet { + slidingWindowSize = 50 + minimumRequiredCalls = 25 + failureRateThreshold = 50 + permittedCallsInHalfOpenState = 10 + waitDurationInOpenState = 15s + } + timeout.pet { + duration = 5000ms + } + retry.pet { + delay = 100ms + attempts = 2 + } +} + + +logging.level { + "root": "WARN" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" + "ru.tinkoff.kora.application.graph.internal.loom.VirtualThreadExecutorHolder": "DEBUG" +} diff --git a/kora-java-graalvm-crud-jdbc/src/main/resources/db/migration/V1__setup-tables.sql b/kora-java-graalvm-crud-jdbc/src/main/resources/db/migration/V1__setup-tables.sql new file mode 100644 index 0000000..3e70499 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/resources/db/migration/V1__setup-tables.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS categories +( + id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY, + name VARCHAR NOT NULL, + PRIMARY KEY (id) +); + + +CREATE TABLE IF NOT EXISTS pets +( + id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY, + name VARCHAR NOT NULL, + status SMALLINT NOT NULL, + category_id BIGINT NOT NULL REFERENCES categories(id), + PRIMARY KEY (id) +); diff --git a/kora-java-graalvm-crud-jdbc/src/main/resources/logback.xml b/kora-java-graalvm-crud-jdbc/src/main/resources/logback.xml new file mode 100644 index 0000000..745e83f --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n + + + + + + + + + + + + + diff --git a/kora-java-graalvm-crud-jdbc/src/main/resources/openapi/http-server.yaml b/kora-java-graalvm-crud-jdbc/src/main/resources/openapi/http-server.yaml new file mode 100644 index 0000000..8d99f49 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/main/resources/openapi/http-server.yaml @@ -0,0 +1,315 @@ +openapi: 3.0.3 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.11 +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io +paths: + /v3/pets: + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + required: true + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/PetCreateTO' + responses: + '200': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + /v3/pets/{id}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: id + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + nullable: false + minimum: 1 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + parameters: + - name: id + in: path + description: Pet id to update + required: true + schema: + type: integer + format: int64 + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/PetUpdateTO' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + delete: + tags: + - pet + summary: Deletes a pet + description: delete a pet + operationId: deletePet + parameters: + - name: id + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' +components: + schemas: + MessageTO: + type: object + properties: + message: + type: string + CategoryTO: + required: + - id + - name + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + CategoryCreateTO: + required: + - name + type: object + properties: + name: + type: string + example: Dogs + PetStatusTO: + properties: + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + PetTO: + allOf: + - $ref: '#/components/schemas/PetStatusTO' + - type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + example: 10 + nullable: false + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryTO' + PetCreateTO: + required: + - name + - category + type: object + properties: + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryCreateTO' + PetUpdateTO: + allOf: + - $ref: '#/components/schemas/PetStatusTO' + - type: object + required: + - name + properties: + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryCreateTO' diff --git a/kora-java-graalvm-crud-jdbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/AppContainer.java b/kora-java-graalvm-crud-jdbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/AppContainer.java new file mode 100644 index 0000000..90192cd --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/AppContainer.java @@ -0,0 +1,47 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc; + +import java.net.URI; +import java.nio.file.Paths; +import java.time.Duration; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.utility.DockerImageName; + +public final class AppContainer extends GenericContainer { + + private AppContainer() { + super(new ImageFromDockerfile("kora-java-graalvm-crud-jdbc") + .withDockerfile(Paths.get("Dockerfile").toAbsolutePath())); + } + + private AppContainer(DockerImageName image) { + super(image); + } + + public static AppContainer build() { + final String appImage = System.getenv("IMAGE_KORA_JAVA_GRAALVM_CRUD_JDBC"); + return (appImage != null && !appImage.isBlank()) + ? new AppContainer(DockerImageName.parse(appImage)) + : new AppContainer(); + } + + @Override + protected void configure() { + super.configure(); + withExposedPorts(8080, 8085); + withStartupTimeout(Duration.ofSeconds(120)); + withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(AppContainer.class))); + waitingFor(Wait.forHttp("/system/readiness").forPort(8085).forStatusCode(200)); + } + + public int getPort() { + return getMappedPort(8080); + } + + public URI getURI() { + return URI.create(String.format("http://%s:%s", getHost(), getPort())); + } +} diff --git a/kora-java-graalvm-crud-jdbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/PetControllerTests.java b/kora-java-graalvm-crud-jdbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/PetControllerTests.java new file mode 100644 index 0000000..085fb59 --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/PetControllerTests.java @@ -0,0 +1,210 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc; + +import static org.junit.jupiter.api.Assertions.*; + +import io.goodforgod.testcontainers.extensions.ContainerMode; +import io.goodforgod.testcontainers.extensions.Network; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; +import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; +import io.goodforgod.testcontainers.extensions.jdbc.Migration; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Map; +import org.json.JSONObject; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; + +@TestcontainersPostgreSQL( + network = @Network(shared = true), + mode = ContainerMode.PER_RUN, + migration = @Migration( + engine = Migration.Engines.FLYWAY, + apply = Migration.Mode.PER_METHOD, + drop = Migration.Mode.PER_METHOD)) +class PetControllerTests { + + private static final AppContainer container = AppContainer.build() + .withNetwork(org.testcontainers.containers.Network.SHARED); + + @ConnectionPostgreSQL + private JdbcConnection connection; + + @BeforeEach + public void setup(@ConnectionPostgreSQL JdbcConnection connection) { + if (!container.isRunning()) { + var params = connection.paramsInNetwork().orElseThrow(); + container.withEnv(Map.of( + "POSTGRES_JDBC_URL", params.jdbcUrl(), + "POSTGRES_USER", params.username(), + "POSTGRES_PASS", params.password(), + "CACHE_EXPIRE_WRITE", "0s")); + + container.start(); + } + } + + @AfterAll + public static void cleanup() { + container.stop(); + } + + @Test + void addPet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var requestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + // when + var request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, response.statusCode(), response.body()); + + // then + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var responseBody = new JSONObject(response.body()); + assertNotNull(responseBody.query("/id")); + assertNotEquals(0L, responseBody.query("/id")); + assertNotNull(responseBody.query("/status")); + assertEquals(requestBody.query("/name"), responseBody.query("/name")); + assertNotNull(responseBody.query("/category/id")); + assertEquals(requestBody.query("/category/name"), responseBody.query("/category/name")); + } + + @Test + void getPet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + // when + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var createResponseBody = new JSONObject(createResponse.body()); + + // then + var getRequest = HttpRequest.newBuilder() + .GET() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var getResponse = httpClient.send(getRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, getResponse.statusCode(), getResponse.body()); + + var getResponseBody = new JSONObject(getResponse.body()); + JSONAssert.assertEquals(createResponseBody.toString(), getResponseBody.toString(), JSONCompareMode.LENIENT); + } + + @Test + void updatePet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var createResponseBody = new JSONObject(createResponse.body()); + + // when + var updateRequestBody = new JSONObject() + .put("name", "doggie2") + .put("status", "pending") + .put("category", new JSONObject() + .put("name", "Dogs2")); + + var updateRequest = HttpRequest.newBuilder() + .PUT(HttpRequest.BodyPublishers.ofString(updateRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var updateResponse = httpClient.send(updateRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, updateResponse.statusCode(), updateResponse.body()); + var updateResponseBody = new JSONObject(updateResponse.body()); + + // then + var getRequest = HttpRequest.newBuilder() + .GET() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var getResponse = httpClient.send(getRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), getResponse.body()); + + var getResponseBody = new JSONObject(getResponse.body()); + JSONAssert.assertEquals(updateResponseBody.toString(), getResponseBody.toString(), JSONCompareMode.LENIENT); + } + + @Test + void deletePet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var createResponseBody = new JSONObject(createResponse.body()); + + // when + var deleteRequest = HttpRequest.newBuilder() + .DELETE() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var deleteResponse = httpClient.send(deleteRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, deleteResponse.statusCode(), deleteResponse.body()); + + // then + connection.assertCountsEquals(0, "pets"); + } +} diff --git a/kora-java-graalvm-crud-jdbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/PetServiceTests.java b/kora-java-graalvm-crud-jdbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/PetServiceTests.java new file mode 100644 index 0000000..8ca45be --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/jdbc/PetServiceTests.java @@ -0,0 +1,123 @@ +package ru.tinkoff.kora.example.graalvm.crud.jdbc; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; + +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.repository.CategoryRepository; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.repository.PetRepository; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.service.PetCache; +import ru.tinkoff.kora.example.graalvm.crud.jdbc.service.PetService; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.CategoryCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetUpdateTO; +import ru.tinkoff.kora.test.extension.junit5.KoraAppTest; +import ru.tinkoff.kora.test.extension.junit5.KoraAppTestConfigModifier; +import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; +import ru.tinkoff.kora.test.extension.junit5.TestComponent; + +@KoraAppTest(Application.class) +class PetServiceTests implements KoraAppTestConfigModifier { + + @Mock + @TestComponent + private PetCache petCache; + @Mock + @TestComponent + private PetRepository petRepository; + @Mock + @TestComponent + private CategoryRepository categoryRepository; + + @TestComponent + private PetService petService; + + @NotNull + @Override + public KoraConfigModification config() { + return KoraConfigModification.ofString(""" + resilient { + circuitbreaker.pet { + slidingWindowSize = 2 + minimumRequiredCalls = 2 + failureRateThreshold = 100 + permittedCallsInHalfOpenState = 1 + waitDurationInOpenState = 15s + } + timeout.pet { + duration = 5000ms + } + retry.pet { + delay = 100ms + attempts = 2 + } + } + """); + } + + @Test + void updatePetWithNewCategoryCreated() { + // given + mockCache(); + mockRepository(Map.of("dog", 1L, "cat", 2L)); + + var added = petService.add(new PetCreateTO("dog", new CategoryCreateTO("dog"))); + assertEquals(1, added.id()); + assertEquals(1, added.category().id()); + + // when + Mockito.when(petRepository.findById(anyLong())).thenReturn(Optional.of(added)); + var updated = petService.update(added.id(), + new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat", new CategoryCreateTO("cat"))); + assertTrue(updated.isPresent()); + assertEquals(1, updated.get().id()); + assertEquals(2, updated.get().category().id()); + + // then + Mockito.verify(petRepository).insert(any()); + Mockito.verify(categoryRepository, Mockito.times(2)).insert(any()); + } + + @Test + void updatePetWithSameCategory() { + // given + mockCache(); + mockRepository(Map.of("dog", 1L)); + + var added = petService.add(new PetCreateTO("dog", new CategoryCreateTO("dog"))); + assertEquals(1, added.id()); + assertEquals(1, added.category().id()); + + // when + Mockito.when(petRepository.findById(anyLong())).thenReturn(Optional.of(added)); + Mockito.when(categoryRepository.findByName(any())).thenReturn(Optional.of(added.category())); + var updated = petService.update(added.id(), + new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat", new CategoryCreateTO("dog"))); + assertTrue(updated.isPresent()); + assertNotEquals(0, updated.get().id()); + assertNotEquals(0, updated.get().category().id()); + + // then + Mockito.verify(petRepository).insert(any()); + Mockito.verify(categoryRepository).insert(any()); + } + + private void mockCache() { + Mockito.when(petCache.get(anyLong())).thenReturn(null); + Mockito.when(petCache.put(anyLong(), any())).then(invocation -> invocation.getArguments()[1]); + Mockito.when(petCache.get(anyCollection())).thenReturn(Collections.emptyMap()); + } + + private void mockRepository(Map categoryNameToId) { + categoryNameToId.forEach((k, v) -> Mockito.when(categoryRepository.insert(k)).thenReturn(v)); + Mockito.when(categoryRepository.findByName(any())).thenReturn(Optional.empty()); + Mockito.when(petRepository.insert(any())).thenReturn(1L); + Mockito.when(petRepository.findById(anyLong())).thenReturn(Optional.empty()); + } +} diff --git a/kora-java-graalvm-crud-jdbc/src/test/resources/logback-test.xml b/kora-java-graalvm-crud-jdbc/src/test/resources/logback-test.xml new file mode 100644 index 0000000..adccdab --- /dev/null +++ b/kora-java-graalvm-crud-jdbc/src/test/resources/logback-test.xml @@ -0,0 +1,24 @@ + + + + + + UTF-8 + %cyan(%d{HH:mm:ss.SSS}) %highlight(%-5level) [%thread] %logger{36} - %msg%n + + + + + + + + + + + + + + + + + diff --git a/kora-java-graalvm-crud-r2dbc/Dockerfile b/kora-java-graalvm-crud-r2dbc/Dockerfile new file mode 100644 index 0000000..de40743 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/Dockerfile @@ -0,0 +1,25 @@ +FROM ghcr.io/graalvm/native-image-community:21 as builder + +ARG APP_DIR=/opt/application +ARG JAR_DIR=build/libs +WORKDIR $APP_DIR + +ADD $JAR_DIR/*.jar $APP_DIR/application.jar + +RUN native-image --no-fallback -classpath $APP_DIR/application.jar + +FROM ubuntu:noble-20240212 as runner + +ARG APP_DIR=/opt/application +WORKDIR $APP_DIR + +COPY --from=builder $APP_DIR/application $APP_DIR/application + +ARG DOCKER_USER=app +RUN groupadd -r $DOCKER_USER && useradd -rg $DOCKER_USER $DOCKER_USER +RUN chmod +x application +USER $DOCKER_USER + +EXPOSE 8080/tcp +EXPOSE 8085/tcp +CMD "/opt/application/application" \ No newline at end of file diff --git a/kora-java-graalvm-crud-r2dbc/README.md b/kora-java-graalvm-crud-r2dbc/README.md new file mode 100644 index 0000000..3b98d3b --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/README.md @@ -0,0 +1,49 @@ +# Kora Java GraalVM CRUD R2DBC Service + +Пример сервиса реализованного на Kora с HTTP [CRUD](https://github.com/swagger-api/swagger-petstore) API, +в качестве базы данных выступает Postgres, используется кэш Caffeine, а также другие модули которые использовались бы в реальном приложении в бою. + +В примере использовались модули: +- [HTTP Server](https://kora-projects.github.io/kora-docs/ru/documentation/http-server/) +- [HTTP Server OpenAPI Generation](https://kora-projects.github.io/kora-docs/ru/documentation/openapi-codegen/) +- [Probes](https://kora-projects.github.io/kora-docs/ru/documentation/probes/) +- [Metrics](https://kora-projects.github.io/kora-docs/ru/documentation/metrics/) +- [Database R2DBC](https://kora-projects.github.io/kora-docs/ru/documentation/database-r2dbc/) +- [JSON](https://kora-projects.github.io/kora-docs/ru/documentation/json/) +- [Resilient](https://kora-projects.github.io/kora-docs/ru/documentation/resilient/) +- [Validation](https://kora-projects.github.io/kora-docs/ru/documentation/validation/) +- [Cache Caffeine](https://kora-projects.github.io/kora-docs/ru/documentation/cache/#caffeine) + +Скомпилирован с помощью [GraalVM](https://www.graalvm.org/release-notes/JDK_21/) + +## Build + +Собрать артефакт: + +```shell +./gradlew shadowJar +docker build -t kora-java-graalvm-crud-r2dbc . +``` + +### Generate + +Сгенерировать API для HTTP Server: +```shell +./gradlew openApiGenerateHttpServer +``` + +## Run + +Запустить локально: +```shell +./gradlew run +``` + +## Test + +Тесты используют [Testcontainers](https://java.testcontainers.org/), требуется [Docker](https://docs.docker.com/engine/install/) окружение для запуска тестов или аналогичные контейнерные окружения ([colima](https://github.com/abiosoft/colima) / итп) + +Протестировать локально: +```shell +./gradlew test +``` diff --git a/kora-java-graalvm-crud-r2dbc/build.gradle b/kora-java-graalvm-crud-r2dbc/build.gradle new file mode 100644 index 0000000..c8b135c --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/build.gradle @@ -0,0 +1,182 @@ +buildscript { + dependencies { + classpath("ru.tinkoff.kora:openapi-generator:$koraVersion") + } +} + +plugins { + id "java" + id "jacoco" + id "application" + + id "org.openapi.generator" version "7.1.0" + id "com.github.johnrengelman.shadow" version "8.1.1" + id "org.flywaydb.flyway" version "8.4.2" + id "org.graalvm.buildtools.native" version "0.10.1" +} + +repositories { + mavenLocal() + mavenCentral() +} + +mainClassName = "ru.tinkoff.kora.example.graalvm.crud.r2dbc.Application" + +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 + +configurations { + koraBom + implementation.extendsFrom(koraBom) + annotationProcessor.extendsFrom(koraBom) +} + +dependencies { + koraBom platform("ru.tinkoff.kora:kora-parent:$koraVersion") + annotationProcessor "org.mapstruct:mapstruct-processor:1.5.5.Final" + annotationProcessor "ru.tinkoff.kora:annotation-processors" + annotationProcessor "io.goodforgod:graalvm-hint-processor:1.2.0" + compileOnly "io.goodforgod:graalvm-hint-annotations:1.2.0" + compileOnly "org.postgresql:postgresql:42.7.2" + + implementation "ru.tinkoff.kora:http-server-undertow" + implementation "ru.tinkoff.kora:database-r2dbc" + implementation "ru.tinkoff.kora:micrometer-module" + implementation "ru.tinkoff.kora:json-module" + implementation "ru.tinkoff.kora:validation-module" + implementation "ru.tinkoff.kora:cache-caffeine" + implementation "ru.tinkoff.kora:resilient-kora" + implementation "ru.tinkoff.kora:config-hocon" + implementation "ru.tinkoff.kora:openapi-management" + implementation "ru.tinkoff.kora:logging-logback" + + implementation "org.mapstruct:mapstruct:1.5.5.Final" + implementation "org.postgresql:r2dbc-postgresql:1.0.4.RELEASE" + + testImplementation "org.json:json:20231013" + testImplementation "org.skyscreamer:jsonassert:1.5.1" + testImplementation "org.postgresql:postgresql:42.7.2" + + testImplementation "org.mockito:mockito-core:5.6.0" + testImplementation "ru.tinkoff.kora:test-junit5" + testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.11.0" + testImplementation "org.testcontainers:junit-jupiter:1.17.6" +} + +openApiGenerate { + generatorName = "kora" + group = "openapi tools" + inputSpec = "$projectDir/src/main/resources/openapi/http-server.yaml" + outputDir = "$buildDir/generated/openapi" + apiPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.api" + modelPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.model" + invokerPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.invoker" + configOptions = [ + mode : "java-reactive-server", // так же есть java-server вариация HTTP Server"а + enableServerValidation: "true" + ] +} + +graalvmNative { + binaries { + main { + imageName = "$project.name" + mainClass = "$mainClassName" + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(21) + vendor = JvmVendorSpec.matching("GraalVM Community") + } + } + } + metadataRepository { + enabled = true + } +} + +compileJava.dependsOn tasks.openApiGenerate +processResources.dependsOn tasks.collectReachabilityMetadata +test.dependsOn tasks.shadowJar + +//noinspection GroovyAssignabilityCheck +run { + environment([ + "POSTGRES_R2DBC_URL": "r2dbc:postgresql://$postgresHost:$postgresPort/$postgresDatabase", + "POSTGRES_USER" : "$postgresUser", + "POSTGRES_PASS" : "$postgresPassword", + ]) +} + +test { + jvmArgs += [ + "-XX:+TieredCompilation", + "-XX:TieredStopAtLevel=1", + ] + + environment([ + "": "" + ]) + + useJUnitPlatform() + testLogging { + showStandardStreams(true) + events("passed", "skipped", "failed") + exceptionFormat("full") + } + + jacoco { + excludes += ["**/Application*"] + } + + reports { + html.required = false + junitXml.required = false + } +} + +sourceSets { + main { + java.srcDirs += "$buildDir/generated/openapi" + resources.srcDirs += "$buildDir/native-reachability-metadata" + } +} + +flyway { + url = "jdbc:postgresql://$postgresHost:$postgresPort/$postgresDatabase" + user = "$postgresUser" + password = "$postgresPassword" + locations = ["classpath:db/migration"] +} + +jar.enabled = false +shadowJar { + mergeServiceFiles() + manifest { + attributes "Main-Class": mainClassName + attributes "Implementation-Version": koraVersion + } +} + +artifacts { + archives shadowJar +} + +compileJava { + options.encoding("UTF-8") + options.incremental(true) + options.fork = true +} + +check.dependsOn jacocoTestReport +jacocoTestReport { + reports { + xml.required = true + html.outputLocation = layout.buildDirectory.dir("jacocoHtml") + } +} + +javadoc { + options.encoding = "UTF-8" + if (JavaVersion.current().isJava9Compatible()) { + options.addBooleanOption("html5", true) + } +} \ No newline at end of file diff --git a/kora-java-graalvm-crud-r2dbc/gradle.properties b/kora-java-graalvm-crud-r2dbc/gradle.properties new file mode 100644 index 0000000..8e7a509 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/gradle.properties @@ -0,0 +1,6 @@ +##### POSTGRES ##### +postgresHost=localhost +postgresPort=5432 +postgresUser=postgres +postgresPassword=postgres +postgresDatabase=postgres diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/Application.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/Application.java new file mode 100644 index 0000000..a877774 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/Application.java @@ -0,0 +1,36 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc; + +import io.goodforgod.graalvm.hint.annotation.NativeImageHint; +import io.goodforgod.graalvm.hint.annotation.ResourceHint; +import ru.tinkoff.kora.application.graph.KoraApplication; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule; +import ru.tinkoff.kora.common.KoraApp; +import ru.tinkoff.kora.config.hocon.HoconConfigModule; +import ru.tinkoff.kora.database.r2dbc.R2dbcDatabaseModule; +import ru.tinkoff.kora.http.server.undertow.UndertowHttpServerModule; +import ru.tinkoff.kora.json.module.JsonModule; +import ru.tinkoff.kora.logging.logback.LogbackModule; +import ru.tinkoff.kora.micrometer.module.MetricsModule; +import ru.tinkoff.kora.openapi.management.OpenApiManagementModule; +import ru.tinkoff.kora.resilient.ResilientModule; +import ru.tinkoff.kora.validation.module.ValidationModule; + +@ResourceHint(include = { "openapi/http-server.yaml" }) +@NativeImageHint(name = "application", entrypoint = Application.class) +@KoraApp +public interface Application extends + HoconConfigModule, + LogbackModule, + R2dbcDatabaseModule, + ValidationModule, + JsonModule, + CaffeineCacheModule, + ResilientModule, + MetricsModule, + OpenApiManagementModule, + UndertowHttpServerModule { + + static void main(String[] args) { + KoraApplication.run(ApplicationGraph::graph); + } +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/controller/HttpExceptionHandler.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/controller/HttpExceptionHandler.java new file mode 100644 index 0000000..3a5f251 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/controller/HttpExceptionHandler.java @@ -0,0 +1,42 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.controller; + +import io.micrometer.core.instrument.config.validate.ValidationException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeoutException; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.common.Context; +import ru.tinkoff.kora.common.Tag; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.MessageTO; +import ru.tinkoff.kora.http.common.body.HttpBody; +import ru.tinkoff.kora.http.server.common.*; +import ru.tinkoff.kora.json.common.JsonWriter; + +@Tag(HttpServerModule.class) +@Component +public final class HttpExceptionHandler implements HttpServerInterceptor { + + private final JsonWriter errorJsonWriter; + + public HttpExceptionHandler(JsonWriter errorJsonWriter) { + this.errorJsonWriter = errorJsonWriter; + } + + @Override + public CompletionStage intercept(Context context, HttpServerRequest request, InterceptChain chain) + throws Exception { + return chain.process(context, request).exceptionally(e -> { + if (e instanceof HttpServerResponseException ex) { + return ex; + } + + var body = HttpBody.json(errorJsonWriter.toByteArrayUnchecked(new MessageTO(e.getMessage()))); + if (e instanceof IllegalArgumentException || e instanceof ValidationException) { + return HttpServerResponse.of(400, body); + } else if (e instanceof TimeoutException) { + return HttpServerResponse.of(408, body); + } else { + return HttpServerResponse.of(500, body); + } + }); + } +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/controller/PetDelegate.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/controller/PetDelegate.java new file mode 100644 index 0000000..7222a13 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/controller/PetDelegate.java @@ -0,0 +1,82 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.controller; + +import static ru.tinkoff.kora.example.graalvm.crud.openapi.server.api.PetApiResponses.*; + +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.api.PetApiDelegate; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.MessageTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetUpdateTO; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.mapper.PetMapper; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.service.PetService; + +@Component +public final class PetDelegate implements PetApiDelegate { + + private final PetMapper petMapper; + private final PetService petService; + + public PetDelegate(PetMapper petMapper, PetService petService) { + this.petMapper = petMapper; + this.petService = petService; + } + + @Override + public Mono getPetById(long petId) { + if (petId < 0) { + return Mono.just(new GetPetByIdApiResponse.GetPetById400ApiResponse(malformedId(petId))); + } + + return petService.findByID(petId) + .map(pet -> { + var body = petMapper.asDTO(pet); + return ((GetPetByIdApiResponse) new GetPetByIdApiResponse.GetPetById200ApiResponse(body)); + }) + .switchIfEmpty(Mono.fromSupplier(() -> new GetPetByIdApiResponse.GetPetById404ApiResponse(notFound(petId)))); + } + + @Override + public Mono addPet(PetCreateTO petCreateTO) { + return petService.add(petCreateTO) + .map(pet -> { + var body = petMapper.asDTO(pet); + return new AddPetApiResponse.AddPet200ApiResponse(body); + }); + } + + @Override + public Mono updatePet(long petId, PetUpdateTO petUpdateTO) { + if (petId < 0) { + return Mono.just(new UpdatePetApiResponse.UpdatePet400ApiResponse(malformedId(petId))); + } + + return petService.update(petId, petUpdateTO) + .map(pet -> { + var body = petMapper.asDTO(pet); + return ((UpdatePetApiResponse) new UpdatePetApiResponse.UpdatePet200ApiResponse(body)); + }) + .switchIfEmpty(Mono.fromSupplier(() -> new UpdatePetApiResponse.UpdatePet404ApiResponse(notFound(petId)))); + } + + @Override + public Mono deletePet(long petId) { + if (petId < 0) { + return Mono.just(new DeletePetApiResponse.DeletePet400ApiResponse(malformedId(petId))); + } + + return petService.delete(petId) + .map(isDeleted -> (isDeleted) + ? new DeletePetApiResponse.DeletePet200ApiResponse( + new MessageTO("Successfully deleted pet with ID: " + petId)) + : new DeletePetApiResponse.DeletePet404ApiResponse(notFound(petId))); + } + + private static MessageTO notFound(long petId) { + return new MessageTO("Pet not found for ID: " + petId); + } + + private static MessageTO malformedId(long petId) { + return new MessageTO("Pet malformed ID: " + petId); + } +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/dao/Pet.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/dao/Pet.java new file mode 100644 index 0000000..d285192 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/dao/Pet.java @@ -0,0 +1,25 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao; + +import ru.tinkoff.kora.database.common.annotation.Column; +import ru.tinkoff.kora.database.common.annotation.Id; +import ru.tinkoff.kora.database.common.annotation.Table; + +@Table("pets") +public record Pet(@Id @Column("id") long id, + @Column("name") String name, + @Column("status") Status status, + @Column("category_id") long categoryId) { + + public enum Status { + + AVAILABLE(0), + PENDING(10), + SOLD(20); + + public final int code; + + Status(int code) { + this.code = code; + } + } +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/dao/PetCategory.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/dao/PetCategory.java new file mode 100644 index 0000000..8cfb55f --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/dao/PetCategory.java @@ -0,0 +1,8 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao; + +import ru.tinkoff.kora.database.common.annotation.Id; +import ru.tinkoff.kora.database.common.annotation.Table; + +@Table("categories") +public record PetCategory(@Id long id, + String name) {} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/dao/PetWithCategory.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/dao/PetWithCategory.java new file mode 100644 index 0000000..eb44134 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/dao/PetWithCategory.java @@ -0,0 +1,18 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao; + +import ru.tinkoff.kora.database.common.annotation.Column; +import ru.tinkoff.kora.database.common.annotation.Embedded; + +public record PetWithCategory(@Column("id") long id, + @Column("name") String name, + @Column("status") Pet.Status status, + @Embedded("category_") PetCategory category) { + + public PetWithCategory(Pet pet, PetCategory category) { + this(pet.id(), pet.name(), pet.status(), category); + } + + public Pet getPet() { + return new Pet(id, name, status, category.id()); + } +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/mapper/PetMapper.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/mapper/PetMapper.java new file mode 100644 index 0000000..40effdd --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/model/mapper/PetMapper.java @@ -0,0 +1,15 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.mapper; + +import org.mapstruct.Mapper; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.CategoryTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetTO; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao.PetCategory; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao.PetWithCategory; + +@Mapper +public interface PetMapper { + + PetTO asDTO(PetWithCategory pet); + + CategoryTO asDTO(PetCategory category); +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/CategoryRepository.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/CategoryRepository.java new file mode 100644 index 0000000..a88df75 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/CategoryRepository.java @@ -0,0 +1,23 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.repository; + +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.database.common.UpdateCount; +import ru.tinkoff.kora.database.common.annotation.Id; +import ru.tinkoff.kora.database.common.annotation.Query; +import ru.tinkoff.kora.database.common.annotation.Repository; +import ru.tinkoff.kora.database.r2dbc.R2dbcRepository; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao.PetCategory; + +@Repository +public interface CategoryRepository extends R2dbcRepository { + + @Query("SELECT %{return#selects} FROM %{return#table} WHERE name = :name") + Mono findByName(String name); + + @Id + @Query("INSERT INTO categories(name) VALUES (:categoryName)") + Mono insert(String categoryName); + + @Query("DELETE FROM categories WHERE id = :id") + Mono deleteById(long id); +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/PetRepository.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/PetRepository.java new file mode 100644 index 0000000..9c18736 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/PetRepository.java @@ -0,0 +1,32 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.repository; + +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.database.common.UpdateCount; +import ru.tinkoff.kora.database.common.annotation.Id; +import ru.tinkoff.kora.database.common.annotation.Query; +import ru.tinkoff.kora.database.common.annotation.Repository; +import ru.tinkoff.kora.database.r2dbc.R2dbcRepository; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao.Pet; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao.PetWithCategory; + +@Repository +public interface PetRepository extends R2dbcRepository { + + @Query(""" + SELECT p.id, p.name, p.status, p.category_id, c.name as category_name + FROM pets p + JOIN categories c on c.id = p.category_id + WHERE p.id = :id + """) + Mono findById(long id); + + @Id + @Query("INSERT INTO %{entity#inserts -= id}") + Mono insert(Pet entity); + + @Query("UPDATE %{entity#table} SET %{entity#updates} WHERE %{entity#where = @id}") + Mono update(Pet entity); + + @Query("DELETE FROM pets WHERE id = :id") + Mono deleteById(long id); +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/mapper/PetStatusParameterMapper.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/mapper/PetStatusParameterMapper.java new file mode 100644 index 0000000..958051f --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/mapper/PetStatusParameterMapper.java @@ -0,0 +1,20 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.repository.mapper; + +import io.r2dbc.spi.Statement; +import jakarta.annotation.Nullable; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.database.r2dbc.mapper.parameter.R2dbcParameterColumnMapper; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao.Pet; + +@Component +public final class PetStatusParameterMapper implements R2dbcParameterColumnMapper { + + @Override + public void apply(Statement stmt, int index, @Nullable Pet.Status value) { + if (value == null) { + stmt.bindNull(index, Integer.class); + } else { + stmt.bind(index, value.code); + } + } +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/mapper/PetStatusResultMapper.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/mapper/PetStatusResultMapper.java new file mode 100644 index 0000000..543e2bf --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/repository/mapper/PetStatusResultMapper.java @@ -0,0 +1,24 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.repository.mapper; + +import io.r2dbc.spi.Row; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.database.r2dbc.mapper.result.R2dbcResultColumnMapper; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao.Pet; + +@Component +public final class PetStatusResultMapper implements R2dbcResultColumnMapper { + + private final Pet.Status[] statuses = Pet.Status.values(); + + @Override + public Pet.Status apply(Row row, String label) { + final int code = row.get(label, Integer.class); + for (Pet.Status status : statuses) { + if (code == status.code) { + return status; + } + } + + throw new IllegalStateException("Unknown code: " + code); + } +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/service/PetCache.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/service/PetCache.java new file mode 100644 index 0000000..3515e97 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/service/PetCache.java @@ -0,0 +1,10 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.service; + +import ru.tinkoff.kora.cache.annotation.Cache; +import ru.tinkoff.kora.cache.caffeine.CaffeineCache; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao.PetWithCategory; + +@Cache("pet-cache") +public interface PetCache extends CaffeineCache { + +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/service/PetService.java b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/service/PetService.java new file mode 100644 index 0000000..8a552e1 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/service/PetService.java @@ -0,0 +1,91 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc.service; + +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.cache.annotation.CacheInvalidate; +import ru.tinkoff.kora.cache.annotation.CachePut; +import ru.tinkoff.kora.cache.annotation.Cacheable; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetUpdateTO; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao.Pet; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao.PetCategory; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.model.dao.PetWithCategory; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.repository.CategoryRepository; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.repository.PetRepository; +import ru.tinkoff.kora.resilient.circuitbreaker.annotation.CircuitBreaker; +import ru.tinkoff.kora.resilient.retry.annotation.Retry; +import ru.tinkoff.kora.resilient.timeout.annotation.Timeout; + +@Component +public class PetService { + + private final PetRepository petRepository; + private final CategoryRepository categoryRepository; + + public PetService(PetRepository petRepository, CategoryRepository categoryRepository) { + this.petRepository = petRepository; + this.categoryRepository = categoryRepository; + } + + @Cacheable(PetCache.class) + @CircuitBreaker("pet") + @Retry("pet") + @Timeout("pet") + public Mono findByID(long petId) { + return petRepository.findById(petId); + } + + @CircuitBreaker("pet") + @Timeout("pet") + public Mono add(PetCreateTO createTO) { + return categoryRepository.findByName(createTO.category().name()) + .map(PetCategory::id) + .switchIfEmpty(categoryRepository.insert(createTO.category().name())) + .flatMap(categoryId -> { + var pet = new Pet(0, createTO.name(), Pet.Status.AVAILABLE, categoryId); + return petRepository.insert(pet) + .map(petId -> new PetWithCategory(petId, pet.name(), pet.status(), + new PetCategory(categoryId, createTO.category().name()))); + }); + } + + @CircuitBreaker("pet") + @Timeout("pet") + @CachePut(value = PetCache.class, parameters = "id") + public Mono update(long id, PetUpdateTO updateTO) { + return petRepository.findById(id) + .flatMap(existingPet -> { + var categoryMono = Mono.just(existingPet.category()); + if (updateTO.category() != null) { + categoryMono = categoryRepository.findByName(updateTO.category().name()) + .switchIfEmpty(Mono.defer(() -> categoryRepository.insert(updateTO.category().name()) + .map(newCategoryId -> new PetCategory(newCategoryId, updateTO.category().name())))); + } + + var status = (updateTO.status() == null) + ? existingPet.status() + : toStatus(updateTO.status()); + + return categoryMono + .flatMap(category -> { + var result = new PetWithCategory(existingPet.id(), updateTO.name(), status, category); + return petRepository.update(result.getPet()).then(Mono.just(result)); + }); + }); + } + + @CircuitBreaker("pet") + @Timeout("pet") + @CacheInvalidate(PetCache.class) + public Mono delete(long petId) { + return petRepository.deleteById(petId).map(counter -> counter.value() == 1); + } + + private static Pet.Status toStatus(PetUpdateTO.StatusEnum statusEnum) { + return switch (statusEnum) { + case AVAILABLE -> Pet.Status.AVAILABLE; + case PENDING -> Pet.Status.PENDING; + case SOLD -> Pet.Status.SOLD; + }; + } +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/resources/application.conf b/kora-java-graalvm-crud-r2dbc/src/main/resources/application.conf new file mode 100644 index 0000000..269f7c1 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/resources/application.conf @@ -0,0 +1,60 @@ +httpServer { + publicApiHttpPort = 8080 + privateApiHttpPort = 8085 +} + + +db { + r2dbcUrl = ${POSTGRES_R2DBC_URL} + username = ${POSTGRES_USER} + password = ${POSTGRES_PASS} + maxPoolSize = 10 + poolName = "kora" + initializationFailTimeout = "10s" +} + + +pet-cache { + maximumSize = 1000 + expireAfterWrite = ${?CACHE_EXPIRE_WRITE} +} + + +openapi { + management { + enabled = true + file = "openapi/http-server.yaml" + swaggerui { + enabled = true + } + rapidoc { + enabled = true + } + } +} + + +resilient { + circuitbreaker.pet { + slidingWindowSize = 50 + minimumRequiredCalls = 25 + failureRateThreshold = 50 + permittedCallsInHalfOpenState = 10 + waitDurationInOpenState = 15s + } + timeout.pet { + duration = 5000ms + } + retry.pet { + delay = 100ms + attempts = 2 + } +} + + +logging.level { + "root": "WARN" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" + "ru.tinkoff.kora.application.graph.internal.loom.VirtualThreadExecutorHolder": "DEBUG" +} diff --git a/kora-java-graalvm-crud-r2dbc/src/main/resources/db/migration/V1__setup-tables.sql b/kora-java-graalvm-crud-r2dbc/src/main/resources/db/migration/V1__setup-tables.sql new file mode 100644 index 0000000..3e70499 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/resources/db/migration/V1__setup-tables.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS categories +( + id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY, + name VARCHAR NOT NULL, + PRIMARY KEY (id) +); + + +CREATE TABLE IF NOT EXISTS pets +( + id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY, + name VARCHAR NOT NULL, + status SMALLINT NOT NULL, + category_id BIGINT NOT NULL REFERENCES categories(id), + PRIMARY KEY (id) +); diff --git a/kora-java-graalvm-crud-r2dbc/src/main/resources/logback.xml b/kora-java-graalvm-crud-r2dbc/src/main/resources/logback.xml new file mode 100644 index 0000000..745e83f --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n + + + + + + + + + + + + + diff --git a/kora-java-graalvm-crud-r2dbc/src/main/resources/openapi/http-server.yaml b/kora-java-graalvm-crud-r2dbc/src/main/resources/openapi/http-server.yaml new file mode 100644 index 0000000..8d99f49 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/main/resources/openapi/http-server.yaml @@ -0,0 +1,315 @@ +openapi: 3.0.3 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.11 +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io +paths: + /v3/pets: + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + required: true + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/PetCreateTO' + responses: + '200': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + /v3/pets/{id}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: id + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + nullable: false + minimum: 1 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + parameters: + - name: id + in: path + description: Pet id to update + required: true + schema: + type: integer + format: int64 + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/PetUpdateTO' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + delete: + tags: + - pet + summary: Deletes a pet + description: delete a pet + operationId: deletePet + parameters: + - name: id + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' +components: + schemas: + MessageTO: + type: object + properties: + message: + type: string + CategoryTO: + required: + - id + - name + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + CategoryCreateTO: + required: + - name + type: object + properties: + name: + type: string + example: Dogs + PetStatusTO: + properties: + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + PetTO: + allOf: + - $ref: '#/components/schemas/PetStatusTO' + - type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + example: 10 + nullable: false + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryTO' + PetCreateTO: + required: + - name + - category + type: object + properties: + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryCreateTO' + PetUpdateTO: + allOf: + - $ref: '#/components/schemas/PetStatusTO' + - type: object + required: + - name + properties: + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryCreateTO' diff --git a/kora-java-graalvm-crud-r2dbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/AppContainer.java b/kora-java-graalvm-crud-r2dbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/AppContainer.java new file mode 100644 index 0000000..2071bdd --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/AppContainer.java @@ -0,0 +1,47 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc; + +import java.net.URI; +import java.nio.file.Paths; +import java.time.Duration; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.utility.DockerImageName; + +public final class AppContainer extends GenericContainer { + + private AppContainer() { + super(new ImageFromDockerfile("kora-java-graalvm-crud-r2dbc") + .withDockerfile(Paths.get("Dockerfile").toAbsolutePath())); + } + + private AppContainer(DockerImageName image) { + super(image); + } + + public static AppContainer build() { + final String appImage = System.getenv("IMAGE_KORA_JAVA_GRAALVM_CRUD_R2DBC"); + return (appImage != null && !appImage.isBlank()) + ? new AppContainer(DockerImageName.parse(appImage)) + : new AppContainer(); + } + + @Override + protected void configure() { + super.configure(); + withExposedPorts(8080, 8085); + withStartupTimeout(Duration.ofSeconds(120)); + withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(AppContainer.class))); + waitingFor(Wait.forHttp("/system/readiness").forPort(8085).forStatusCode(200)); + } + + public int getPort() { + return getMappedPort(8080); + } + + public URI getURI() { + return URI.create(String.format("http://%s:%s", getHost(), getPort())); + } +} diff --git a/kora-java-graalvm-crud-r2dbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/PetControllerTests.java b/kora-java-graalvm-crud-r2dbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/PetControllerTests.java new file mode 100644 index 0000000..61d56d1 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/PetControllerTests.java @@ -0,0 +1,211 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc; + +import static org.junit.jupiter.api.Assertions.*; + +import io.goodforgod.testcontainers.extensions.ContainerMode; +import io.goodforgod.testcontainers.extensions.Network; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; +import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; +import io.goodforgod.testcontainers.extensions.jdbc.Migration; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Map; +import org.json.JSONObject; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; + +@TestcontainersPostgreSQL( + network = @Network(shared = true), + mode = ContainerMode.PER_RUN, + migration = @Migration( + engine = Migration.Engines.FLYWAY, + apply = Migration.Mode.PER_METHOD, + drop = Migration.Mode.PER_METHOD)) +class PetControllerTests { + + private static final AppContainer container = AppContainer.build() + .withNetwork(org.testcontainers.containers.Network.SHARED); + + @ConnectionPostgreSQL + private JdbcConnection connection; + + @BeforeEach + public void setup(@ConnectionPostgreSQL JdbcConnection connection) { + if (!container.isRunning()) { + var params = connection.paramsInNetwork().orElseThrow(); + container.withEnv(Map.of( + "POSTGRES_R2DBC_URL", + "r2dbc:postgresql://%s:%s/%s".formatted(params.host(), params.port(), params.database()), + "POSTGRES_USER", params.username(), + "POSTGRES_PASS", params.password(), + "CACHE_EXPIRE_WRITE", "0s")); + + container.start(); + } + } + + @AfterAll + public static void cleanup() { + container.stop(); + } + + @Test + void addPet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var requestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + // when + var request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, response.statusCode(), response.body()); + + // then + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var responseBody = new JSONObject(response.body()); + assertNotNull(responseBody.query("/id")); + assertNotEquals(0L, responseBody.query("/id")); + assertNotNull(responseBody.query("/status")); + assertEquals(requestBody.query("/name"), responseBody.query("/name")); + assertNotNull(responseBody.query("/category/id")); + assertEquals(requestBody.query("/category/name"), responseBody.query("/category/name")); + } + + @Test + void getPet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + // when + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var createResponseBody = new JSONObject(createResponse.body()); + + // then + var getRequest = HttpRequest.newBuilder() + .GET() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var getResponse = httpClient.send(getRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, getResponse.statusCode(), getResponse.body()); + + var getResponseBody = new JSONObject(getResponse.body()); + JSONAssert.assertEquals(createResponseBody.toString(), getResponseBody.toString(), JSONCompareMode.LENIENT); + } + + @Test + void updatePet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var createResponseBody = new JSONObject(createResponse.body()); + + // when + var updateRequestBody = new JSONObject() + .put("name", "doggie2") + .put("status", "pending") + .put("category", new JSONObject() + .put("name", "Dogs2")); + + var updateRequest = HttpRequest.newBuilder() + .PUT(HttpRequest.BodyPublishers.ofString(updateRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var updateResponse = httpClient.send(updateRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, updateResponse.statusCode(), updateResponse.body()); + var updateResponseBody = new JSONObject(updateResponse.body()); + + // then + var getRequest = HttpRequest.newBuilder() + .GET() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var getResponse = httpClient.send(getRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), getResponse.body()); + + var getResponseBody = new JSONObject(getResponse.body()); + JSONAssert.assertEquals(updateResponseBody.toString(), getResponseBody.toString(), JSONCompareMode.LENIENT); + } + + @Test + void deletePet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var createResponseBody = new JSONObject(createResponse.body()); + + // when + var deleteRequest = HttpRequest.newBuilder() + .DELETE() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var deleteResponse = httpClient.send(deleteRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, deleteResponse.statusCode(), deleteResponse.body()); + + // then + connection.assertCountsEquals(0, "pets"); + } +} diff --git a/kora-java-graalvm-crud-r2dbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/PetServiceTests.java b/kora-java-graalvm-crud-r2dbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/PetServiceTests.java new file mode 100644 index 0000000..47fec72 --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/test/java/ru/tinkoff/kora/example/graalvm/crud/r2dbc/PetServiceTests.java @@ -0,0 +1,127 @@ +package ru.tinkoff.kora.example.graalvm.crud.r2dbc; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; + +import java.util.Collections; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.CategoryCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetUpdateTO; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.repository.CategoryRepository; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.repository.PetRepository; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.service.PetCache; +import ru.tinkoff.kora.example.graalvm.crud.r2dbc.service.PetService; +import ru.tinkoff.kora.test.extension.junit5.KoraAppTest; +import ru.tinkoff.kora.test.extension.junit5.KoraAppTestConfigModifier; +import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; +import ru.tinkoff.kora.test.extension.junit5.TestComponent; + +@KoraAppTest(Application.class) +class PetServiceTests implements KoraAppTestConfigModifier { + + @Mock + @TestComponent + private PetCache petCache; + @Mock + @TestComponent + private PetRepository petRepository; + @Mock + @TestComponent + private CategoryRepository categoryRepository; + + @TestComponent + private PetService petService; + + @NotNull + @Override + public KoraConfigModification config() { + return KoraConfigModification.ofString(""" + resilient { + circuitbreaker.pet { + slidingWindowSize = 2 + minimumRequiredCalls = 2 + failureRateThreshold = 100 + permittedCallsInHalfOpenState = 1 + waitDurationInOpenState = 15s + } + timeout.pet { + duration = 5000ms + } + retry.pet { + delay = 100ms + attempts = 2 + } + } + """); + } + + @Test + void updatePetWithNewCategoryCreated() { + // given + mockCache(); + mockRepository(Map.of("dog", 1L, "cat", 2L)); + + var added = petService.add(new PetCreateTO("dog", new CategoryCreateTO("dog"))).block(); + assertEquals(1, added.id()); + assertEquals(1, added.category().id()); + + // when + Mockito.when(petRepository.findById(anyLong())).thenReturn(Mono.just(added)); + Mockito.when(petRepository.update(any())).thenReturn(Mono.empty()); + var updated = petService.update(added.id(), + new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat", new CategoryCreateTO("cat"))) + .blockOptional(); + assertTrue(updated.isPresent()); + assertEquals(1, updated.get().id()); + assertEquals(2, updated.get().category().id()); + + // then + Mockito.verify(petRepository).insert(any()); + Mockito.verify(categoryRepository, Mockito.times(2)).insert(any()); + } + + @Test + void updatePetWithSameCategory() { + // given + mockCache(); + mockRepository(Map.of("dog", 1L)); + + var added = petService.add(new PetCreateTO("dog", new CategoryCreateTO("dog"))).block(); + assertEquals(1, added.id()); + assertEquals(1, added.category().id()); + + // when + Mockito.when(petRepository.findById(anyLong())).thenReturn(Mono.just(added)); + Mockito.when(petRepository.update(any())).thenReturn(Mono.empty()); + Mockito.when(categoryRepository.findByName(any())).thenReturn(Mono.just(added.category())); + var updated = petService.update(added.id(), + new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat", new CategoryCreateTO("dog"))) + .blockOptional(); + assertTrue(updated.isPresent()); + assertNotEquals(0, updated.get().id()); + assertNotEquals(0, updated.get().category().id()); + + // then + Mockito.verify(petRepository).insert(any()); + Mockito.verify(categoryRepository).insert(any()); + } + + private void mockCache() { + Mockito.when(petCache.get(anyLong())).thenReturn(null); + Mockito.when(petCache.put(anyLong(), any())).then(invocation -> invocation.getArguments()[1]); + Mockito.when(petCache.get(anyCollection())).thenReturn(Collections.emptyMap()); + } + + private void mockRepository(Map categoryNameToId) { + categoryNameToId.forEach((k, v) -> Mockito.when(categoryRepository.insert(k)).thenReturn(Mono.just(v))); + Mockito.when(categoryRepository.findByName(any())).thenReturn(Mono.empty()); + Mockito.when(petRepository.insert(any())).thenReturn(Mono.just(1L)); + Mockito.when(petRepository.findById(anyLong())).thenReturn(Mono.empty()); + } +} diff --git a/kora-java-graalvm-crud-r2dbc/src/test/resources/logback-test.xml b/kora-java-graalvm-crud-r2dbc/src/test/resources/logback-test.xml new file mode 100644 index 0000000..adccdab --- /dev/null +++ b/kora-java-graalvm-crud-r2dbc/src/test/resources/logback-test.xml @@ -0,0 +1,24 @@ + + + + + + UTF-8 + %cyan(%d{HH:mm:ss.SSS}) %highlight(%-5level) [%thread] %logger{36} - %msg%n + + + + + + + + + + + + + + + + + diff --git a/kora-java-graalvm-crud-vertx/Dockerfile b/kora-java-graalvm-crud-vertx/Dockerfile new file mode 100644 index 0000000..de40743 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/Dockerfile @@ -0,0 +1,25 @@ +FROM ghcr.io/graalvm/native-image-community:21 as builder + +ARG APP_DIR=/opt/application +ARG JAR_DIR=build/libs +WORKDIR $APP_DIR + +ADD $JAR_DIR/*.jar $APP_DIR/application.jar + +RUN native-image --no-fallback -classpath $APP_DIR/application.jar + +FROM ubuntu:noble-20240212 as runner + +ARG APP_DIR=/opt/application +WORKDIR $APP_DIR + +COPY --from=builder $APP_DIR/application $APP_DIR/application + +ARG DOCKER_USER=app +RUN groupadd -r $DOCKER_USER && useradd -rg $DOCKER_USER $DOCKER_USER +RUN chmod +x application +USER $DOCKER_USER + +EXPOSE 8080/tcp +EXPOSE 8085/tcp +CMD "/opt/application/application" \ No newline at end of file diff --git a/kora-java-graalvm-crud-vertx/README.md b/kora-java-graalvm-crud-vertx/README.md new file mode 100644 index 0000000..2aef01a --- /dev/null +++ b/kora-java-graalvm-crud-vertx/README.md @@ -0,0 +1,49 @@ +# Kora Java GraalVM CRUD Vertx Service + +Пример сервиса реализованного на Kora с HTTP [CRUD](https://github.com/swagger-api/swagger-petstore) API, +в качестве базы данных выступает Postgres, используется кэш Caffeine, а также другие модули которые использовались бы в реальном приложении в бою. + +В примере использовались модули: +- [HTTP Server](https://kora-projects.github.io/kora-docs/ru/documentation/http-server/) +- [HTTP Server OpenAPI Generation](https://kora-projects.github.io/kora-docs/ru/documentation/openapi-codegen/) +- [Probes](https://kora-projects.github.io/kora-docs/ru/documentation/probes/) +- [Metrics](https://kora-projects.github.io/kora-docs/ru/documentation/metrics/) +- [Database Vertx](https://kora-projects.github.io/kora-docs/ru/documentation/database-vertx/) +- [JSON](https://kora-projects.github.io/kora-docs/ru/documentation/json/) +- [Resilient](https://kora-projects.github.io/kora-docs/ru/documentation/resilient/) +- [Validation](https://kora-projects.github.io/kora-docs/ru/documentation/validation/) +- [Cache Caffeine](https://kora-projects.github.io/kora-docs/ru/documentation/cache/#caffeine) + +Скомпилирован с помощью [GraalVM](https://www.graalvm.org/release-notes/JDK_21/) + +## Build + +Собрать артефакт: + +```shell +./gradlew shadowJar +docker build -t kora-java-graalvm-crud-vertx . +``` + +### Generate + +Сгенерировать API для HTTP Server: +```shell +./gradlew openApiGenerateHttpServer +``` + +## Run + +Запустить локально: +```shell +./gradlew run +``` + +## Test + +Тесты используют [Testcontainers](https://java.testcontainers.org/), требуется [Docker](https://docs.docker.com/engine/install/) окружение для запуска тестов или аналогичные контейнерные окружения ([colima](https://github.com/abiosoft/colima) / итп) + +Протестировать локально: +```shell +./gradlew test +``` diff --git a/kora-java-graalvm-crud-vertx/build.gradle b/kora-java-graalvm-crud-vertx/build.gradle new file mode 100644 index 0000000..bcae7f3 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/build.gradle @@ -0,0 +1,184 @@ +buildscript { + dependencies { + classpath("ru.tinkoff.kora:openapi-generator:$koraVersion") + } +} + +plugins { + id "java" + id "jacoco" + id "application" + + id "org.openapi.generator" version "7.1.0" + id "com.github.johnrengelman.shadow" version "8.1.1" + id "org.flywaydb.flyway" version "8.4.2" + id "org.graalvm.buildtools.native" version "0.10.1" +} + +repositories { + mavenLocal() + mavenCentral() +} + +mainClassName = "ru.tinkoff.kora.example.graalvm.crud.vertx.Application" + +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 + +configurations { + koraBom + implementation.extendsFrom(koraBom) + annotationProcessor.extendsFrom(koraBom) +} + +dependencies { + koraBom platform("ru.tinkoff.kora:kora-parent:$koraVersion") + annotationProcessor "org.mapstruct:mapstruct-processor:1.5.5.Final" + annotationProcessor "ru.tinkoff.kora:annotation-processors" + annotationProcessor "io.goodforgod:graalvm-hint-processor:1.2.0" + compileOnly "io.goodforgod:graalvm-hint-annotations:1.2.0" + compileOnly "org.postgresql:postgresql:42.7.2" + + implementation "ru.tinkoff.kora:http-server-undertow" + implementation "ru.tinkoff.kora:database-vertx" + implementation "ru.tinkoff.kora:micrometer-module" + implementation "ru.tinkoff.kora:json-module" + implementation "ru.tinkoff.kora:validation-module" + implementation "ru.tinkoff.kora:cache-caffeine" + implementation "ru.tinkoff.kora:resilient-kora" + implementation "ru.tinkoff.kora:config-hocon" + implementation "ru.tinkoff.kora:openapi-management" + implementation "ru.tinkoff.kora:logging-logback" + + implementation "org.mapstruct:mapstruct:1.5.5.Final" + implementation "io.vertx:vertx-pg-client:4.3.8" + implementation "com.ongres.scram:client:2.1" + implementation "io.projectreactor:reactor-core:3.6.3" // For reactive examples (optional) + + testImplementation "org.json:json:20231013" + testImplementation "org.skyscreamer:jsonassert:1.5.1" + testImplementation "org.postgresql:postgresql:42.7.2" + + testImplementation "org.mockito:mockito-core:5.6.0" + testImplementation "ru.tinkoff.kora:test-junit5" + testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.11.0" + testImplementation "org.testcontainers:junit-jupiter:1.17.6" +} + +openApiGenerate { + generatorName = "kora" + group = "openapi tools" + inputSpec = "$projectDir/src/main/resources/openapi/http-server.yaml" + outputDir = "$buildDir/generated/openapi" + apiPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.api" + modelPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.model" + invokerPackage = "ru.tinkoff.kora.example.graalvm.crud.openapi.server.invoker" + configOptions = [ + mode : "java-reactive-server", // так же есть java-server вариация HTTP Server"а + enableServerValidation: "true" + ] +} + +graalvmNative { + binaries { + main { + imageName = "$project.name" + mainClass = "$mainClassName" + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(21) + vendor = JvmVendorSpec.matching("GraalVM Community") + } + } + } + metadataRepository { + enabled = true + } +} + +compileJava.dependsOn tasks.openApiGenerate +processResources.dependsOn tasks.collectReachabilityMetadata +test.dependsOn tasks.shadowJar + +//noinspection GroovyAssignabilityCheck +run { + environment([ + "POSTGRES_VERTX_URL": ":postgresql://$postgresHost:$postgresPort/$postgresDatabase", + "POSTGRES_USER" : "$postgresUser", + "POSTGRES_PASS" : "$postgresPassword", + ]) +} + +test { + jvmArgs += [ + "-XX:+TieredCompilation", + "-XX:TieredStopAtLevel=1", + ] + + environment([ + "": "" + ]) + + useJUnitPlatform() + testLogging { + showStandardStreams(true) + events("passed", "skipped", "failed") + exceptionFormat("full") + } + + jacoco { + excludes += ["**/Application*"] + } + + reports { + html.required = false + junitXml.required = false + } +} + +sourceSets { + main { + java.srcDirs += "$buildDir/generated/openapi" + resources.srcDirs += "$buildDir/native-reachability-metadata" + } +} + +flyway { + url = "jdbc:postgresql://$postgresHost:$postgresPort/$postgresDatabase" + user = "$postgresUser" + password = "$postgresPassword" + locations = ["classpath:db/migration"] +} + +jar.enabled = false +shadowJar { + mergeServiceFiles() + manifest { + attributes "Main-Class": mainClassName + attributes "Implementation-Version": koraVersion + } +} + +artifacts { + archives shadowJar +} + +compileJava { + options.encoding("UTF-8") + options.incremental(true) + options.fork = true +} + +check.dependsOn jacocoTestReport +jacocoTestReport { + reports { + xml.required = true + html.outputLocation = layout.buildDirectory.dir("jacocoHtml") + } +} + +javadoc { + options.encoding = "UTF-8" + if (JavaVersion.current().isJava9Compatible()) { + options.addBooleanOption("html5", true) + } +} \ No newline at end of file diff --git a/kora-java-graalvm-crud-vertx/gradle.properties b/kora-java-graalvm-crud-vertx/gradle.properties new file mode 100644 index 0000000..8e7a509 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/gradle.properties @@ -0,0 +1,6 @@ +##### POSTGRES ##### +postgresHost=localhost +postgresPort=5432 +postgresUser=postgres +postgresPassword=postgres +postgresDatabase=postgres diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/Application.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/Application.java new file mode 100644 index 0000000..37c675b --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/Application.java @@ -0,0 +1,36 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx; + +import io.goodforgod.graalvm.hint.annotation.NativeImageHint; +import io.goodforgod.graalvm.hint.annotation.ResourceHint; +import ru.tinkoff.kora.application.graph.KoraApplication; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule; +import ru.tinkoff.kora.common.KoraApp; +import ru.tinkoff.kora.config.hocon.HoconConfigModule; +import ru.tinkoff.kora.database.vertx.VertxDatabaseModule; +import ru.tinkoff.kora.http.server.undertow.UndertowHttpServerModule; +import ru.tinkoff.kora.json.module.JsonModule; +import ru.tinkoff.kora.logging.logback.LogbackModule; +import ru.tinkoff.kora.micrometer.module.MetricsModule; +import ru.tinkoff.kora.openapi.management.OpenApiManagementModule; +import ru.tinkoff.kora.resilient.ResilientModule; +import ru.tinkoff.kora.validation.module.ValidationModule; + +@ResourceHint(include = { "openapi/http-server.yaml" }) +@NativeImageHint(name = "application", entrypoint = Application.class) +@KoraApp +public interface Application extends + HoconConfigModule, + LogbackModule, + VertxDatabaseModule, + ValidationModule, + JsonModule, + CaffeineCacheModule, + ResilientModule, + MetricsModule, + OpenApiManagementModule, + UndertowHttpServerModule { + + static void main(String[] args) { + KoraApplication.run(ApplicationGraph::graph); + } +} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/controller/HttpExceptionHandler.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/controller/HttpExceptionHandler.java new file mode 100644 index 0000000..9a6fa53 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/controller/HttpExceptionHandler.java @@ -0,0 +1,43 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.controller; + +import io.micrometer.core.instrument.config.validate.ValidationException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeoutException; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.common.Context; +import ru.tinkoff.kora.common.Tag; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.MessageTO; +import ru.tinkoff.kora.http.common.body.HttpBody; +import ru.tinkoff.kora.http.server.common.*; +import ru.tinkoff.kora.json.common.JsonWriter; + +@Tag(HttpServerModule.class) +@Component +public final class HttpExceptionHandler implements HttpServerInterceptor { + + private final JsonWriter errorJsonWriter; + + public HttpExceptionHandler(JsonWriter errorJsonWriter) { + this.errorJsonWriter = errorJsonWriter; + } + + @Override + public CompletionStage intercept(Context context, HttpServerRequest request, InterceptChain chain) + throws Exception { + return chain.process(context, request).exceptionally(e -> { + if (e instanceof HttpServerResponseException ex) { + return ex; + } + + e.printStackTrace(); + var body = HttpBody.json(errorJsonWriter.toByteArrayUnchecked(new MessageTO(e.getMessage()))); + if (e instanceof IllegalArgumentException || e instanceof ValidationException) { + return HttpServerResponse.of(400, body); + } else if (e instanceof TimeoutException) { + return HttpServerResponse.of(408, body); + } else { + return HttpServerResponse.of(500, body); + } + }); + } +} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/controller/PetDelegate.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/controller/PetDelegate.java new file mode 100644 index 0000000..572271d --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/controller/PetDelegate.java @@ -0,0 +1,93 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.controller; + +import static ru.tinkoff.kora.example.graalvm.crud.openapi.server.api.PetApiResponses.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.api.PetApiDelegate; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.MessageTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetUpdateTO; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.mapper.PetMapper; +import ru.tinkoff.kora.example.graalvm.crud.vertx.service.PetService; + +@Component +public final class PetDelegate implements PetApiDelegate { + + private final PetMapper petMapper; + private final PetService petService; + + public PetDelegate(PetMapper petMapper, PetService petService) { + this.petMapper = petMapper; + this.petService = petService; + } + + @Override + public Mono getPetById(long petId) { + if (petId < 0) { + return Mono.just(new GetPetByIdApiResponse.GetPetById400ApiResponse(malformedId(petId))); + } + + return petService.findByID(petId) + .map(pet -> { + var body = petMapper.asDTO(pet); + return ((GetPetByIdApiResponse) new GetPetByIdApiResponse.GetPetById200ApiResponse(body)); + }) + .switchIfEmpty(Mono.fromSupplier(() -> new GetPetByIdApiResponse.GetPetById404ApiResponse(notFound(petId)))); + } + + @Override + public Mono addPet(PetCreateTO petCreateTO) { + Logger logger = LoggerFactory.getLogger(getClass()); + return petService.add(petCreateTO) + .doOnSuccess(r -> { + if (r == null) { + logger.warn("INSERT NULL"); + } else { + logger.warn("INSERT NOT NULL"); + } + }) + .doOnError(e -> { logger.warn("ERROR - " + e); }) + .map(pet -> { + var body = petMapper.asDTO(pet); + return new AddPetApiResponse.AddPet200ApiResponse(body); + }); + } + + @Override + public Mono updatePet(long petId, PetUpdateTO petUpdateTO) { + if (petId < 0) { + return Mono.just(new UpdatePetApiResponse.UpdatePet400ApiResponse(malformedId(petId))); + } + + return petService.update(petId, petUpdateTO) + .map(pet -> { + var body = petMapper.asDTO(pet); + return ((UpdatePetApiResponse) new UpdatePetApiResponse.UpdatePet200ApiResponse(body)); + }) + .switchIfEmpty(Mono.fromSupplier(() -> new UpdatePetApiResponse.UpdatePet404ApiResponse(notFound(petId)))); + } + + @Override + public Mono deletePet(long petId) { + if (petId < 0) { + return Mono.just(new DeletePetApiResponse.DeletePet400ApiResponse(malformedId(petId))); + } + + return petService.delete(petId) + .map(isDeleted -> (isDeleted) + ? new DeletePetApiResponse.DeletePet200ApiResponse( + new MessageTO("Successfully deleted pet with ID: " + petId)) + : new DeletePetApiResponse.DeletePet404ApiResponse(notFound(petId))); + } + + private static MessageTO notFound(long petId) { + return new MessageTO("Pet not found for ID: " + petId); + } + + private static MessageTO malformedId(long petId) { + return new MessageTO("Pet malformed ID: " + petId); + } +} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/dao/Pet.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/dao/Pet.java new file mode 100644 index 0000000..fc8f842 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/dao/Pet.java @@ -0,0 +1,25 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao; + +import ru.tinkoff.kora.database.common.annotation.Column; +import ru.tinkoff.kora.database.common.annotation.Id; +import ru.tinkoff.kora.database.common.annotation.Table; + +@Table("pets") +public record Pet(@Id @Column("id") long id, + @Column("name") String name, + @Column("status") Status status, + @Column("category_id") long categoryId) { + + public enum Status { + + AVAILABLE(0), + PENDING(10), + SOLD(20); + + public final int code; + + Status(int code) { + this.code = code; + } + } +} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/dao/PetCategory.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/dao/PetCategory.java new file mode 100644 index 0000000..0dc285e --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/dao/PetCategory.java @@ -0,0 +1,8 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao; + +import ru.tinkoff.kora.database.common.annotation.Id; +import ru.tinkoff.kora.database.common.annotation.Table; + +@Table("categories") +public record PetCategory(@Id long id, + String name) {} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/dao/PetWithCategory.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/dao/PetWithCategory.java new file mode 100644 index 0000000..7c15099 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/dao/PetWithCategory.java @@ -0,0 +1,14 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao; + +import ru.tinkoff.kora.database.common.annotation.Column; +import ru.tinkoff.kora.database.common.annotation.Embedded; + +public record PetWithCategory(@Column("id") long id, + @Column("name") String name, + @Column("status") Pet.Status status, + @Embedded("category_") PetCategory category) { + + public Pet getPet() { + return new Pet(id, name, status, category.id()); + } +} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/mapper/PetMapper.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/mapper/PetMapper.java new file mode 100644 index 0000000..2372331 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/model/mapper/PetMapper.java @@ -0,0 +1,15 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.model.mapper; + +import org.mapstruct.Mapper; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.CategoryTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetTO; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao.PetCategory; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao.PetWithCategory; + +@Mapper +public interface PetMapper { + + PetTO asDTO(PetWithCategory pet); + + CategoryTO asDTO(PetCategory category); +} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/CategoryRepository.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/CategoryRepository.java new file mode 100644 index 0000000..ca5a28b --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/CategoryRepository.java @@ -0,0 +1,21 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.repository; + +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.database.common.UpdateCount; +import ru.tinkoff.kora.database.common.annotation.Query; +import ru.tinkoff.kora.database.common.annotation.Repository; +import ru.tinkoff.kora.database.vertx.VertxRepository; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao.PetCategory; + +@Repository +public interface CategoryRepository extends VertxRepository { + + @Query("SELECT %{return#selects} FROM %{return#table} WHERE name = :name") + Mono findByName(String name); + + @Query("INSERT INTO categories(name) VALUES (:categoryName) RETURNING id") + Mono insert(String categoryName); + + @Query("DELETE FROM categories WHERE id = :id") + Mono deleteById(long id); +} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/PetRepository.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/PetRepository.java new file mode 100644 index 0000000..e299066 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/PetRepository.java @@ -0,0 +1,30 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.repository; + +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.database.common.UpdateCount; +import ru.tinkoff.kora.database.common.annotation.Query; +import ru.tinkoff.kora.database.common.annotation.Repository; +import ru.tinkoff.kora.database.vertx.VertxRepository; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao.Pet; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao.PetWithCategory; + +@Repository +public interface PetRepository extends VertxRepository { + + @Query(""" + SELECT p.id, p.name, p.status, p.category_id, c.name as category_name + FROM pets p + JOIN categories c on c.id = p.category_id + WHERE p.id = :id + """) + Mono findById(long id); + + @Query("INSERT INTO %{entity#inserts -= id} RETURNING id") + Mono insert(Pet entity); + + @Query("UPDATE %{entity#table} SET %{entity#updates} WHERE %{entity#where = @id}") + Mono update(Pet entity); + + @Query("DELETE FROM pets WHERE id = :id") + Mono deleteById(long id); +} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/mapper/PetStatusParameterMapper.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/mapper/PetStatusParameterMapper.java new file mode 100644 index 0000000..5a273eb --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/mapper/PetStatusParameterMapper.java @@ -0,0 +1,19 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.repository.mapper; + +import jakarta.annotation.Nullable; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.database.vertx.mapper.parameter.VertxParameterColumnMapper; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao.Pet; + +@Component +public final class PetStatusParameterMapper implements VertxParameterColumnMapper { + + @Override + public Object apply(@Nullable Pet.Status value) { + if (value == null) { + return null; + } else { + return value.code; + } + } +} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/mapper/PetStatusResultMapper.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/mapper/PetStatusResultMapper.java new file mode 100644 index 0000000..cdbfa21 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/repository/mapper/PetStatusResultMapper.java @@ -0,0 +1,23 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.repository.mapper; + +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.database.vertx.mapper.result.VertxResultColumnMapper; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao.Pet; + +@Component +public final class PetStatusResultMapper implements VertxResultColumnMapper { + + private final Pet.Status[] statuses = Pet.Status.values(); + + @Override + public Pet.Status apply(io.vertx.sqlclient.Row row, int index) { + final int code = row.get(Integer.class, index); + for (Pet.Status status : statuses) { + if (code == status.code) { + return status; + } + } + + throw new IllegalStateException("Unknown code: " + code); + } +} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/service/PetCache.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/service/PetCache.java new file mode 100644 index 0000000..2c1a4e5 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/service/PetCache.java @@ -0,0 +1,10 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.service; + +import ru.tinkoff.kora.cache.annotation.Cache; +import ru.tinkoff.kora.cache.caffeine.CaffeineCache; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao.PetWithCategory; + +@Cache("pet-cache") +public interface PetCache extends CaffeineCache { + +} diff --git a/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/service/PetService.java b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/service/PetService.java new file mode 100644 index 0000000..c56b721 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/java/ru/tinkoff/kora/example/graalvm/crud/vertx/service/PetService.java @@ -0,0 +1,91 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx.service; + +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.cache.annotation.CacheInvalidate; +import ru.tinkoff.kora.cache.annotation.CachePut; +import ru.tinkoff.kora.cache.annotation.Cacheable; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetUpdateTO; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao.Pet; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao.PetCategory; +import ru.tinkoff.kora.example.graalvm.crud.vertx.model.dao.PetWithCategory; +import ru.tinkoff.kora.example.graalvm.crud.vertx.repository.CategoryRepository; +import ru.tinkoff.kora.example.graalvm.crud.vertx.repository.PetRepository; +import ru.tinkoff.kora.resilient.circuitbreaker.annotation.CircuitBreaker; +import ru.tinkoff.kora.resilient.retry.annotation.Retry; +import ru.tinkoff.kora.resilient.timeout.annotation.Timeout; + +@Component +public class PetService { + + private final PetRepository petRepository; + private final CategoryRepository categoryRepository; + + public PetService(PetRepository petRepository, CategoryRepository categoryRepository) { + this.petRepository = petRepository; + this.categoryRepository = categoryRepository; + } + + @Cacheable(PetCache.class) + @CircuitBreaker("pet") + @Retry("pet") + @Timeout("pet") + public Mono findByID(long petId) { + return petRepository.findById(petId); + } + + @CircuitBreaker("pet") + @Timeout("pet") + public Mono add(PetCreateTO createTO) { + return categoryRepository.findByName(createTO.category().name()) + .map(PetCategory::id) + .switchIfEmpty(categoryRepository.insert(createTO.category().name())) + .flatMap(categoryId -> { + var pet = new Pet(0, createTO.name(), Pet.Status.AVAILABLE, categoryId); + return petRepository.insert(pet) + .map(petId -> new PetWithCategory(petId, pet.name(), pet.status(), + new PetCategory(categoryId, createTO.category().name()))); + }); + } + + @CircuitBreaker("pet") + @Timeout("pet") + @CachePut(value = PetCache.class, parameters = "id") + public Mono update(long id, PetUpdateTO updateTO) { + return petRepository.findById(id) + .flatMap(existingPet -> { + var categoryMono = Mono.just(existingPet.category()); + if (updateTO.category() != null) { + categoryMono = categoryRepository.findByName(updateTO.category().name()) + .switchIfEmpty(Mono.defer(() -> categoryRepository.insert(updateTO.category().name()) + .map(newCategoryId -> new PetCategory(newCategoryId, updateTO.category().name())))); + } + + var status = (updateTO.status() == null) + ? existingPet.status() + : toStatus(updateTO.status()); + + return categoryMono + .flatMap(category -> { + var result = new PetWithCategory(existingPet.id(), updateTO.name(), status, category); + return petRepository.update(result.getPet()).then(Mono.just(result)); + }); + }); + } + + @CircuitBreaker("pet") + @Timeout("pet") + @CacheInvalidate(PetCache.class) + public Mono delete(long petId) { + return petRepository.deleteById(petId).map(counter -> counter.value() == 1); + } + + private static Pet.Status toStatus(PetUpdateTO.StatusEnum statusEnum) { + return switch (statusEnum) { + case AVAILABLE -> Pet.Status.AVAILABLE; + case PENDING -> Pet.Status.PENDING; + case SOLD -> Pet.Status.SOLD; + }; + } +} diff --git a/kora-java-graalvm-crud-vertx/src/main/resources/application.conf b/kora-java-graalvm-crud-vertx/src/main/resources/application.conf new file mode 100644 index 0000000..e13d1a3 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/resources/application.conf @@ -0,0 +1,60 @@ +httpServer { + publicApiHttpPort = 8080 + privateApiHttpPort = 8085 +} + + +db { + connectionUri = ${POSTGRES_VERTX_URL} + username = ${POSTGRES_USER} + password = ${POSTGRES_PASS} + maxPoolSize = 10 + poolName = "kora" + initializationFailTimeout = "10s" +} + + +pet-cache { + maximumSize = 1000 + expireAfterWrite = ${?CACHE_EXPIRE_WRITE} +} + + +openapi { + management { + enabled = true + file = "openapi/http-server.yaml" + swaggerui { + enabled = true + } + rapidoc { + enabled = true + } + } +} + + +resilient { + circuitbreaker.pet { + slidingWindowSize = 50 + minimumRequiredCalls = 25 + failureRateThreshold = 50 + permittedCallsInHalfOpenState = 10 + waitDurationInOpenState = 15s + } + timeout.pet { + duration = 5000ms + } + retry.pet { + delay = 100ms + attempts = 2 + } +} + + +logging.level { + "root": "WARN" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" + "ru.tinkoff.kora.application.graph.internal.loom.VirtualThreadExecutorHolder": "DEBUG" +} diff --git a/kora-java-graalvm-crud-vertx/src/main/resources/db/migration/V1__setup-tables.sql b/kora-java-graalvm-crud-vertx/src/main/resources/db/migration/V1__setup-tables.sql new file mode 100644 index 0000000..3e70499 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/resources/db/migration/V1__setup-tables.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS categories +( + id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY, + name VARCHAR NOT NULL, + PRIMARY KEY (id) +); + + +CREATE TABLE IF NOT EXISTS pets +( + id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY, + name VARCHAR NOT NULL, + status SMALLINT NOT NULL, + category_id BIGINT NOT NULL REFERENCES categories(id), + PRIMARY KEY (id) +); diff --git a/kora-java-graalvm-crud-vertx/src/main/resources/logback.xml b/kora-java-graalvm-crud-vertx/src/main/resources/logback.xml new file mode 100644 index 0000000..745e83f --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n + + + + + + + + + + + + + diff --git a/kora-java-graalvm-crud-vertx/src/main/resources/openapi/http-server.yaml b/kora-java-graalvm-crud-vertx/src/main/resources/openapi/http-server.yaml new file mode 100644 index 0000000..8d99f49 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/main/resources/openapi/http-server.yaml @@ -0,0 +1,315 @@ +openapi: 3.0.3 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.11 +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io +paths: + /v3/pets: + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + required: true + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/PetCreateTO' + responses: + '200': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + /v3/pets/{id}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: id + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + nullable: false + minimum: 1 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + parameters: + - name: id + in: path + description: Pet id to update + required: true + schema: + type: integer + format: int64 + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/PetUpdateTO' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PetTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + delete: + tags: + - pet + summary: Deletes a pet + description: delete a pet + operationId: deletePet + parameters: + - name: id + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '400': + description: Invalid parameters supplier + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '404': + description: Pet not found + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '408': + description: Timeout exception + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '429': + description: Too many requests + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/MessageTO' +components: + schemas: + MessageTO: + type: object + properties: + message: + type: string + CategoryTO: + required: + - id + - name + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + CategoryCreateTO: + required: + - name + type: object + properties: + name: + type: string + example: Dogs + PetStatusTO: + properties: + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + PetTO: + allOf: + - $ref: '#/components/schemas/PetStatusTO' + - type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + example: 10 + nullable: false + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryTO' + PetCreateTO: + required: + - name + - category + type: object + properties: + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryCreateTO' + PetUpdateTO: + allOf: + - $ref: '#/components/schemas/PetStatusTO' + - type: object + required: + - name + properties: + name: + type: string + example: doggie + nullable: false + minLength: 1 + maxLength: 50 + category: + $ref: '#/components/schemas/CategoryCreateTO' diff --git a/kora-java-graalvm-crud-vertx/src/test/java/ru/tinkoff/kora/example/graalvm/crud/vertx/AppContainer.java b/kora-java-graalvm-crud-vertx/src/test/java/ru/tinkoff/kora/example/graalvm/crud/vertx/AppContainer.java new file mode 100644 index 0000000..c002c8d --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/test/java/ru/tinkoff/kora/example/graalvm/crud/vertx/AppContainer.java @@ -0,0 +1,47 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx; + +import java.net.URI; +import java.nio.file.Paths; +import java.time.Duration; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.utility.DockerImageName; + +public final class AppContainer extends GenericContainer { + + private AppContainer() { + super(new ImageFromDockerfile("kora-java-graalvm-crud-r2dbc") + .withDockerfile(Paths.get("Dockerfile").toAbsolutePath())); + } + + private AppContainer(DockerImageName image) { + super(image); + } + + public static AppContainer build() { + final String appImage = System.getenv("IMAGE_KORA_JAVA_GRAALVM_CRUD_R2DBC"); + return (appImage != null && !appImage.isBlank()) + ? new AppContainer(DockerImageName.parse(appImage)) + : new AppContainer(); + } + + @Override + protected void configure() { + super.configure(); + withExposedPorts(8080, 8085); + withStartupTimeout(Duration.ofSeconds(120)); + withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(AppContainer.class))); + waitingFor(Wait.forHttp("/system/readiness").forPort(8085).forStatusCode(200)); + } + + public int getPort() { + return getMappedPort(8080); + } + + public URI getURI() { + return URI.create(String.format("http://%s:%s", getHost(), getPort())); + } +} diff --git a/kora-java-graalvm-crud-vertx/src/test/java/ru/tinkoff/kora/example/graalvm/crud/vertx/PetControllerTests.java b/kora-java-graalvm-crud-vertx/src/test/java/ru/tinkoff/kora/example/graalvm/crud/vertx/PetControllerTests.java new file mode 100644 index 0000000..bed57a8 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/test/java/ru/tinkoff/kora/example/graalvm/crud/vertx/PetControllerTests.java @@ -0,0 +1,210 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx; + +import static org.junit.jupiter.api.Assertions.*; + +import io.goodforgod.testcontainers.extensions.ContainerMode; +import io.goodforgod.testcontainers.extensions.Network; +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; +import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; +import io.goodforgod.testcontainers.extensions.jdbc.Migration; +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Map; +import org.json.JSONObject; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; + +@TestcontainersPostgreSQL( + network = @Network(shared = true), + mode = ContainerMode.PER_RUN, + migration = @Migration( + engine = Migration.Engines.FLYWAY, + apply = Migration.Mode.PER_METHOD, + drop = Migration.Mode.PER_METHOD)) +class PetControllerTests { + + private static final AppContainer container = AppContainer.build() + .withNetwork(org.testcontainers.containers.Network.SHARED); + + @ConnectionPostgreSQL + private JdbcConnection connection; + + @BeforeEach + public void setup(@ConnectionPostgreSQL JdbcConnection connection) { + if (!container.isRunning()) { + var params = connection.paramsInNetwork().orElseThrow(); + container.withEnv(Map.of( + "POSTGRES_VERTX_URL", "postgresql://%s:%s/%s".formatted(params.host(), params.port(), params.database()), + "POSTGRES_USER", params.username(), + "POSTGRES_PASS", params.password(), + "CACHE_EXPIRE_WRITE", "0s")); + + container.start(); + } + } + + @AfterAll + public static void cleanup() { + container.stop(); + } + + @Test + void addPet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var requestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + // when + var request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, response.statusCode(), response.body()); + + // then + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var responseBody = new JSONObject(response.body()); + assertNotNull(responseBody.query("/id")); + assertNotEquals(0L, responseBody.query("/id")); + assertNotNull(responseBody.query("/status")); + assertEquals(requestBody.query("/name"), responseBody.query("/name")); + assertNotNull(responseBody.query("/category/id")); + assertEquals(requestBody.query("/category/name"), responseBody.query("/category/name")); + } + + @Test + void getPet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + // when + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var createResponseBody = new JSONObject(createResponse.body()); + + // then + var getRequest = HttpRequest.newBuilder() + .GET() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var getResponse = httpClient.send(getRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, getResponse.statusCode(), getResponse.body()); + + var getResponseBody = new JSONObject(getResponse.body()); + JSONAssert.assertEquals(createResponseBody.toString(), getResponseBody.toString(), JSONCompareMode.LENIENT); + } + + @Test + void updatePet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var createResponseBody = new JSONObject(createResponse.body()); + + // when + var updateRequestBody = new JSONObject() + .put("name", "doggie2") + .put("status", "pending") + .put("category", new JSONObject() + .put("name", "Dogs2")); + + var updateRequest = HttpRequest.newBuilder() + .PUT(HttpRequest.BodyPublishers.ofString(updateRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var updateResponse = httpClient.send(updateRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, updateResponse.statusCode(), updateResponse.body()); + var updateResponseBody = new JSONObject(updateResponse.body()); + + // then + var getRequest = HttpRequest.newBuilder() + .GET() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var getResponse = httpClient.send(getRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), getResponse.body()); + + var getResponseBody = new JSONObject(getResponse.body()); + JSONAssert.assertEquals(updateResponseBody.toString(), getResponseBody.toString(), JSONCompareMode.LENIENT); + } + + @Test + void deletePet() throws Exception { + // given + var httpClient = HttpClient.newHttpClient(); + var createRequestBody = new JSONObject() + .put("name", "doggie") + .put("category", new JSONObject() + .put("name", "Dogs")); + + var createRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) + .uri(container.getURI().resolve("/v3/pets")) + .timeout(Duration.ofSeconds(5)) + .build(); + + var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, createResponse.statusCode(), createResponse.body()); + connection.assertCountsEquals(1, "pets"); + connection.assertCountsEquals(1, "categories"); + var createResponseBody = new JSONObject(createResponse.body()); + + // when + var deleteRequest = HttpRequest.newBuilder() + .DELETE() + .uri(container.getURI().resolve("/v3/pets/" + createResponseBody.query("/id"))) + .timeout(Duration.ofSeconds(5)) + .build(); + + var deleteResponse = httpClient.send(deleteRequest, HttpResponse.BodyHandlers.ofString()); + assertEquals(200, deleteResponse.statusCode(), deleteResponse.body()); + + // then + connection.assertCountsEquals(0, "pets"); + } +} diff --git a/kora-java-graalvm-crud-vertx/src/test/java/ru/tinkoff/kora/example/graalvm/crud/vertx/PetServiceTests.java b/kora-java-graalvm-crud-vertx/src/test/java/ru/tinkoff/kora/example/graalvm/crud/vertx/PetServiceTests.java new file mode 100644 index 0000000..104fba7 --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/test/java/ru/tinkoff/kora/example/graalvm/crud/vertx/PetServiceTests.java @@ -0,0 +1,127 @@ +package ru.tinkoff.kora.example.graalvm.crud.vertx; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; + +import java.util.Collections; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.CategoryCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetCreateTO; +import ru.tinkoff.kora.example.graalvm.crud.openapi.server.model.PetUpdateTO; +import ru.tinkoff.kora.example.graalvm.crud.vertx.repository.CategoryRepository; +import ru.tinkoff.kora.example.graalvm.crud.vertx.repository.PetRepository; +import ru.tinkoff.kora.example.graalvm.crud.vertx.service.PetCache; +import ru.tinkoff.kora.example.graalvm.crud.vertx.service.PetService; +import ru.tinkoff.kora.test.extension.junit5.KoraAppTest; +import ru.tinkoff.kora.test.extension.junit5.KoraAppTestConfigModifier; +import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; +import ru.tinkoff.kora.test.extension.junit5.TestComponent; + +@KoraAppTest(Application.class) +class PetServiceTests implements KoraAppTestConfigModifier { + + @Mock + @TestComponent + private PetCache petCache; + @Mock + @TestComponent + private PetRepository petRepository; + @Mock + @TestComponent + private CategoryRepository categoryRepository; + + @TestComponent + private PetService petService; + + @NotNull + @Override + public KoraConfigModification config() { + return KoraConfigModification.ofString(""" + resilient { + circuitbreaker.pet { + slidingWindowSize = 2 + minimumRequiredCalls = 2 + failureRateThreshold = 100 + permittedCallsInHalfOpenState = 1 + waitDurationInOpenState = 15s + } + timeout.pet { + duration = 5000ms + } + retry.pet { + delay = 100ms + attempts = 2 + } + } + """); + } + + @Test + void updatePetWithNewCategoryCreated() { + // given + mockCache(); + mockRepository(Map.of("dog", 1L, "cat", 2L)); + + var added = petService.add(new PetCreateTO("dog", new CategoryCreateTO("dog"))).block(); + assertEquals(1, added.id()); + assertEquals(1, added.category().id()); + + // when + Mockito.when(petRepository.findById(anyLong())).thenReturn(Mono.just(added)); + Mockito.when(petRepository.update(any())).thenReturn(Mono.empty()); + var updated = petService.update(added.id(), + new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat", new CategoryCreateTO("cat"))) + .blockOptional(); + assertTrue(updated.isPresent()); + assertEquals(1, updated.get().id()); + assertEquals(2, updated.get().category().id()); + + // then + Mockito.verify(petRepository).insert(any()); + Mockito.verify(categoryRepository, Mockito.times(2)).insert(any()); + } + + @Test + void updatePetWithSameCategory() { + // given + mockCache(); + mockRepository(Map.of("dog", 1L)); + + var added = petService.add(new PetCreateTO("dog", new CategoryCreateTO("dog"))).block(); + assertEquals(1, added.id()); + assertEquals(1, added.category().id()); + + // when + Mockito.when(petRepository.findById(anyLong())).thenReturn(Mono.just(added)); + Mockito.when(petRepository.update(any())).thenReturn(Mono.empty()); + Mockito.when(categoryRepository.findByName(any())).thenReturn(Mono.just(added.category())); + var updated = petService.update(added.id(), + new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat", new CategoryCreateTO("dog"))) + .blockOptional(); + assertTrue(updated.isPresent()); + assertNotEquals(0, updated.get().id()); + assertNotEquals(0, updated.get().category().id()); + + // then + Mockito.verify(petRepository).insert(any()); + Mockito.verify(categoryRepository).insert(any()); + } + + private void mockCache() { + Mockito.when(petCache.get(anyLong())).thenReturn(null); + Mockito.when(petCache.put(anyLong(), any())).then(invocation -> invocation.getArguments()[1]); + Mockito.when(petCache.get(anyCollection())).thenReturn(Collections.emptyMap()); + } + + private void mockRepository(Map categoryNameToId) { + categoryNameToId.forEach((k, v) -> Mockito.when(categoryRepository.insert(k)).thenReturn(Mono.just(v))); + Mockito.when(categoryRepository.findByName(any())).thenReturn(Mono.empty()); + Mockito.when(petRepository.insert(any())).thenReturn(Mono.just(1L)); + Mockito.when(petRepository.findById(anyLong())).thenReturn(Mono.empty()); + } +} diff --git a/kora-java-graalvm-crud-vertx/src/test/resources/logback-test.xml b/kora-java-graalvm-crud-vertx/src/test/resources/logback-test.xml new file mode 100644 index 0000000..adccdab --- /dev/null +++ b/kora-java-graalvm-crud-vertx/src/test/resources/logback-test.xml @@ -0,0 +1,24 @@ + + + + + + UTF-8 + %cyan(%d{HH:mm:ss.SSS}) %highlight(%-5level) [%thread] %logger{36} - %msg%n + + + + + + + + + + + + + + + + + diff --git a/kora-java-graalvm-kafka/Dockerfile b/kora-java-graalvm-kafka/Dockerfile new file mode 100644 index 0000000..de40743 --- /dev/null +++ b/kora-java-graalvm-kafka/Dockerfile @@ -0,0 +1,25 @@ +FROM ghcr.io/graalvm/native-image-community:21 as builder + +ARG APP_DIR=/opt/application +ARG JAR_DIR=build/libs +WORKDIR $APP_DIR + +ADD $JAR_DIR/*.jar $APP_DIR/application.jar + +RUN native-image --no-fallback -classpath $APP_DIR/application.jar + +FROM ubuntu:noble-20240212 as runner + +ARG APP_DIR=/opt/application +WORKDIR $APP_DIR + +COPY --from=builder $APP_DIR/application $APP_DIR/application + +ARG DOCKER_USER=app +RUN groupadd -r $DOCKER_USER && useradd -rg $DOCKER_USER $DOCKER_USER +RUN chmod +x application +USER $DOCKER_USER + +EXPOSE 8080/tcp +EXPOSE 8085/tcp +CMD "/opt/application/application" \ No newline at end of file diff --git a/kora-java-graalvm-kafka/README.md b/kora-java-graalvm-kafka/README.md new file mode 100644 index 0000000..c85ab92 --- /dev/null +++ b/kora-java-graalvm-kafka/README.md @@ -0,0 +1,34 @@ +# Kora Java GraalVM Kafka + +Пример модуля Kafka в Kora. + +В примере использовались модули: +- [Kafka](https://kora-projects.github.io/kora-docs/ru/documentation/kafka/) +- [JSON](https://kora-projects.github.io/kora-docs/ru/documentation/json/) + +Скомпилирован с помощью [GraalVM](https://www.graalvm.org/release-notes/JDK_21/) + +## Build + +Собрать артефакт: + +```shell +./gradlew shadowJar +docker build -t kora-java-graalvm-kafka . +``` + +## Run + +Запустить локально: +```shell +./gradlew run +``` + +## Test + +Тесты используют [Testcontainers](https://java.testcontainers.org/), требуется [Docker](https://docs.docker.com/engine/install/) окружение для запуска тестов или аналогичные контейнерные окружения ([colima](https://github.com/abiosoft/colima) / итп) + +Протестировать локально: +```shell +./gradlew test +``` diff --git a/kora-java-graalvm-kafka/build.gradle b/kora-java-graalvm-kafka/build.gradle new file mode 100644 index 0000000..13d2083 --- /dev/null +++ b/kora-java-graalvm-kafka/build.gradle @@ -0,0 +1,137 @@ +plugins { + id "java" + id "jacoco" + id "application" + + id "com.github.johnrengelman.shadow" version "8.1.1" + id "org.graalvm.buildtools.native" version "0.10.1" +} + +repositories { + mavenLocal() + mavenCentral() +} + +mainClassName = "ru.tinkoff.kora.example.graalvm.kafka.Application" + +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 + +configurations { + koraBom + implementation.extendsFrom(koraBom) + annotationProcessor.extendsFrom(koraBom) +} + +dependencies { + koraBom platform("ru.tinkoff.kora:kora-parent:$koraVersion") + annotationProcessor "ru.tinkoff.kora:annotation-processors" + annotationProcessor "io.goodforgod:graalvm-hint-processor:1.2.0" + compileOnly "io.goodforgod:graalvm-hint-annotations:1.2.0" + + implementation "ru.tinkoff.kora:http-server-undertow" + implementation "ru.tinkoff.kora:kafka" + implementation "ru.tinkoff.kora:json-module" + implementation "ru.tinkoff.kora:config-yaml" + implementation "ru.tinkoff.kora:micrometer-module" + implementation "ru.tinkoff.kora:logging-logback" + + testImplementation "org.json:json:20231013" + testImplementation "org.mockito:mockito-core:5.6.0" + testImplementation "ru.tinkoff.kora:test-junit5" + testImplementation "io.goodforgod:testcontainers-extensions-kafka:0.11.0" + testImplementation "org.testcontainers:junit-jupiter:1.17.6" +} + +graalvmNative { + binaries { + main { + imageName = "$project.name" + mainClass = "$mainClassName" + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(21) + vendor = JvmVendorSpec.matching("GraalVM Community") + } + } + } + metadataRepository { + enabled = true + } +} + +processResources.dependsOn tasks.collectReachabilityMetadata +test.dependsOn tasks.shadowJar + +//noinspection GroovyAssignabilityCheck +run { + environment([ + "KAFKA_BOOTSTRAP": "$kafkaBootstrapServers" + ]) +} + +test { + jvmArgs += [ + "-XX:+TieredCompilation", + "-XX:TieredStopAtLevel=1", + ] + + environment([ + "": "" + ]) + + useJUnitPlatform() + testLogging { + showStandardStreams(true) + events("passed", "skipped", "failed") + exceptionFormat("full") + } + + jacoco { + excludes += ["**/Application*"] + } + + reports { + html.required = false + junitXml.required = false + } +} + +sourceSets { + main { + resources.srcDirs += "$buildDir/native-reachability-metadata" + } +} + +jar.enabled = false +shadowJar { + mergeServiceFiles() + manifest { + attributes "Main-Class": mainClassName + attributes "Implementation-Version": koraVersion + } +} + +artifacts { + archives shadowJar +} + +compileJava { + options.encoding("UTF-8") + options.incremental(true) + options.fork = true +} + +check.dependsOn jacocoTestReport +jacocoTestReport { + reports { + xml.required = true + html.outputLocation = layout.buildDirectory.dir("jacocoHtml") + } +} + +javadoc { + options.encoding = "UTF-8" + if (JavaVersion.current().isJava9Compatible()) { + options.addBooleanOption("html5", true) + } +} \ No newline at end of file diff --git a/kora-java-graalvm-kafka/gradle.properties b/kora-java-graalvm-kafka/gradle.properties new file mode 100644 index 0000000..71fdfaa --- /dev/null +++ b/kora-java-graalvm-kafka/gradle.properties @@ -0,0 +1,2 @@ +##### KAFKA ##### +kafkaBootstrapServers=localhost:9092 diff --git a/kora-java-graalvm-kafka/src/main/java/ru/tinkoff/kora/example/graalvm/kafka/Application.java b/kora-java-graalvm-kafka/src/main/java/ru/tinkoff/kora/example/graalvm/kafka/Application.java new file mode 100644 index 0000000..81ceab8 --- /dev/null +++ b/kora-java-graalvm-kafka/src/main/java/ru/tinkoff/kora/example/graalvm/kafka/Application.java @@ -0,0 +1,26 @@ +package ru.tinkoff.kora.example.graalvm.kafka; + +import io.goodforgod.graalvm.hint.annotation.NativeImageHint; +import ru.tinkoff.kora.application.graph.KoraApplication; +import ru.tinkoff.kora.common.KoraApp; +import ru.tinkoff.kora.config.yaml.YamlConfigModule; +import ru.tinkoff.kora.http.server.undertow.UndertowModule; +import ru.tinkoff.kora.json.module.JsonModule; +import ru.tinkoff.kora.kafka.common.KafkaModule; +import ru.tinkoff.kora.logging.logback.LogbackModule; +import ru.tinkoff.kora.micrometer.module.MetricsModule; + +@NativeImageHint(name = "application", entrypoint = Application.class) +@KoraApp +public interface Application extends + YamlConfigModule, + LogbackModule, + JsonModule, + UndertowModule, + KafkaModule, + MetricsModule { + + static void main(String[] args) { + KoraApplication.run(ApplicationGraph::graph); + } +} diff --git a/kora-java-graalvm-kafka/src/main/java/ru/tinkoff/kora/example/graalvm/kafka/TaskPublisher.java b/kora-java-graalvm-kafka/src/main/java/ru/tinkoff/kora/example/graalvm/kafka/TaskPublisher.java new file mode 100644 index 0000000..30240f0 --- /dev/null +++ b/kora-java-graalvm-kafka/src/main/java/ru/tinkoff/kora/example/graalvm/kafka/TaskPublisher.java @@ -0,0 +1,14 @@ +package ru.tinkoff.kora.example.graalvm.kafka; + +import ru.tinkoff.kora.json.common.annotation.Json; +import ru.tinkoff.kora.kafka.common.annotation.KafkaPublisher; + +@KafkaPublisher("kafka.publisher.task") +public interface TaskPublisher { + + @Json + record Task(String name, long code) {} + + @KafkaPublisher.Topic("kafka.publisher.task") + void send(@Json TaskPublisher.Task value); +} diff --git a/kora-java-graalvm-kafka/src/main/java/ru/tinkoff/kora/example/graalvm/kafka/UserListener.java b/kora-java-graalvm-kafka/src/main/java/ru/tinkoff/kora/example/graalvm/kafka/UserListener.java new file mode 100644 index 0000000..ed8a7ac --- /dev/null +++ b/kora-java-graalvm-kafka/src/main/java/ru/tinkoff/kora/example/graalvm/kafka/UserListener.java @@ -0,0 +1,24 @@ +package ru.tinkoff.kora.example.graalvm.kafka; + +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.json.common.annotation.Json; +import ru.tinkoff.kora.kafka.common.annotation.KafkaListener; + +@Component +public final class UserListener { + + @Json + public record User(String id, String name) {} + + private final TaskPublisher taskPublisher; + + public UserListener(TaskPublisher taskPublisher) { + this.taskPublisher = taskPublisher; + } + + @KafkaListener("kafka.listener.user") + void process(@Json UserListener.User value) { + long code = System.currentTimeMillis(); + taskPublisher.send(new TaskPublisher.Task(value.name() + "-" + code, code)); + } +} diff --git a/kora-java-graalvm-kafka/src/main/resources/application.yaml b/kora-java-graalvm-kafka/src/main/resources/application.yaml new file mode 100644 index 0000000..528539f --- /dev/null +++ b/kora-java-graalvm-kafka/src/main/resources/application.yaml @@ -0,0 +1,24 @@ +kafka: + publisher: + task: + topic: "tasks" + driverProperties: + bootstrap: + servers: ${KAFKA_BOOTSTRAP} + listener: + user: + pollTimeout: 250ms + topics: "users" + driverProperties: + bootstrap.servers: ${KAFKA_BOOTSTRAP} + group.id: "users-gi" + auto.offset.reset: "latest" + enable.auto.commit: true + + +logging: + level: + root: "WARN" + ru.tinkoff.kora: "INFO" + ru.tinkoff.kora.example: "INFO" + ru.tinkoff.kora.application.graph.internal.loom.VirtualThreadExecutorHolder: "DEBUG" \ No newline at end of file diff --git a/kora-java-graalvm-kafka/src/main/resources/logback.xml b/kora-java-graalvm-kafka/src/main/resources/logback.xml new file mode 100644 index 0000000..745e83f --- /dev/null +++ b/kora-java-graalvm-kafka/src/main/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n + + + + + + + + + + + + + diff --git a/kora-java-graalvm-kafka/src/test/java/ru/tinkoff/kora/example/graalvm/kafka/AppContainer.java b/kora-java-graalvm-kafka/src/test/java/ru/tinkoff/kora/example/graalvm/kafka/AppContainer.java new file mode 100644 index 0000000..740be19 --- /dev/null +++ b/kora-java-graalvm-kafka/src/test/java/ru/tinkoff/kora/example/graalvm/kafka/AppContainer.java @@ -0,0 +1,47 @@ +package ru.tinkoff.kora.example.graalvm.kafka; + +import java.net.URI; +import java.nio.file.Paths; +import java.time.Duration; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.utility.DockerImageName; + +public final class AppContainer extends GenericContainer { + + private AppContainer() { + super(new ImageFromDockerfile("kora-java-graalvm-kafka") + .withDockerfile(Paths.get("Dockerfile").toAbsolutePath())); + } + + private AppContainer(DockerImageName image) { + super(image); + } + + public static AppContainer build() { + final String appImage = System.getenv("IMAGE_KORA_JAVA_GRAALVM_KAFKA"); + return (appImage != null && !appImage.isBlank()) + ? new AppContainer(DockerImageName.parse(appImage)) + : new AppContainer(); + } + + @Override + protected void configure() { + super.configure(); + withExposedPorts(8080, 8085); + withStartupTimeout(Duration.ofSeconds(120)); + withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(AppContainer.class))); + waitingFor(Wait.forHttp("/system/readiness").forPort(8085).forStatusCode(200)); + } + + public int getPort() { + return getMappedPort(8080); + } + + public URI getURI() { + return URI.create(String.format("http://%s:%s", getHost(), getPort())); + } +} diff --git a/kora-java-graalvm-kafka/src/test/java/ru/tinkoff/kora/example/graalvm/kafka/UserListenerTests.java b/kora-java-graalvm-kafka/src/test/java/ru/tinkoff/kora/example/graalvm/kafka/UserListenerTests.java new file mode 100644 index 0000000..0727ae9 --- /dev/null +++ b/kora-java-graalvm-kafka/src/test/java/ru/tinkoff/kora/example/graalvm/kafka/UserListenerTests.java @@ -0,0 +1,46 @@ +package ru.tinkoff.kora.example.graalvm.kafka; + +import io.goodforgod.testcontainers.extensions.ContainerMode; +import io.goodforgod.testcontainers.extensions.Network; +import io.goodforgod.testcontainers.extensions.kafka.*; +import java.time.Duration; +import java.util.Map; +import java.util.UUID; +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +@TestcontainersKafka( + network = @Network(shared = true), + mode = ContainerMode.PER_RUN, + topics = @Topics({ "tasks", "users" })) +class UserListenerTests { + + private static final AppContainer container = AppContainer.build() + .withNetwork(org.testcontainers.containers.Network.SHARED); + + @ConnectionKafka + private KafkaConnection connection; + + @BeforeAll + public static void setup(@ConnectionKafka KafkaConnection connection) { + var params = connection.paramsInNetwork().orElseThrow(); + container.withEnv(Map.of("KAFKA_BOOTSTRAP", params.bootstrapServers())); + container.start(); + } + + @Test + void userEventReceivedAndTaskEventSent() { + // given + var topicUsers = "users"; + var topicTasks = "tasks"; + var event = new JSONObject().put("id", UUID.randomUUID().toString()).put("name", "Ivan"); + + // when + var consumerTask = connection.subscribe(topicTasks); + connection.send(topicUsers, Event.ofValueAndRandomKey(event)); + + // then + consumerTask.assertReceivedEqualsInTime(1, Duration.ofSeconds(20)); + } +} diff --git a/kora-java-graalvm-kafka/src/test/resources/logback-test.xml b/kora-java-graalvm-kafka/src/test/resources/logback-test.xml new file mode 100644 index 0000000..adccdab --- /dev/null +++ b/kora-java-graalvm-kafka/src/test/resources/logback-test.xml @@ -0,0 +1,24 @@ + + + + + + UTF-8 + %cyan(%d{HH:mm:ss.SSS}) %highlight(%-5level) [%thread] %logger{36} - %msg%n + + + + + + + + + + + + + + + + + diff --git a/kora-java-grpc-client/build.gradle b/kora-java-grpc-client/build.gradle index 3b47b9c..01692a3 100644 --- a/kora-java-grpc-client/build.gradle +++ b/kora-java-grpc-client/build.gradle @@ -95,13 +95,12 @@ sourceSets { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-grpc-client/src/main/resources/application.conf b/kora-java-grpc-client/src/main/resources/application.conf index ae7d490..1beb554 100644 --- a/kora-java-grpc-client/src/main/resources/application.conf +++ b/kora-java-grpc-client/src/main/resources/application.conf @@ -7,6 +7,6 @@ grpcClient { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-grpc-client/src/main/resources/logback.xml b/kora-java-grpc-client/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-grpc-client/src/main/resources/logback.xml +++ b/kora-java-grpc-client/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-grpc-client/src/test/resources/logback-test.xml b/kora-java-grpc-client/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-grpc-client/src/test/resources/logback-test.xml +++ b/kora-java-grpc-client/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-grpc-server/build.gradle b/kora-java-grpc-server/build.gradle index 544a3c4..9ad7a0f 100644 --- a/kora-java-grpc-server/build.gradle +++ b/kora-java-grpc-server/build.gradle @@ -96,13 +96,12 @@ sourceSets { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-grpc-server/src/main/resources/application.conf b/kora-java-grpc-server/src/main/resources/application.conf index 011482b..e502497 100644 --- a/kora-java-grpc-server/src/main/resources/application.conf +++ b/kora-java-grpc-server/src/main/resources/application.conf @@ -5,6 +5,6 @@ grpcServer { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-grpc-server/src/main/resources/logback.xml b/kora-java-grpc-server/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-grpc-server/src/main/resources/logback.xml +++ b/kora-java-grpc-server/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-grpc-server/src/test/resources/logback-test.xml b/kora-java-grpc-server/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-grpc-server/src/test/resources/logback-test.xml +++ b/kora-java-grpc-server/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-helloworld/build.gradle b/kora-java-helloworld/build.gradle index 76853fe..0e0b2c0 100644 --- a/kora-java-helloworld/build.gradle +++ b/kora-java-helloworld/build.gradle @@ -74,13 +74,12 @@ test { test.dependsOn tasks.shadowJar -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-helloworld/src/main/resources/application.conf b/kora-java-helloworld/src/main/resources/application.conf index dc5f2e0..2828793 100644 --- a/kora-java-helloworld/src/main/resources/application.conf +++ b/kora-java-helloworld/src/main/resources/application.conf @@ -6,6 +6,6 @@ httpServer { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-helloworld/src/main/resources/logback.xml b/kora-java-helloworld/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-helloworld/src/main/resources/logback.xml +++ b/kora-java-helloworld/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-helloworld/src/test/java/ru/tinkoff/kora/example/helloworld/AppContainer.java b/kora-java-helloworld/src/test/java/ru/tinkoff/kora/example/helloworld/AppContainer.java index 196a748..311c01f 100644 --- a/kora-java-helloworld/src/test/java/ru/tinkoff/kora/example/helloworld/AppContainer.java +++ b/kora-java-helloworld/src/test/java/ru/tinkoff/kora/example/helloworld/AppContainer.java @@ -2,6 +2,7 @@ import java.net.URI; import java.nio.file.Paths; +import java.time.Duration; import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; @@ -31,6 +32,7 @@ public static AppContainer build() { protected void configure() { super.configure(); withExposedPorts(8080, 8085); + withStartupTimeout(Duration.ofSeconds(120)); withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(AppContainer.class))); waitingFor(Wait.forHttp("/system/readiness").forPort(8085).forStatusCode(200)); } diff --git a/kora-java-helloworld/src/test/resources/logback-test.xml b/kora-java-helloworld/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-helloworld/src/test/resources/logback-test.xml +++ b/kora-java-helloworld/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-http-client/build.gradle b/kora-java-http-client/build.gradle index 94e924d..a72afd9 100644 --- a/kora-java-http-client/build.gradle +++ b/kora-java-http-client/build.gradle @@ -28,13 +28,13 @@ dependencies { implementation "ru.tinkoff.kora:http-client-jdk" implementation "ru.tinkoff.kora:json-module" - implementation "io.projectreactor:reactor-core:3.5.10" // For reactive examples (optional) + implementation "io.projectreactor:reactor-core:3.6.3" // For reactive examples (optional) implementation "ru.tinkoff.kora:logging-logback" implementation "ru.tinkoff.kora:config-hocon" testImplementation "ru.tinkoff.kora:test-junit5" - testImplementation "io.goodforgod:testcontainers-extensions-mockserver:0.9.6" + testImplementation "io.goodforgod:testcontainers-extensions-mockserver:0.11.0" } test.dependsOn tasks.shadowJar @@ -73,13 +73,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-http-client/src/main/resources/application.conf b/kora-java-http-client/src/main/resources/application.conf index d80236d..e2853bc 100644 --- a/kora-java-http-client/src/main/resources/application.conf +++ b/kora-java-http-client/src/main/resources/application.conf @@ -9,6 +9,6 @@ httpClient.default { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-http-client/src/main/resources/logback.xml b/kora-java-http-client/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-http-client/src/main/resources/logback.xml +++ b/kora-java-http-client/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/FormHttpClientTests.java b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/FormHttpClientTests.java index c694500..00512f0 100644 --- a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/FormHttpClientTests.java +++ b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/FormHttpClientTests.java @@ -3,9 +3,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.mockserver.ContainerMockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.MockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockserver; +import io.goodforgod.testcontainers.extensions.mockserver.ConnectionMockServer; +import io.goodforgod.testcontainers.extensions.mockserver.MockServerConnection; +import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockServer; import java.nio.charset.StandardCharsets; import java.util.List; import org.jetbrains.annotations.NotNull; @@ -22,12 +22,12 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersMockserver(mode = ContainerMode.PER_RUN) +@TestcontainersMockServer(mode = ContainerMode.PER_RUN) @KoraAppTest(Application.class) class FormHttpClientTests implements KoraAppTestConfigModifier { - @ContainerMockserverConnection - private MockserverConnection mockserverConnection; + @ConnectionMockServer + private MockServerConnection mockserverConnection; @NotNull @Override @@ -46,14 +46,14 @@ void formEncodedHttpClient() { .withMethod("POST") .withHeader("content-type", "application/x-www-form-urlencoded") .withPath("/form/encoded") - .withBody(new StringBody("password=12345&name=Bob"))) + .withBody(new StringBody("password=12345&name=Ivan"))) .respond( org.mockserver.model.HttpResponse.response() .withBody("OK")); // then var response = httpClient.formEncoded(new FormUrlEncoded( - new FormUrlEncoded.FormPart("name", "Bob"), + new FormUrlEncoded.FormPart("name", "Ivan"), new FormUrlEncoded.FormPart("password", "12345"))); assertEquals(200, response.code()); diff --git a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/InterceptedHttpClientTests.java b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/InterceptedHttpClientTests.java index ca7d51a..3385ed9 100644 --- a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/InterceptedHttpClientTests.java +++ b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/InterceptedHttpClientTests.java @@ -3,9 +3,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.mockserver.ContainerMockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.MockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockserver; +import io.goodforgod.testcontainers.extensions.mockserver.ConnectionMockServer; +import io.goodforgod.testcontainers.extensions.mockserver.MockServerConnection; +import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockServer; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import ru.tinkoff.kora.test.extension.junit5.KoraAppTest; @@ -13,12 +13,12 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersMockserver(mode = ContainerMode.PER_RUN) +@TestcontainersMockServer(mode = ContainerMode.PER_RUN) @KoraAppTest(Application.class) class InterceptedHttpClientTests implements KoraAppTestConfigModifier { - @ContainerMockserverConnection - private MockserverConnection mockserverConnection; + @ConnectionMockServer + private MockServerConnection mockserverConnection; @NotNull @Override diff --git a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/JsonHttpClientTests.java b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/JsonHttpClientTests.java index c552480..fbfcca0 100644 --- a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/JsonHttpClientTests.java +++ b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/JsonHttpClientTests.java @@ -3,9 +3,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.mockserver.ContainerMockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.MockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockserver; +import io.goodforgod.testcontainers.extensions.mockserver.ConnectionMockServer; +import io.goodforgod.testcontainers.extensions.mockserver.MockServerConnection; +import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockServer; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.mockserver.model.StringBody; @@ -14,12 +14,12 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersMockserver(mode = ContainerMode.PER_RUN) +@TestcontainersMockServer(mode = ContainerMode.PER_RUN) @KoraAppTest(Application.class) class JsonHttpClientTests implements KoraAppTestConfigModifier { - @ContainerMockserverConnection - private MockserverConnection mockserverConnection; + @ConnectionMockServer + private MockServerConnection mockserverConnection; @NotNull @Override @@ -40,11 +40,11 @@ void jsonHttpClient() { .withBody(new StringBody("{\"id\":\"1\"}"))) .respond( org.mockserver.model.HttpResponse.response() - .withBody("{\"name\":\"Bob\",\"value\":100}")); + .withBody("{\"name\":\"Ivan\",\"value\":100}")); // then var response = httpClient.post(new JsonHttpClient.JsonRequest("1")); - assertEquals("Bob", response.name()); + assertEquals("Ivan", response.name()); assertEquals(100, response.value()); } } diff --git a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/MapperRequestHttpClientTests.java b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/MapperRequestHttpClientTests.java index d1f7560..043f3c2 100644 --- a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/MapperRequestHttpClientTests.java +++ b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/MapperRequestHttpClientTests.java @@ -4,9 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.mockserver.ContainerMockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.MockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockserver; +import io.goodforgod.testcontainers.extensions.mockserver.ConnectionMockServer; +import io.goodforgod.testcontainers.extensions.mockserver.MockServerConnection; +import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockServer; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import ru.tinkoff.kora.test.extension.junit5.KoraAppTest; @@ -14,12 +14,12 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersMockserver(mode = ContainerMode.PER_RUN) +@TestcontainersMockServer(mode = ContainerMode.PER_RUN) @KoraAppTest(Application.class) class MapperRequestHttpClientTests implements KoraAppTestConfigModifier { - @ContainerMockserverConnection - private MockserverConnection mockserverConnection; + @ConnectionMockServer + private MockServerConnection mockserverConnection; @NotNull @Override diff --git a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/MapperResponseHttpClientTests.java b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/MapperResponseHttpClientTests.java index f45e4c5..b2d53a3 100644 --- a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/MapperResponseHttpClientTests.java +++ b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/MapperResponseHttpClientTests.java @@ -3,9 +3,9 @@ import static org.junit.jupiter.api.Assertions.*; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.mockserver.ContainerMockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.MockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockserver; +import io.goodforgod.testcontainers.extensions.mockserver.ConnectionMockServer; +import io.goodforgod.testcontainers.extensions.mockserver.MockServerConnection; +import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockServer; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import ru.tinkoff.kora.test.extension.junit5.KoraAppTest; @@ -13,12 +13,12 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersMockserver(mode = ContainerMode.PER_RUN) +@TestcontainersMockServer(mode = ContainerMode.PER_RUN) @KoraAppTest(Application.class) class MapperResponseHttpClientTests implements KoraAppTestConfigModifier { - @ContainerMockserverConnection - private MockserverConnection mockserverConnection; + @ConnectionMockServer + private MockServerConnection mockserverConnection; @NotNull @Override diff --git a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/ParametersHttpClientTests.java b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/ParametersHttpClientTests.java index 44b60e5..f7a535b 100644 --- a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/ParametersHttpClientTests.java +++ b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/ParametersHttpClientTests.java @@ -3,9 +3,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.mockserver.ContainerMockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.MockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockserver; +import io.goodforgod.testcontainers.extensions.mockserver.ConnectionMockServer; +import io.goodforgod.testcontainers.extensions.mockserver.MockServerConnection; +import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockServer; import java.util.List; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -14,12 +14,12 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersMockserver(mode = ContainerMode.PER_RUN) +@TestcontainersMockServer(mode = ContainerMode.PER_RUN) @KoraAppTest(Application.class) class ParametersHttpClientTests implements KoraAppTestConfigModifier { - @ContainerMockserverConnection - private MockserverConnection mockserverConnection; + @ConnectionMockServer + private MockServerConnection mockserverConnection; @NotNull @Override diff --git a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/ReactorHttpClientTests.java b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/ReactorHttpClientTests.java index 44d66e3..89fcc71 100644 --- a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/ReactorHttpClientTests.java +++ b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/ReactorHttpClientTests.java @@ -4,9 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.mockserver.ContainerMockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.MockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockserver; +import io.goodforgod.testcontainers.extensions.mockserver.ConnectionMockServer; +import io.goodforgod.testcontainers.extensions.mockserver.MockServerConnection; +import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockServer; import java.nio.charset.StandardCharsets; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -15,12 +15,12 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersMockserver(mode = ContainerMode.PER_RUN) +@TestcontainersMockServer(mode = ContainerMode.PER_RUN) @KoraAppTest(Application.class) class ReactorHttpClientTests implements KoraAppTestConfigModifier { - @ContainerMockserverConnection - private MockserverConnection mockserverConnection; + @ConnectionMockServer + private MockServerConnection mockserverConnection; @NotNull @Override diff --git a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/VoidHttpClientTests.java b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/VoidHttpClientTests.java index e28bad6..684e797 100644 --- a/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/VoidHttpClientTests.java +++ b/kora-java-http-client/src/test/java/ru/tinkoff/kora/example/http/client/VoidHttpClientTests.java @@ -1,9 +1,9 @@ package ru.tinkoff.kora.example.http.client; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.mockserver.ContainerMockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.MockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockserver; +import io.goodforgod.testcontainers.extensions.mockserver.ConnectionMockServer; +import io.goodforgod.testcontainers.extensions.mockserver.MockServerConnection; +import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockServer; import java.time.Duration; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -12,12 +12,12 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersMockserver(mode = ContainerMode.PER_RUN) +@TestcontainersMockServer(mode = ContainerMode.PER_RUN) @KoraAppTest(Application.class) class VoidHttpClientTests implements KoraAppTestConfigModifier { - @ContainerMockserverConnection - private MockserverConnection mockserverConnection; + @ConnectionMockServer + private MockServerConnection mockserverConnection; @NotNull @Override diff --git a/kora-java-http-client/src/test/resources/logback-test.xml b/kora-java-http-client/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-http-client/src/test/resources/logback-test.xml +++ b/kora-java-http-client/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-http-server/build.gradle b/kora-java-http-server/build.gradle index 5b58c1f..681ceea 100644 --- a/kora-java-http-server/build.gradle +++ b/kora-java-http-server/build.gradle @@ -28,7 +28,7 @@ dependencies { implementation "ru.tinkoff.kora:http-server-undertow" implementation "ru.tinkoff.kora:json-module" - implementation "io.projectreactor:reactor-core:3.5.10" // For reactive examples (optional) + implementation "io.projectreactor:reactor-core:3.6.3" // For reactive examples (optional) implementation "ru.tinkoff.kora:logging-logback" implementation "ru.tinkoff.kora:config-hocon" @@ -68,13 +68,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-http-server/src/main/resources/application.conf b/kora-java-http-server/src/main/resources/application.conf index dc5f2e0..2828793 100644 --- a/kora-java-http-server/src/main/resources/application.conf +++ b/kora-java-http-server/src/main/resources/application.conf @@ -6,6 +6,6 @@ httpServer { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-http-server/src/main/resources/logback.xml b/kora-java-http-server/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-http-server/src/main/resources/logback.xml +++ b/kora-java-http-server/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-http-server/src/test/java/ru/tinkoff/kora/example/httpserver/InterceptedControllerTests.java b/kora-java-http-server/src/test/java/ru/tinkoff/kora/example/httpserver/InterceptedControllerTests.java index a74f31a..52a3310 100644 --- a/kora-java-http-server/src/test/java/ru/tinkoff/kora/example/httpserver/InterceptedControllerTests.java +++ b/kora-java-http-server/src/test/java/ru/tinkoff/kora/example/httpserver/InterceptedControllerTests.java @@ -38,7 +38,8 @@ void interceptedController() throws Exception { // then var interceptedLogs = Awaitility.await() .atMost(Duration.ofSeconds(30)) - .until(() -> container.getLogs().split("\n"), logs -> logs.length >= 9); + .until(() -> container.getLogs().split("\n"), + logs -> Arrays.stream(logs).anyMatch(log -> log.endsWith("Method Level Interceptor"))); assertTrue(Arrays.stream(interceptedLogs).anyMatch(log -> log.endsWith("Server Level Interceptor"))); assertTrue(Arrays.stream(interceptedLogs).anyMatch(log -> log.endsWith("Controller Level Interceptor"))); assertTrue(Arrays.stream(interceptedLogs).anyMatch(log -> log.endsWith("Method Level Interceptor"))); diff --git a/kora-java-http-server/src/test/java/ru/tinkoff/kora/example/httpserver/JsonPostControllerTests.java b/kora-java-http-server/src/test/java/ru/tinkoff/kora/example/httpserver/JsonPostControllerTests.java index 688421c..043054d 100644 --- a/kora-java-http-server/src/test/java/ru/tinkoff/kora/example/httpserver/JsonPostControllerTests.java +++ b/kora-java-http-server/src/test/java/ru/tinkoff/kora/example/httpserver/JsonPostControllerTests.java @@ -23,13 +23,13 @@ void jsonPostController() throws Exception { // then var request = HttpRequest.newBuilder() - .POST(HttpRequest.BodyPublishers.ofString("{\"name\":\"Bob\"}")) + .POST(HttpRequest.BodyPublishers.ofString("{\"name\":\"Ivan\"}")) .uri(container.getURI().resolve("/json")) .timeout(Duration.ofSeconds(5)) .build(); var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); assertEquals(200, response.statusCode(), response.body()); - assertEquals("Hello world: Bob", response.body(), response.body()); + assertEquals("Hello world: Ivan", response.body(), response.body()); } } diff --git a/kora-java-http-server/src/test/resources/logback-test.xml b/kora-java-http-server/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-http-server/src/test/resources/logback-test.xml +++ b/kora-java-http-server/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-kafka/build.gradle b/kora-java-kafka/build.gradle index d3523d6..4d909dc 100644 --- a/kora-java-kafka/build.gradle +++ b/kora-java-kafka/build.gradle @@ -34,7 +34,7 @@ dependencies { implementation "ru.tinkoff.kora:config-hocon" testImplementation "ru.tinkoff.kora:test-junit5" - testImplementation "io.goodforgod:testcontainers-extensions-kafka:0.9.6" + testImplementation "io.goodforgod:testcontainers-extensions-kafka:0.11.0" } //noinspection GroovyAssignabilityCheck @@ -71,13 +71,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-kafka/src/main/resources/application.conf b/kora-java-kafka/src/main/resources/application.conf index 58b1bb8..2710c0d 100644 --- a/kora-java-kafka/src/main/resources/application.conf +++ b/kora-java-kafka/src/main/resources/application.conf @@ -30,6 +30,6 @@ kafka { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-kafka/src/main/resources/logback.xml b/kora-java-kafka/src/main/resources/logback.xml index 9bc40f3..27d7854 100644 --- a/kora-java-kafka/src/main/resources/logback.xml +++ b/kora-java-kafka/src/main/resources/logback.xml @@ -16,6 +16,8 @@ + + diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordExceptionListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordExceptionListenerTests.java index 6bb20b7..679708e 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordExceptionListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordExceptionListenerTests.java @@ -5,7 +5,6 @@ import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.kafka.*; import java.time.Duration; -import java.util.Timer; import java.util.concurrent.Executors; import org.jetbrains.annotations.NotNull; import org.json.JSONObject; @@ -23,7 +22,7 @@ @KoraAppTest(Application.class) class AutoCommitRecordExceptionListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitRecordExceptionListenerModule.AutoCommitRecordExceptionListenerProcessTag.class) @@ -46,7 +45,7 @@ void processed() throws InterruptedException { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordJsonListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordJsonListenerTests.java index 2d338e2..d7101a9 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordJsonListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordJsonListenerTests.java @@ -1,7 +1,7 @@ package ru.tinkoff.kora.example.kafka.listener; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.Event; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class AutoCommitRecordJsonListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitRecordJsonListenerModule.AutoCommitRecordJsonListenerProcessTag.class) @@ -45,7 +45,7 @@ public KoraConfigModification config() { void processed() { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordListenerTests.java index 733d19a..13d4624 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordListenerTests.java @@ -1,7 +1,7 @@ package ru.tinkoff.kora.example.kafka.listener; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.Event; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class AutoCommitRecordListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitRecordListenerModule.AutoCommitRecordListenerProcessTag.class) @@ -45,7 +45,7 @@ public KoraConfigModification config() { void processed() { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordMapperListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordMapperListenerTests.java index 079aa04..bd00ab4 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordMapperListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordMapperListenerTests.java @@ -1,7 +1,7 @@ package ru.tinkoff.kora.example.kafka.listener; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.Event; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class AutoCommitRecordMapperListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitRecordMapperListenerModule.AutoCommitRecordMapperListenerProcessTag.class) @@ -45,7 +45,7 @@ public KoraConfigModification config() { void processed() { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordsListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordsListenerTests.java index b7c3244..dce3fce 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordsListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordsListenerTests.java @@ -1,7 +1,7 @@ package ru.tinkoff.kora.example.kafka.listener; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.Event; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class AutoCommitRecordsListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitRecordsListenerModule.AutoCommitRecordsListenerProcessTag.class) @@ -45,7 +45,7 @@ public KoraConfigModification config() { void processed() { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordsTelemetryListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordsTelemetryListenerTests.java index 83b1935..9c4e852 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordsTelemetryListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitRecordsTelemetryListenerTests.java @@ -21,7 +21,7 @@ @KoraAppTest(Application.class) class AutoCommitRecordsTelemetryListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitRecordsTelemetryListenerModule.AutoCommitRecordsTelemetryListenerProcessTag.class) diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueExceptionListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueExceptionListenerTests.java index da5184f..fba1eff 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueExceptionListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueExceptionListenerTests.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.Event; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; @@ -26,7 +26,7 @@ @KoraAppTest(Application.class) class AutoCommitValueExceptionListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitValueExceptionListenerModule.AutoCommitValueExceptionListenerProcessTag.class) @@ -47,7 +47,7 @@ public KoraConfigModification config() { void processed() { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueJsonListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueJsonListenerTests.java index 3d841d5..0c96e27 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueJsonListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueJsonListenerTests.java @@ -1,7 +1,7 @@ package ru.tinkoff.kora.example.kafka.listener; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.Event; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class AutoCommitValueJsonListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitValueJsonListenerModule.AutoCommitValueJsonListenerProcessTag.class) @@ -45,7 +45,7 @@ public KoraConfigModification config() { void processed() { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueKeyHeadersListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueKeyHeadersListenerTests.java index cd4ff4e..0396a7f 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueKeyHeadersListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueKeyHeadersListenerTests.java @@ -1,7 +1,7 @@ package ru.tinkoff.kora.example.kafka.listener; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.Event; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class AutoCommitValueKeyHeadersListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitValueKeyHeadersListenerModule.AutoCommitValueKeyHeadersListenerProcessTag.class) @@ -45,7 +45,7 @@ public KoraConfigModification config() { void processed() { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueKeyListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueKeyListenerTests.java index b615d3a..472f23a 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueKeyListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueKeyListenerTests.java @@ -1,7 +1,7 @@ package ru.tinkoff.kora.example.kafka.listener; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.Event; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class AutoCommitValueKeyListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitValueKeyListenerModule.AutoCommitValueKeyListenerProcessTag.class) @@ -45,7 +45,7 @@ public KoraConfigModification config() { void processed() { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueListenerTests.java index 3bb9444..721cfb6 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueListenerTests.java @@ -1,7 +1,7 @@ package ru.tinkoff.kora.example.kafka.listener; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.Event; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class AutoCommitValueListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitValueListenerModule.AutoCommitValueListenerProcessTag.class) @@ -45,7 +45,7 @@ public KoraConfigModification config() { void processed() { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueMapperListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueMapperListenerTests.java index f94190e..6eee5b5 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueMapperListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/AutoCommitValueMapperListenerTests.java @@ -1,7 +1,7 @@ package ru.tinkoff.kora.example.kafka.listener; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.Event; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class AutoCommitValueMapperListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(AutoCommitValueMapperListenerModule.AutoCommitValueMapperListenerProcessTag.class) @@ -45,7 +45,7 @@ public KoraConfigModification config() { void processed() { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/ManualCommitRecordListenerTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/ManualCommitRecordListenerTests.java index b4e964d..b811631 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/ManualCommitRecordListenerTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/listener/ManualCommitRecordListenerTests.java @@ -1,7 +1,7 @@ package ru.tinkoff.kora.example.kafka.listener; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.Event; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; @@ -24,7 +24,7 @@ @KoraAppTest(Application.class) class ManualCommitRecordListenerTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @Tag(ManualCommitRecordListenerModule.ManualCommitRecordListenerProcessTag.class) @@ -45,7 +45,7 @@ public KoraConfigModification config() { void processed() { // given var topic = "my-topic-consumer"; - var event = new JSONObject().put("username", "Bob").put("code", 1); + var event = new JSONObject().put("username", "Ivan").put("code", 1); // when connection.send(topic, Event.ofValueAndRandomKey(event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerJsonPublisherTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerJsonPublisherTests.java index 9e6811e..4b5a56e 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerJsonPublisherTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerJsonPublisherTests.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; import io.goodforgod.testcontainers.extensions.kafka.Topics; @@ -22,7 +22,7 @@ @KoraAppTest(Application.class) class ProducerJsonPublisherTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @TestComponent @@ -43,7 +43,7 @@ void processed() { // when var code = ThreadLocalRandom.current().nextInt(1, 100_000); - var name = "Bob"; + var name = "Ivan"; var event = new ProducerJsonPublisher.MyEvent(name, code); publisher.send(new ProducerRecord<>(topic, event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerMapperPublisherTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerMapperPublisherTests.java index 187bc7a..a6fa634 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerMapperPublisherTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerMapperPublisherTests.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; import io.goodforgod.testcontainers.extensions.kafka.Topics; @@ -22,7 +22,7 @@ @KoraAppTest(Application.class) class ProducerMapperPublisherTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @TestComponent @@ -43,7 +43,7 @@ void processed() { // when var code = ThreadLocalRandom.current().nextInt(1, 100_000); - var name = "Bob"; + var name = "Ivan"; var event = new ProducerMapperPublisher.MyEvent(name, code); publisher.send(new ProducerRecord<>(topic, event)); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerPublisherTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerPublisherTests.java index 1e76bac..4068394 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerPublisherTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/ProducerPublisherTests.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; import io.goodforgod.testcontainers.extensions.kafka.Topics; @@ -23,7 +23,7 @@ @KoraAppTest(Application.class) class ProducerPublisherTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @TestComponent @@ -45,7 +45,7 @@ void processed() throws InterruptedException { // when var code = ThreadLocalRandom.current().nextInt(1, 100_000); - var name = "Bob"; + var name = "Ivan"; var event = new JSONObject().put("username", name).put("code", code); publisher.send(new ProducerRecord<>(topic, event.toString())); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicJsonPublisherTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicJsonPublisherTests.java index 82902d8..3ceae85 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicJsonPublisherTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicJsonPublisherTests.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; import io.goodforgod.testcontainers.extensions.kafka.Topics; @@ -21,7 +21,7 @@ @KoraAppTest(Application.class) class TopicJsonPublisherTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @TestComponent @@ -42,7 +42,7 @@ void processed() { // when var code = ThreadLocalRandom.current().nextInt(1, 100_000); - var name = "Bob"; + var name = "Ivan"; var event = new TopicJsonPublisher.MyEvent(name, code); publisher.send(event); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicKeyHeadersPublisherTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicKeyHeadersPublisherTests.java index 9b8b876..45acf61 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicKeyHeadersPublisherTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicKeyHeadersPublisherTests.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; import io.goodforgod.testcontainers.extensions.kafka.Topics; @@ -25,7 +25,7 @@ @KoraAppTest(Application.class) class TopicKeyHeadersPublisherTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @TestComponent @@ -46,7 +46,7 @@ void processed() { // when var code = ThreadLocalRandom.current().nextInt(1, 100_000); - var name = "Bob"; + var name = "Ivan"; var event = new JSONObject().put("username", name).put("code", code); publisher.send("1", event.toString(), new RecordHeaders(List.of(new RecordHeader("2", "3".getBytes(StandardCharsets.UTF_8))))); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicKeyPublisherTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicKeyPublisherTests.java index cff86a0..4e84197 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicKeyPublisherTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicKeyPublisherTests.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; import io.goodforgod.testcontainers.extensions.kafka.Topics; @@ -21,7 +21,7 @@ @KoraAppTest(Application.class) class TopicKeyPublisherTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @TestComponent @@ -42,7 +42,7 @@ void processed() { // when var code = ThreadLocalRandom.current().nextInt(1, 100_000); - var name = "Bob"; + var name = "Ivan"; var event = new JSONObject().put("username", name).put("code", code); publisher.send("1", event.toString()); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicMapperPublisherTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicMapperPublisherTests.java index dfed5a5..4eb81d3 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicMapperPublisherTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicMapperPublisherTests.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; import io.goodforgod.testcontainers.extensions.kafka.Topics; @@ -21,7 +21,7 @@ @KoraAppTest(Application.class) class TopicMapperPublisherTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @TestComponent @@ -42,7 +42,7 @@ void processed() { // when var code = ThreadLocalRandom.current().nextInt(1, 100_000); - var name = "Bob"; + var name = "Ivan"; var event = new TopicMapperPublisher.MyEvent(name, code); publisher.send(event); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicPublisherTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicPublisherTests.java index a063848..8ea5cd1 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicPublisherTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TopicPublisherTests.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection; +import io.goodforgod.testcontainers.extensions.kafka.ConnectionKafka; import io.goodforgod.testcontainers.extensions.kafka.KafkaConnection; import io.goodforgod.testcontainers.extensions.kafka.TestcontainersKafka; import io.goodforgod.testcontainers.extensions.kafka.Topics; @@ -22,7 +22,7 @@ @KoraAppTest(Application.class) class TopicPublisherTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection + @ConnectionKafka private KafkaConnection connection; @TestComponent @@ -43,7 +43,7 @@ void processed() { // when var code = ThreadLocalRandom.current().nextInt(1, 100_000); - var name = "Bob"; + var name = "Ivan"; var event = new JSONObject().put("username", name).put("code", code); publisher.send(event.toString()); diff --git a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TransactionalPublisherTests.java b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TransactionalPublisherTests.java index 5e63751..b6b36ea 100644 --- a/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TransactionalPublisherTests.java +++ b/kora-java-kafka/src/test/java/ru/tinkoff/kora/example/kafka/publisher/TransactionalPublisherTests.java @@ -4,7 +4,6 @@ import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.kafka.*; -import io.goodforgod.testcontainers.extensions.kafka.ContainerKafkaConnection.Property; import io.goodforgod.testcontainers.extensions.kafka.Topics; import java.time.Duration; import java.util.concurrent.ThreadLocalRandom; @@ -22,7 +21,7 @@ @KoraAppTest(Application.class) class TransactionalPublisherTests implements KoraAppTestConfigModifier { - @ContainerKafkaConnection(properties = @Property(name = ConsumerConfig.ISOLATION_LEVEL_CONFIG, value = "read_committed")) + @ConnectionKafka(properties = { ConsumerConfig.ISOLATION_LEVEL_CONFIG, "read_committed" }) private KafkaConnection connection; @TestComponent diff --git a/kora-java-kafka/src/test/resources/logback-test.xml b/kora-java-kafka/src/test/resources/logback-test.xml index 89dcf64..66bab1b 100644 --- a/kora-java-kafka/src/test/resources/logback-test.xml +++ b/kora-java-kafka/src/test/resources/logback-test.xml @@ -16,10 +16,12 @@ + + - + diff --git a/kora-java-openapi-generator-http-client/build.gradle b/kora-java-openapi-generator-http-client/build.gradle index 6bafb95..66bb1eb 100644 --- a/kora-java-openapi-generator-http-client/build.gradle +++ b/kora-java-openapi-generator-http-client/build.gradle @@ -36,10 +36,9 @@ dependencies { annotationProcessor "ru.tinkoff.kora:annotation-processors" implementation "ru.tinkoff.kora:validation-module" - implementation "ru.tinkoff.kora:validation-common" implementation "ru.tinkoff.kora:http-client-jdk" implementation "ru.tinkoff.kora:json-module" - implementation "io.projectreactor:reactor-core:3.5.10" // For reactive examples (optional) + implementation "io.projectreactor:reactor-core:3.6.3" // For reactive examples (optional) implementation "ru.tinkoff.kora:logging-logback" implementation "ru.tinkoff.kora:config-hocon" @@ -47,7 +46,7 @@ dependencies { testImplementation "org.json:json:20231013" testImplementation "org.skyscreamer:jsonassert:1.5.1" testImplementation "ru.tinkoff.kora:test-junit5" - testImplementation "io.goodforgod:testcontainers-extensions-mockserver:0.9.6" + testImplementation "io.goodforgod:testcontainers-extensions-mockserver:0.11.0" } // OpeAPI for V2 @@ -123,13 +122,12 @@ sourceSets { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-openapi-generator-http-client/src/main/resources/application.conf b/kora-java-openapi-generator-http-client/src/main/resources/application.conf index 68eb285..5c8424e 100644 --- a/kora-java-openapi-generator-http-client/src/main/resources/application.conf +++ b/kora-java-openapi-generator-http-client/src/main/resources/application.conf @@ -16,6 +16,6 @@ httpClient.petV3.PetApi { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-openapi-generator-http-client/src/main/resources/logback.xml b/kora-java-openapi-generator-http-client/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-openapi-generator-http-client/src/main/resources/logback.xml +++ b/kora-java-openapi-generator-http-client/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-openapi-generator-http-client/src/main/resources/openapi/petstoreV3.yaml b/kora-java-openapi-generator-http-client/src/main/resources/openapi/petstoreV3.yaml index 70107f1..d4e1511 100644 --- a/kora-java-openapi-generator-http-client/src/main/resources/openapi/petstoreV3.yaml +++ b/kora-java-openapi-generator-http-client/src/main/resources/openapi/petstoreV3.yaml @@ -149,6 +149,7 @@ paths: type: integer format: int64 nullable: false + minimum: 1 responses: '200': description: successful operation diff --git a/kora-java-openapi-generator-http-client/src/test/java/ru/tinkoff/kora/example/openapi/http/client/HttpClientPetV2Tests.java b/kora-java-openapi-generator-http-client/src/test/java/ru/tinkoff/kora/example/openapi/http/client/HttpClientPetV2Tests.java index ac4ba93..fa62d40 100644 --- a/kora-java-openapi-generator-http-client/src/test/java/ru/tinkoff/kora/example/openapi/http/client/HttpClientPetV2Tests.java +++ b/kora-java-openapi-generator-http-client/src/test/java/ru/tinkoff/kora/example/openapi/http/client/HttpClientPetV2Tests.java @@ -6,9 +6,9 @@ import static org.mockserver.model.HttpResponse.response; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.mockserver.ContainerMockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.MockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockserver; +import io.goodforgod.testcontainers.extensions.mockserver.ConnectionMockServer; +import io.goodforgod.testcontainers.extensions.mockserver.MockServerConnection; +import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockServer; import java.util.List; import org.jetbrains.annotations.NotNull; import org.json.JSONArray; @@ -25,12 +25,12 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersMockserver(mode = ContainerMode.PER_RUN) +@TestcontainersMockServer(mode = ContainerMode.PER_RUN) @KoraAppTest(Application.class) class HttpClientPetV2Tests implements KoraAppTestConfigModifier { - @ContainerMockserverConnection - private MockserverConnection mockserverConnection; + @ConnectionMockServer + private MockServerConnection mockserverConnection; @NotNull @Override diff --git a/kora-java-openapi-generator-http-client/src/test/java/ru/tinkoff/kora/example/openapi/http/client/HttpClientPetV3Tests.java b/kora-java-openapi-generator-http-client/src/test/java/ru/tinkoff/kora/example/openapi/http/client/HttpClientPetV3Tests.java index 1c2119f..7eb0e9d 100644 --- a/kora-java-openapi-generator-http-client/src/test/java/ru/tinkoff/kora/example/openapi/http/client/HttpClientPetV3Tests.java +++ b/kora-java-openapi-generator-http-client/src/test/java/ru/tinkoff/kora/example/openapi/http/client/HttpClientPetV3Tests.java @@ -6,9 +6,9 @@ import static org.mockserver.model.HttpResponse.response; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.mockserver.ContainerMockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.MockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockserver; +import io.goodforgod.testcontainers.extensions.mockserver.ConnectionMockServer; +import io.goodforgod.testcontainers.extensions.mockserver.MockServerConnection; +import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockServer; import java.util.List; import org.jetbrains.annotations.NotNull; import org.json.JSONArray; @@ -25,12 +25,12 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersMockserver(mode = ContainerMode.PER_RUN) +@TestcontainersMockServer(mode = ContainerMode.PER_RUN) @KoraAppTest(Application.class) class HttpClientPetV3Tests implements KoraAppTestConfigModifier { - @ContainerMockserverConnection - private MockserverConnection mockserverConnection; + @ConnectionMockServer + private MockServerConnection mockserverConnection; @NotNull @Override diff --git a/kora-java-openapi-generator-http-client/src/test/resources/logback-test.xml b/kora-java-openapi-generator-http-client/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-openapi-generator-http-client/src/test/resources/logback-test.xml +++ b/kora-java-openapi-generator-http-client/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-openapi-generator-http-server/build.gradle b/kora-java-openapi-generator-http-server/build.gradle index 3fc4b3d..e7738b6 100644 --- a/kora-java-openapi-generator-http-server/build.gradle +++ b/kora-java-openapi-generator-http-server/build.gradle @@ -36,10 +36,9 @@ dependencies { annotationProcessor "ru.tinkoff.kora:annotation-processors" implementation "ru.tinkoff.kora:validation-module" - implementation "ru.tinkoff.kora:validation-common" implementation "ru.tinkoff.kora:http-server-undertow" implementation "ru.tinkoff.kora:json-module" - implementation "io.projectreactor:reactor-core:3.5.10" // For reactive examples (optional) + implementation "io.projectreactor:reactor-core:3.6.3" // For reactive examples (optional) implementation "ru.tinkoff.kora:logging-logback" implementation "ru.tinkoff.kora:config-hocon" @@ -75,7 +74,7 @@ tasks.register("openApiGeneratePetV3", GenerateTask) { modelPackage = "ru.tinkoff.kora.example.openapi.petV3.model" invokerPackage = "ru.tinkoff.kora.example.openapi.petV3.invoker" configOptions = [ - mode : "java-reactive-server", // так же есть java_server вариация HTTP Server'а + mode : "java-reactive-server", // так же есть java-server вариация HTTP Server'а enableServerValidation: "true" ] } @@ -117,13 +116,12 @@ sourceSets { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-openapi-generator-http-server/src/main/resources/application.conf b/kora-java-openapi-generator-http-server/src/main/resources/application.conf index dc5f2e0..2828793 100644 --- a/kora-java-openapi-generator-http-server/src/main/resources/application.conf +++ b/kora-java-openapi-generator-http-server/src/main/resources/application.conf @@ -6,6 +6,6 @@ httpServer { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-openapi-generator-http-server/src/main/resources/logback.xml b/kora-java-openapi-generator-http-server/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-openapi-generator-http-server/src/main/resources/logback.xml +++ b/kora-java-openapi-generator-http-server/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-openapi-generator-http-server/src/main/resources/openapi/petstoreV3.yaml b/kora-java-openapi-generator-http-server/src/main/resources/openapi/petstoreV3.yaml index 70107f1..d4e1511 100644 --- a/kora-java-openapi-generator-http-server/src/main/resources/openapi/petstoreV3.yaml +++ b/kora-java-openapi-generator-http-server/src/main/resources/openapi/petstoreV3.yaml @@ -149,6 +149,7 @@ paths: type: integer format: int64 nullable: false + minimum: 1 responses: '200': description: successful operation diff --git a/kora-java-openapi-generator-http-server/src/test/resources/logback-test.xml b/kora-java-openapi-generator-http-server/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-openapi-generator-http-server/src/test/resources/logback-test.xml +++ b/kora-java-openapi-generator-http-server/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-resilient/build.gradle b/kora-java-resilient/build.gradle index 658abe7..8096906 100644 --- a/kora-java-resilient/build.gradle +++ b/kora-java-resilient/build.gradle @@ -68,13 +68,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-resilient/src/main/resources/application.conf b/kora-java-resilient/src/main/resources/application.conf index b9a49f7..126fcd2 100644 --- a/kora-java-resilient/src/main/resources/application.conf +++ b/kora-java-resilient/src/main/resources/application.conf @@ -28,6 +28,6 @@ resilient { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-resilient/src/main/resources/logback.xml b/kora-java-resilient/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-resilient/src/main/resources/logback.xml +++ b/kora-java-resilient/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-resilient/src/test/resources/logback-test.xml b/kora-java-resilient/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-resilient/src/test/resources/logback-test.xml +++ b/kora-java-resilient/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-scheduling-jdk/build.gradle b/kora-java-scheduling-jdk/build.gradle index 49e93ef..3c929a2 100644 --- a/kora-java-scheduling-jdk/build.gradle +++ b/kora-java-scheduling-jdk/build.gradle @@ -69,13 +69,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-scheduling-jdk/src/main/resources/application.conf b/kora-java-scheduling-jdk/src/main/resources/application.conf index b2a4c8a..81c4c93 100644 --- a/kora-java-scheduling-jdk/src/main/resources/application.conf +++ b/kora-java-scheduling-jdk/src/main/resources/application.conf @@ -12,6 +12,6 @@ scheduling { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-scheduling-jdk/src/main/resources/logback.xml b/kora-java-scheduling-jdk/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-scheduling-jdk/src/main/resources/logback.xml +++ b/kora-java-scheduling-jdk/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-scheduling-jdk/src/test/resources/logback-test.xml b/kora-java-scheduling-jdk/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-scheduling-jdk/src/test/resources/logback-test.xml +++ b/kora-java-scheduling-jdk/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-scheduling-quartz/build.gradle b/kora-java-scheduling-quartz/build.gradle index e7b8ad0..2153340 100644 --- a/kora-java-scheduling-quartz/build.gradle +++ b/kora-java-scheduling-quartz/build.gradle @@ -69,13 +69,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-scheduling-quartz/src/main/resources/application.conf b/kora-java-scheduling-quartz/src/main/resources/application.conf index cace772..34f234d 100644 --- a/kora-java-scheduling-quartz/src/main/resources/application.conf +++ b/kora-java-scheduling-quartz/src/main/resources/application.conf @@ -13,6 +13,6 @@ scheduling { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-scheduling-quartz/src/main/resources/logback.xml b/kora-java-scheduling-quartz/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-scheduling-quartz/src/main/resources/logback.xml +++ b/kora-java-scheduling-quartz/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-scheduling-quartz/src/test/resources/logback-test.xml b/kora-java-scheduling-quartz/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-scheduling-quartz/src/test/resources/logback-test.xml +++ b/kora-java-scheduling-quartz/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-soap-client/build.gradle b/kora-java-soap-client/build.gradle index 426ea74..7ec8ad1 100644 --- a/kora-java-soap-client/build.gradle +++ b/kora-java-soap-client/build.gradle @@ -42,7 +42,7 @@ dependencies { implementation "ru.tinkoff.kora:config-hocon" testImplementation "ru.tinkoff.kora:test-junit5" - testImplementation "io.goodforgod:testcontainers-extensions-mockserver:0.9.6" + testImplementation "io.goodforgod:testcontainers-extensions-mockserver:0.11.0" } //noinspection GroovyAssignabilityCheck @@ -92,13 +92,12 @@ wsdl2java { ] } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-soap-client/src/main/resources/application.conf b/kora-java-soap-client/src/main/resources/application.conf index b3afe95..0148bf6 100644 --- a/kora-java-soap-client/src/main/resources/application.conf +++ b/kora-java-soap-client/src/main/resources/application.conf @@ -12,6 +12,6 @@ httpClient.default { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-soap-client/src/main/resources/logback.xml b/kora-java-soap-client/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-soap-client/src/main/resources/logback.xml +++ b/kora-java-soap-client/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-soap-client/src/test/java/ru/tinkoff/kora/example/soap/client/SimpleServiceTests.java b/kora-java-soap-client/src/test/java/ru/tinkoff/kora/example/soap/client/SimpleServiceTests.java index f51103a..2a456c2 100644 --- a/kora-java-soap-client/src/test/java/ru/tinkoff/kora/example/soap/client/SimpleServiceTests.java +++ b/kora-java-soap-client/src/test/java/ru/tinkoff/kora/example/soap/client/SimpleServiceTests.java @@ -3,9 +3,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.goodforgod.testcontainers.extensions.ContainerMode; -import io.goodforgod.testcontainers.extensions.mockserver.ContainerMockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.MockserverConnection; -import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockserver; +import io.goodforgod.testcontainers.extensions.mockserver.ConnectionMockServer; +import io.goodforgod.testcontainers.extensions.mockserver.MockServerConnection; +import io.goodforgod.testcontainers.extensions.mockserver.TestcontainersMockServer; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.mockserver.model.XmlBody; @@ -16,12 +16,12 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -@TestcontainersMockserver(mode = ContainerMode.PER_CLASS) +@TestcontainersMockServer(mode = ContainerMode.PER_CLASS) @KoraAppTest(Application.class) class SimpleServiceTests implements KoraAppTestConfigModifier { - @ContainerMockserverConnection - private MockserverConnection mockserverConnection; + @ConnectionMockServer + private MockServerConnection mockserverConnection; @NotNull @Override diff --git a/kora-java-soap-client/src/test/resources/logback-test.xml b/kora-java-soap-client/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-soap-client/src/test/resources/logback-test.xml +++ b/kora-java-soap-client/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-telemetry/build.gradle b/kora-java-telemetry/build.gradle index 2871193..ca6908a 100644 --- a/kora-java-telemetry/build.gradle +++ b/kora-java-telemetry/build.gradle @@ -66,13 +66,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-telemetry/src/main/resources/application.conf b/kora-java-telemetry/src/main/resources/application.conf index b2881c9..34892f1 100644 --- a/kora-java-telemetry/src/main/resources/application.conf +++ b/kora-java-telemetry/src/main/resources/application.conf @@ -21,6 +21,6 @@ tracing { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-telemetry/src/main/resources/logback.xml b/kora-java-telemetry/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-telemetry/src/main/resources/logback.xml +++ b/kora-java-telemetry/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-telemetry/src/test/java/ru/tinkoff/kora/example/telemetry/AppContainer.java b/kora-java-telemetry/src/test/java/ru/tinkoff/kora/example/telemetry/AppContainer.java index df80a21..1b6ebd1 100644 --- a/kora-java-telemetry/src/test/java/ru/tinkoff/kora/example/telemetry/AppContainer.java +++ b/kora-java-telemetry/src/test/java/ru/tinkoff/kora/example/telemetry/AppContainer.java @@ -2,6 +2,7 @@ import java.net.URI; import java.nio.file.Paths; +import java.time.Duration; import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; @@ -31,6 +32,7 @@ public static AppContainer build() { protected void configure() { super.configure(); withExposedPorts(8080, 8085); + withStartupTimeout(Duration.ofSeconds(120)); withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(getClass()))); waitingFor(Wait.forHttp("/readiness") .forPort(8085) diff --git a/kora-java-telemetry/src/test/resources/logback-test.xml b/kora-java-telemetry/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-telemetry/src/test/resources/logback-test.xml +++ b/kora-java-telemetry/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-java-validation/build.gradle b/kora-java-validation/build.gradle index 9791599..535f52d 100644 --- a/kora-java-validation/build.gradle +++ b/kora-java-validation/build.gradle @@ -61,13 +61,12 @@ test { } } -jar.enabled = true +jar.enabled = false shadowJar { mergeServiceFiles() manifest { attributes "Main-Class": mainClassName attributes "Implementation-Version": koraVersion - attributes "Build-Time": java.time.OffsetDateTime.now() } } diff --git a/kora-java-validation/src/main/resources/application.conf b/kora-java-validation/src/main/resources/application.conf index b7f05da..aa7b2b0 100644 --- a/kora-java-validation/src/main/resources/application.conf +++ b/kora-java-validation/src/main/resources/application.conf @@ -1,5 +1,5 @@ logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-java-validation/src/main/resources/logback.xml b/kora-java-validation/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-java-validation/src/main/resources/logback.xml +++ b/kora-java-validation/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-java-validation/src/test/java/ru/tinkoff/kora/example/validation/ArgumentValidatorTests.java b/kora-java-validation/src/test/java/ru/tinkoff/kora/example/validation/ArgumentValidatorTests.java index 1886feb..ca6db8b 100644 --- a/kora-java-validation/src/test/java/ru/tinkoff/kora/example/validation/ArgumentValidatorTests.java +++ b/kora-java-validation/src/test/java/ru/tinkoff/kora/example/validation/ArgumentValidatorTests.java @@ -17,7 +17,7 @@ class ArgumentValidatorTests { @Test void createSuccess() { // given - var user = new ArgumentValidator.User("1", "Bob", "2"); + var user = new ArgumentValidator.User("1", "Ivan", "2"); var code = "ME2"; // then @@ -28,7 +28,7 @@ void createSuccess() { @Test void createFails() { // given - var user = new ArgumentValidator.User("1", "Bob", "2"); + var user = new ArgumentValidator.User("1", "Ivan", "2"); var code = "2"; // then diff --git a/kora-java-validation/src/test/java/ru/tinkoff/kora/example/validation/ResultValidatorTests.java b/kora-java-validation/src/test/java/ru/tinkoff/kora/example/validation/ResultValidatorTests.java index f3c8b81..5f8af9a 100644 --- a/kora-java-validation/src/test/java/ru/tinkoff/kora/example/validation/ResultValidatorTests.java +++ b/kora-java-validation/src/test/java/ru/tinkoff/kora/example/validation/ResultValidatorTests.java @@ -16,7 +16,7 @@ class ResultValidatorTests { @Test void createSuccess() { // given - var name = "Bob"; + var name = "Ivan"; var status = "2"; // then diff --git a/kora-java-validation/src/test/resources/logback-test.xml b/kora-java-validation/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-java-validation/src/test/resources/logback-test.xml +++ b/kora-java-validation/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-kotlin-crud/README.md b/kora-kotlin-crud/README.md index b419e04..194d5b0 100644 --- a/kora-kotlin-crud/README.md +++ b/kora-kotlin-crud/README.md @@ -1,6 +1,6 @@ # Kora Kotlin CRUD Service -Пример сервиса реализованного на Kora с HTTP [CRUD](https://appmaster.io/ru/blog/grubye-operatsii-chto-takoe-grubye-operatsii) API, +Пример сервиса реализованного на Kora с HTTP [CRUD](https://github.com/swagger-api/swagger-petstore) API, в качестве базы данных выступает Postgres, используется кэш Caffeine, а также другие модули которые использовались бы в реальном приложении в бою. В примере использовались модули: diff --git a/kora-kotlin-crud/build.gradle.kts b/kora-kotlin-crud/build.gradle.kts index 2eb417d..fd63aab 100644 --- a/kora-kotlin-crud/build.gradle.kts +++ b/kora-kotlin-crud/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { implementation("ru.tinkoff.kora:openapi-management") implementation("ru.tinkoff.kora:logging-logback") - implementation("org.postgresql:postgresql:42.6.0") + implementation("org.postgresql:postgresql:42.7.2") implementation("org.mapstruct:mapstruct:1.5.5.Final") testImplementation("org.json:json:20231013") @@ -67,7 +67,7 @@ dependencies { testImplementation("io.mockk:mockk:1.13.8") testImplementation("ru.tinkoff.kora:test-junit5") - testImplementation("io.goodforgod:testcontainers-extensions-postgres:0.9.6") + testImplementation("io.goodforgod:testcontainers-extensions-postgres:0.11.0") testImplementation("org.testcontainers:junit-jupiter:1.17.6") } diff --git a/kora-kotlin-crud/src/main/resources/application.conf b/kora-kotlin-crud/src/main/resources/application.conf index 6047c88..08f5f45 100644 --- a/kora-kotlin-crud/src/main/resources/application.conf +++ b/kora-kotlin-crud/src/main/resources/application.conf @@ -9,7 +9,7 @@ db { username = ${POSTGRES_USER} password = ${POSTGRES_PASS} maxPoolSize = 10 - poolName = "example" + poolName = "kora" initializationFailTimeout = "10s" } @@ -55,6 +55,6 @@ resilient { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-kotlin-crud/src/main/resources/logback.xml b/kora-kotlin-crud/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-kotlin-crud/src/main/resources/logback.xml +++ b/kora-kotlin-crud/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-kotlin-crud/src/main/resources/openapi/http-server.yaml b/kora-kotlin-crud/src/main/resources/openapi/http-server.yaml index 8851744..8d99f49 100644 --- a/kora-kotlin-crud/src/main/resources/openapi/http-server.yaml +++ b/kora-kotlin-crud/src/main/resources/openapi/http-server.yaml @@ -79,6 +79,7 @@ paths: type: integer format: int64 nullable: false + minimum: 1 responses: '200': description: successful operation diff --git a/kora-kotlin-crud/src/test/kotlin/ru/tinkoff/kora/kotlin/example/crud/AppContainer.kt b/kora-kotlin-crud/src/test/kotlin/ru/tinkoff/kora/kotlin/example/crud/AppContainer.kt index 2dfc714..7f6afc2 100644 --- a/kora-kotlin-crud/src/test/kotlin/ru/tinkoff/kora/kotlin/example/crud/AppContainer.kt +++ b/kora-kotlin-crud/src/test/kotlin/ru/tinkoff/kora/kotlin/example/crud/AppContainer.kt @@ -8,6 +8,7 @@ import org.testcontainers.images.builder.ImageFromDockerfile import org.testcontainers.utility.DockerImageName import java.net.URI import java.nio.file.Paths +import java.time.Duration import java.util.concurrent.Future class AppContainer : GenericContainer { @@ -32,6 +33,7 @@ class AppContainer : GenericContainer { override fun configure() { super.configure() withExposedPorts(8080, 8085) + withStartupTimeout(Duration.ofSeconds(120)) withLogConsumer(Slf4jLogConsumer(LoggerFactory.getLogger(AppContainer::class.java))) waitingFor(Wait.forHttp("/system/readiness").forPort(8085).forStatusCode(200)) } diff --git a/kora-kotlin-crud/src/test/kotlin/ru/tinkoff/kora/kotlin/example/crud/PetControllerTests.kt b/kora-kotlin-crud/src/test/kotlin/ru/tinkoff/kora/kotlin/example/crud/PetControllerTests.kt index ed9ae10..bc6a0c4 100644 --- a/kora-kotlin-crud/src/test/kotlin/ru/tinkoff/kora/kotlin/example/crud/PetControllerTests.kt +++ b/kora-kotlin-crud/src/test/kotlin/ru/tinkoff/kora/kotlin/example/crud/PetControllerTests.kt @@ -2,10 +2,10 @@ package ru.tinkoff.kora.kotlin.example.crud import io.goodforgod.testcontainers.extensions.ContainerMode import io.goodforgod.testcontainers.extensions.Network -import io.goodforgod.testcontainers.extensions.jdbc.ContainerPostgresConnection +import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection import io.goodforgod.testcontainers.extensions.jdbc.Migration -import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgres +import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL import org.json.JSONObject import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Assertions.* @@ -20,7 +20,7 @@ import java.net.http.HttpRequest import java.net.http.HttpResponse import java.time.Duration -@TestcontainersPostgres( +@TestcontainersPostgreSQL( network = Network(shared = true), mode = ContainerMode.PER_RUN, migration = Migration( @@ -29,7 +29,7 @@ import java.time.Duration drop = Migration.Mode.PER_METHOD ) ) -class PetControllerTests(@ContainerPostgresConnection val connection: JdbcConnection) { +class PetControllerTests(@ConnectionPostgreSQL val connection: JdbcConnection) { companion object { @@ -37,7 +37,7 @@ class PetControllerTests(@ContainerPostgresConnection val connection: JdbcConnec @JvmStatic @BeforeAll - fun setup(@ContainerPostgresConnection connection: JdbcConnection) { + fun setup(@ConnectionPostgreSQL connection: JdbcConnection) { val params = connection.paramsInNetwork().orElseThrow() container.withEnv( mapOf( diff --git a/kora-kotlin-crud/src/test/resources/logback-test.xml b/kora-kotlin-crud/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-kotlin-crud/src/test/resources/logback-test.xml +++ b/kora-kotlin-crud/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/kora-kotlin-helloworld/build.gradle.kts b/kora-kotlin-helloworld/build.gradle.kts index b4184ae..dddb997 100644 --- a/kora-kotlin-helloworld/build.gradle.kts +++ b/kora-kotlin-helloworld/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("application") - kotlin("jvm") version ("1.9.22") - id("com.google.devtools.ksp") version ("1.9.22-1.0.16") + kotlin("jvm") version ("1.9.10") + id("com.google.devtools.ksp") version ("1.9.10-1.0.13") } repositories { diff --git a/kora-kotlin-helloworld/src/main/resources/application.conf b/kora-kotlin-helloworld/src/main/resources/application.conf index dc5f2e0..2828793 100644 --- a/kora-kotlin-helloworld/src/main/resources/application.conf +++ b/kora-kotlin-helloworld/src/main/resources/application.conf @@ -6,6 +6,6 @@ httpServer { logging.level { "root": "WARN" - "ru.tinkoff.kora": "DEBUG" - "ru.tinkoff.kora.example": "DEBUG" + "ru.tinkoff.kora": "INFO" + "ru.tinkoff.kora.example": "INFO" } diff --git a/kora-kotlin-helloworld/src/main/resources/logback.xml b/kora-kotlin-helloworld/src/main/resources/logback.xml index 7f6b209..745e83f 100644 --- a/kora-kotlin-helloworld/src/main/resources/logback.xml +++ b/kora-kotlin-helloworld/src/main/resources/logback.xml @@ -15,4 +15,6 @@ + + diff --git a/kora-kotlin-helloworld/src/test/resources/logback-test.xml b/kora-kotlin-helloworld/src/test/resources/logback-test.xml index a9af3b9..adccdab 100644 --- a/kora-kotlin-helloworld/src/test/resources/logback-test.xml +++ b/kora-kotlin-helloworld/src/test/resources/logback-test.xml @@ -16,10 +16,9 @@ - - + - + diff --git a/settings.gradle b/settings.gradle index 26dea0a..91990fb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,7 @@ pluginManagement { gradlePluginPortal() mavenLocal() mavenCentral() + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } } } @@ -31,5 +32,10 @@ include "kora-java-config-hocon" include "kora-java-config-yaml" include "kora-java-helloworld" include "kora-java-crud" +include "kora-java-graalvm-crud-jdbc" +include "kora-java-graalvm-crud-r2dbc" +include "kora-java-graalvm-crud-vertx" +include "kora-java-graalvm-crud-cassandra" +include "kora-java-graalvm-kafka" include "kora-kotlin-helloworld" include "kora-kotlin-crud"