Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 }}
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
16 changes: 15 additions & 1 deletion okapi-kafka/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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!!)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<DeliveryResult.RetriableFailure>()
}

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<DeliveryResult.PermanentFailure>()
}

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<DeliveryResult.PermanentFailure>()
Expand Down
17 changes: 17 additions & 0 deletions okapi-spring-boot/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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!!)
}
}
}
}
Loading