diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a398881..572e79b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,8 @@ permissions: pull-requests: write # auto-merge requirement jobs: - ci: + build: + name: Build & Test (core modules) runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -32,8 +33,8 @@ jobs: - name: Check formatting run: ./gradlew ktlintCheck - - name: Test - run: ./gradlew test + - name: Build and test core modules + run: ./gradlew build -x :okapi-spring-boot:test -x :okapi-kafka:test -x ktlintCheck - name: Upload test results uses: actions/upload-artifact@v4 @@ -42,10 +43,53 @@ jobs: name: test-results-java-${{ matrix.java }} path: '**/build/test-results/test/TEST-*.xml' + spring-compat: + name: "Spring Boot ${{ matrix.spring-boot }}" + runs-on: ubuntu-24.04 + needs: build + strategy: + matrix: + include: + - spring-boot: "3.5.12" + spring: "6.2.17" + - spring-boot: "4.0.4" + spring: "7.0.6" + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + + - name: Test okapi-spring-boot + run: >- + ./gradlew :okapi-spring-boot:test + -PspringBootVersion=${{ matrix.spring-boot }} + -PspringVersion=${{ matrix.spring }} + + kafka-compat: + name: "Kafka ${{ matrix.kafka }}" + runs-on: ubuntu-24.04 + needs: build + strategy: + matrix: + kafka: ["3.9.0", "4.0.2"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + + - name: Test okapi-kafka + run: ./gradlew :okapi-kafka:test -PkafkaVersion=${{ matrix.kafka }} + auto-merge-dependabot: # only for PRs by dependabot[bot] if: github.event.pull_request.user.login == 'dependabot[bot]' - needs: [ ci ] + needs: [ build ] uses: softwaremill/github-actions-workflows/.github/workflows/auto-merge.yml@main secrets: github-token: ${{ secrets.SOFTWAREMILL_CI_PR_TOKEN }} diff --git a/README.md b/README.md index 84dc71a..9b3d25f 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,16 @@ Messages are stored in a database table within the same transaction as your busi | `okapi-spring-boot` | Spring Boot autoconfiguration | | `okapi-bom` | Bill of Materials for version alignment | +## Compatibility + +| Dependency | Supported Versions | Notes | +|---|---|---| +| Java | 21+ | Required | +| Spring Boot | 3.5.x, 4.0.x | `okapi-spring-boot` module | +| Kafka Clients | 3.9.x, 4.x | `okapi-kafka` module — you provide `kafka-clients` dependency | +| Exposed | 1.x | `okapi-postgres`, `okapi-mysql` modules — you provide Exposed | +| Docker | Required for tests | Testcontainers-based integration tests | + ## Quick Start (Spring Boot) ```kotlin @@ -46,6 +56,10 @@ fun placeOrder(order: Order) { } ``` +> **Note:** `okapi-kafka` requires you to add `org.apache.kafka:kafka-clients` to your project. +> `okapi-postgres`/`okapi-mysql` require Exposed ORM dependencies. +> Spring and Kafka versions are not forced by okapi — you control them. + Autoconfiguration handles scheduling, retries, and delivery automatically. ## Standalone Usage diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c88a5b8..5f64c50 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,8 +9,8 @@ testcontainers = "1.20.5" postgresql = "42.7.5" mysql = "9.2.0" kafkaClients = "3.9.0" -spring = "6.2.3" -springBoot = "3.4.3" +spring = "6.2.17" +springBoot = "3.5.12" wiremock = "3.10.0" [libraries] diff --git a/okapi-kafka/build.gradle.kts b/okapi-kafka/build.gradle.kts index 8283a0a..a9eafd0 100644 --- a/okapi-kafka/build.gradle.kts +++ b/okapi-kafka/build.gradle.kts @@ -6,8 +6,22 @@ dependencies { implementation(project(":okapi-core")) implementation(libs.jacksonModuleKotlin) implementation(libs.jacksonDatatypeJsr310) - implementation(libs.kafkaClients) + compileOnly(libs.kafkaClients) + testImplementation(libs.kafkaClients) testImplementation(libs.kotestRunnerJunit5) testImplementation(libs.kotestAssertionsCore) } + +// CI version override: ./gradlew :okapi-kafka:test -PkafkaVersion=4.0.2 +val kafkaVersion: String? by project + +if (kafkaVersion != null) { + configurations.configureEach { + resolutionStrategy.eachDependency { + if (requested.group == "org.apache.kafka") { + useVersion(kafkaVersion!!) + } + } + } +} diff --git a/okapi-kafka/src/test/kotlin/com/softwaremill/okapi/kafka/KafkaMessageDelivererTest.kt b/okapi-kafka/src/test/kotlin/com/softwaremill/okapi/kafka/KafkaMessageDelivererTest.kt index f266e8b..31f4cc4 100644 --- a/okapi-kafka/src/test/kotlin/com/softwaremill/okapi/kafka/KafkaMessageDelivererTest.kt +++ b/okapi-kafka/src/test/kotlin/com/softwaremill/okapi/kafka/KafkaMessageDelivererTest.kt @@ -20,27 +20,27 @@ class KafkaMessageDelivererTest : FunSpec({ } test("successful send → Success") { - val producer = MockProducer(true, StringSerializer(), StringSerializer()) + val producer = MockProducer(true, null, StringSerializer(), StringSerializer()) val deliverer = KafkaMessageDeliverer(producer) deliverer.deliver(entry()) shouldBe DeliveryResult.Success } test("retriable exception (NetworkException) → RetriableFailure") { - val producer = MockProducer(true, StringSerializer(), StringSerializer()) + val producer = MockProducer(true, null, StringSerializer(), StringSerializer()) producer.sendException = NetworkException("broker down") val deliverer = KafkaMessageDeliverer(producer) deliverer.deliver(entry()).shouldBeInstanceOf() } test("permanent exception (AuthenticationException) → PermanentFailure") { - val producer = MockProducer(true, StringSerializer(), StringSerializer()) + val producer = MockProducer(true, null, StringSerializer(), StringSerializer()) producer.sendException = AuthenticationException("bad credentials") val deliverer = KafkaMessageDeliverer(producer) deliverer.deliver(entry()).shouldBeInstanceOf() } test("permanent exception (RecordTooLargeException) → PermanentFailure") { - val producer = MockProducer(true, StringSerializer(), StringSerializer()) + val producer = MockProducer(true, null, StringSerializer(), StringSerializer()) producer.sendException = RecordTooLargeException("too big") val deliverer = KafkaMessageDeliverer(producer) deliverer.deliver(entry()).shouldBeInstanceOf() diff --git a/okapi-spring-boot/build.gradle.kts b/okapi-spring-boot/build.gradle.kts index e0ef746..1997b52 100644 --- a/okapi-spring-boot/build.gradle.kts +++ b/okapi-spring-boot/build.gradle.kts @@ -36,3 +36,20 @@ dependencies { testImplementation(libs.postgresql) testImplementation(libs.wiremock) } + +// CI version override: ./gradlew :okapi-spring-boot:test -PspringBootVersion=4.0.4 -PspringVersion=7.0.6 +val springBootVersion: String? by project +val springVersion: String? by project + +if (springBootVersion != null || springVersion != null) { + configurations.configureEach { + resolutionStrategy.eachDependency { + if (springBootVersion != null && requested.group == "org.springframework.boot") { + useVersion(springBootVersion!!) + } + if (springVersion != null && requested.group == "org.springframework") { + useVersion(springVersion!!) + } + } + } +}