Skip to content

Commit f19f538

Browse files
Add Kotlin Spring Boot starter (#433)
* Introduce sdk-spring-boot-kotlin-starter and move common spring boot integration code in sdk-spring-boot * Dependency cleanup
1 parent b40c904 commit f19f538

File tree

24 files changed

+216
-114
lines changed

24 files changed

+216
-114
lines changed

build.gradle.kts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
plugins {
2-
id("com.github.jk1.dependency-license-report") version "2.0"
3-
id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
4-
5-
id("org.jetbrains.dokka") version "1.9.20"
2+
alias(libs.plugins.dependency.license.report)
3+
alias(libs.plugins.nexus.publish)
4+
alias(libs.plugins.dokka)
65

76
// https://github.com/gradle/gradle/issues/20084#issuecomment-1060822638
87
id(libs.plugins.spotless.get().pluginId) apply false

gradle/libs.versions.toml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ ksp = "2.0.21-1.0.28"
55
protobuf = "4.29.0"
66
opentelemetry = "1.44.1"
77
vertx = "4.5.11"
8-
dokka = "1.9.20"
98
kotlinx-serialization = "1.7.3"
109
kotlinx-coroutines = "1.9.0"
1110
junit = "5.10.2"
@@ -20,7 +19,6 @@ jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations
2019
jackson-core = { module = "com.fasterxml.jackson.core:jackson-core", version.ref = "jackson" }
2120
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
2221
jackson-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson" }
23-
jackson-jdk8 = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jdk8", version.ref = "jackson" }
2422
jackson-parameter-names = { module = "com.fasterxml.jackson.module:jackson-module-parameter-names", version.ref = "jackson" }
2523
handlebars = "com.github.jknack:handlebars:4.3.1"
2624
victools-jsonschema-generator = { module = "com.github.victools:jsonschema-generator", version.ref = "victools-json-schema" }
@@ -30,7 +28,6 @@ tink = "com.google.crypto.tink:tink:1.15.0"
3028
ksp-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
3129
protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }
3230
protobuf-kotlin = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "protobuf" }
33-
protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
3431
jwt = "com.nimbusds:nimbus-jose-jwt:9.47"
3532
opentelemetry-api = { module = "io.opentelemetry:opentelemetry-api", version.ref = "opentelemetry" }
3633
opentelemetry-kotlin = { module = "io.opentelemetry:opentelemetry-extension-kotlin", version.ref = "opentelemetry" }
@@ -44,6 +41,7 @@ log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "lo
4441
tomcat-annotations = "org.apache.tomcat:annotations-api:6.0.53"
4542
assertj = "org.assertj:assertj-core:3.26.0"
4643
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
44+
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
4745
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }
4846
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
4947
jspecify = "org.jspecify:jspecify:1.0.0"
@@ -62,9 +60,9 @@ jib = "com.google.cloud.tools.jib:3.4.4"
6260
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
6361
protobuf = "com.google.protobuf:0.9.4"
6462
aggregate-javadoc = "io.freefair.aggregate-javadoc:8.6"
65-
gradle-nexus-publish-plugin = "io.github.gradle-nexus.publish-plugin:1.3.0"
63+
nexus-publish = "io.github.gradle-nexus.publish-plugin:1.3.0"
6664
spring-dependency-management = "io.spring.dependency-management:1.1.6"
67-
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
65+
dokka = "org.jetbrains.dokka:1.9.20"
6866
jsonschema2pojo = "org.jsonschema2pojo:1.2.1"
6967
openapi-generator = "org.openapi.generator:7.5.0"
7068
spotless = "com.diffplug.spotless:6.25.0"

sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessor.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,25 @@ class ServiceProcessor(private val logger: KSPLogger, private val codeGenerator:
140140
.getClassDeclarationByName<dev.restate.sdk.annotation.Workflow>()!!
141141
.qualifiedName!!,
142142
ServiceType.WORKFLOW))
143+
144+
// Add spring annotations, if available
145+
resolver.getClassDeclarationByName("dev.restate.sdk.springboot.RestateService")?.let {
146+
metaAnnotationsToProcess.add(MetaRestateAnnotation(it.qualifiedName!!, ServiceType.SERVICE))
147+
}
148+
resolver.getClassDeclarationByName("dev.restate.sdk.springboot.RestateVirtualObject")?.let {
149+
metaAnnotationsToProcess.add(
150+
MetaRestateAnnotation(it.qualifiedName!!, ServiceType.VIRTUAL_OBJECT))
151+
}
152+
resolver.getClassDeclarationByName("dev.restate.sdk.springboot.RestateWorkflow")?.let {
153+
metaAnnotationsToProcess.add(MetaRestateAnnotation(it.qualifiedName!!, ServiceType.WORKFLOW))
154+
}
155+
143156
val discoveredAnnotations = mutableSetOf<String>()
144157

145158
var metaAnnotation = metaAnnotationsToProcess.removeFirstOrNull()
146159
while (metaAnnotation != null) {
147160
if (!discoveredAnnotations.add(metaAnnotation.annotationName.asString())) {
148-
// We alredy discovered it, skip
161+
// We already discovered it, skip
149162
continue
150163
}
151164
for (annotatedElement in
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
plugins {
2+
`kotlin-conventions`
3+
`library-publishing-conventions`
4+
alias(libs.plugins.ksp)
5+
alias(libs.plugins.spring.dependency.management)
6+
}
7+
8+
description = "Restate SDK Spring Boot Kotlin starter"
9+
10+
dependencies {
11+
compileOnly(libs.jspecify)
12+
13+
api(project(":sdk-api-kotlin"))
14+
api(project(":sdk-spring-boot"))
15+
16+
kspTest(project(":sdk-api-kotlin-gen"))
17+
testImplementation(project(":sdk-testing"))
18+
testImplementation(libs.kotlinx.coroutines.test)
19+
testImplementation(libs.spring.boot.starter.test)
20+
21+
// We need these for the deployment manifest
22+
testImplementation(project(":sdk-core"))
23+
testImplementation(libs.jackson.annotations)
24+
testImplementation(libs.jackson.databind)
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
2+
//
3+
// This file is part of the Restate Java SDK,
4+
// which is released under the MIT license.
5+
//
6+
// You can find a copy of the license in file LICENSE in the root
7+
// directory of this repository or package, or at
8+
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
9+
package dev.restate.sdk.springboot.kotlin
10+
11+
import dev.restate.sdk.annotation.Handler
12+
import dev.restate.sdk.kotlin.Context
13+
import dev.restate.sdk.springboot.RestateService
14+
import org.springframework.beans.factory.annotation.Value
15+
16+
@RestateService(name = "greeter")
17+
class Greeter {
18+
@Value("\${greetingPrefix}") lateinit var greetingPrefix: String
19+
20+
@Handler
21+
fun greet(ctx: Context, person: String): String {
22+
return greetingPrefix + person
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
2+
//
3+
// This file is part of the Restate Java SDK,
4+
// which is released under the MIT license.
5+
//
6+
// You can find a copy of the license in file LICENSE in the root
7+
// directory of this repository or package, or at
8+
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
9+
package dev.restate.sdk.springboot.kotlin
10+
11+
import com.fasterxml.jackson.databind.ObjectMapper
12+
import dev.restate.sdk.core.manifest.EndpointManifestSchema
13+
import dev.restate.sdk.core.manifest.Service
14+
import dev.restate.sdk.springboot.RestateHttpEndpointBean
15+
import java.io.IOException
16+
import java.net.URI
17+
import java.net.http.HttpClient
18+
import java.net.http.HttpRequest
19+
import java.net.http.HttpResponse
20+
import org.assertj.core.api.Assertions
21+
import org.assertj.core.api.Assertions.assertThat
22+
import org.junit.jupiter.api.Test
23+
import org.springframework.beans.factory.annotation.Autowired
24+
import org.springframework.boot.test.context.SpringBootTest
25+
26+
@SpringBootTest(
27+
classes = [RestateHttpEndpointBean::class, Greeter::class],
28+
properties = ["restate.sdk.http.port=0"])
29+
class RestateHttpEndpointBeanTest {
30+
@Autowired lateinit var restateHttpEndpointBean: RestateHttpEndpointBean
31+
32+
@Test
33+
@Throws(IOException::class, InterruptedException::class)
34+
fun httpEndpointShouldBeRunning() {
35+
assertThat(restateHttpEndpointBean.isRunning).isTrue()
36+
assertThat(restateHttpEndpointBean.actualPort()).isPositive()
37+
38+
// Check if discovery replies containing the Greeter service
39+
val client = HttpClient.newHttpClient()
40+
val response =
41+
client.send<String?>(
42+
HttpRequest.newBuilder()
43+
.GET()
44+
.uri(
45+
URI.create(
46+
("http://localhost:" + restateHttpEndpointBean.actualPort()).toString() +
47+
"/discover"))
48+
.header("Accept", "application/vnd.restate.endpointmanifest.v1+json")
49+
.build(),
50+
HttpResponse.BodyHandlers.ofString())
51+
Assertions.assertThat(response.statusCode()).isEqualTo(200)
52+
53+
val endpointManifest =
54+
ObjectMapper()
55+
.readValue<EndpointManifestSchema>(response.body(), EndpointManifestSchema::class.java)
56+
57+
Assertions.assertThat<Service?>(endpointManifest.services)
58+
.map<String>({ it.name })
59+
.containsOnly("greeter")
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
2+
//
3+
// This file is part of the Restate Java SDK,
4+
// which is released under the MIT license.
5+
//
6+
// You can find a copy of the license in file LICENSE in the root
7+
// directory of this repository or package, or at
8+
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
9+
package dev.restate.sdk.springboot.kotlin
10+
11+
import dev.restate.sdk.client.Client
12+
import dev.restate.sdk.testing.BindService
13+
import dev.restate.sdk.testing.RestateClient
14+
import dev.restate.sdk.testing.RestateTest
15+
import kotlinx.coroutines.test.runTest
16+
import org.assertj.core.api.Assertions
17+
import org.junit.jupiter.api.Test
18+
import org.junit.jupiter.api.Timeout
19+
import org.springframework.beans.factory.annotation.Autowired
20+
import org.springframework.boot.test.context.SpringBootTest
21+
22+
@SpringBootTest(classes = [Greeter::class], properties = ["greetingPrefix=Something something "])
23+
@RestateTest(containerImage = "ghcr.io/restatedev/restate:main")
24+
class SdkTestingIntegrationTest {
25+
@Autowired @BindService lateinit var greeter: Greeter
26+
27+
@Test
28+
@Timeout(value = 10)
29+
fun greet(@RestateClient ingressClient: Client) = runTest {
30+
val client = greeterClient.fromClient(ingressClient)
31+
32+
Assertions.assertThat(client.greet("Francesco")).isEqualTo("Something something Francesco")
33+
}
34+
}

sdk-spring-boot-starter/build.gradle.kts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,7 @@ description = "Restate SDK Spring Boot starter"
1111
dependencies {
1212
compileOnly(libs.jspecify)
1313

14-
api(project(":sdk-common")) {
15-
// Let spring bring jackson in
16-
exclude(group = "com.fasterxml.jackson")
17-
exclude(group = "com.fasterxml.jackson.core")
18-
exclude(group = "com.fasterxml.jackson.datatype")
19-
}
14+
api(project(":sdk-spring-boot"))
2015
api(project(":sdk-api")) {
2116
// Let spring bring jackson in
2217
exclude(group = "com.fasterxml.jackson")
@@ -30,25 +25,6 @@ dependencies {
3025
exclude(group = "com.fasterxml.jackson.datatype")
3126
}
3227

33-
implementation(project(":sdk-http-vertx")) {
34-
// Let spring bring jackson in
35-
exclude(group = "com.fasterxml.jackson")
36-
exclude(group = "com.fasterxml.jackson.core")
37-
exclude(group = "com.fasterxml.jackson.datatype")
38-
}
39-
implementation(project(":sdk-request-identity"))
40-
implementation(libs.vertx.core) {
41-
// Let spring bring jackson in
42-
exclude(group = "com.fasterxml.jackson")
43-
exclude(group = "com.fasterxml.jackson.core")
44-
exclude(group = "com.fasterxml.jackson.datatype")
45-
}
46-
47-
implementation(libs.spring.boot.starter)
48-
49-
// Spring is going to bring jackson in with this
50-
implementation(libs.spring.boot.starter.json)
51-
5228
// We need these for the deployment manifest
5329
testImplementation(project(":sdk-core"))
5430
testImplementation(libs.jackson.annotations)

sdk-spring-boot-starter/src/test/java/dev/restate/sdk/springboot/Greeter.java renamed to sdk-spring-boot-starter/src/test/java/dev/restate/sdk/springboot/java/Greeter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
// You can find a copy of the license in file LICENSE in the root
77
// directory of this repository or package, or at
88
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
9-
package dev.restate.sdk.springboot;
9+
package dev.restate.sdk.springboot.java;
1010

1111
import dev.restate.sdk.Context;
1212
import dev.restate.sdk.annotation.Handler;
13+
import dev.restate.sdk.springboot.RestateService;
1314
import org.springframework.beans.factory.annotation.Value;
1415

1516
@RestateService(name = "greeter")

sdk-spring-boot-starter/src/test/java/dev/restate/sdk/springboot/RestateHttpEndpointBeanTest.java renamed to sdk-spring-boot-starter/src/test/java/dev/restate/sdk/springboot/java/RestateHttpEndpointBeanTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
// You can find a copy of the license in file LICENSE in the root
77
// directory of this repository or package, or at
88
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
9-
package dev.restate.sdk.springboot;
9+
package dev.restate.sdk.springboot.java;
1010

1111
import static org.assertj.core.api.Assertions.assertThat;
1212

1313
import com.fasterxml.jackson.databind.ObjectMapper;
1414
import dev.restate.sdk.core.manifest.EndpointManifestSchema;
15+
import dev.restate.sdk.springboot.RestateHttpEndpointBean;
1516
import java.io.IOException;
1617
import java.net.URI;
1718
import java.net.http.HttpClient;

sdk-spring-boot-starter/src/test/java/dev/restate/sdk/springboot/SdkTestingIntegrationTest.java renamed to sdk-spring-boot-starter/src/test/java/dev/restate/sdk/springboot/java/SdkTestingIntegrationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// You can find a copy of the license in file LICENSE in the root
77
// directory of this repository or package, or at
88
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
9-
package dev.restate.sdk.springboot;
9+
package dev.restate.sdk.springboot.java;
1010

1111
import static org.assertj.core.api.Assertions.assertThat;
1212

sdk-spring-boot/build.gradle.kts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
plugins {
2+
`java-conventions`
3+
`java-library`
4+
`test-jar-conventions`
5+
`library-publishing-conventions`
6+
alias(libs.plugins.spring.dependency.management)
7+
}
8+
9+
description = "Restate SDK Spring Boot integration"
10+
11+
dependencies {
12+
compileOnly(libs.jspecify)
13+
14+
api(project(":sdk-common")) {
15+
// Let spring bring jackson in
16+
exclude(group = "com.fasterxml.jackson")
17+
exclude(group = "com.fasterxml.jackson.core")
18+
exclude(group = "com.fasterxml.jackson.datatype")
19+
}
20+
21+
implementation(project(":sdk-http-vertx")) {
22+
// Let spring bring jackson in
23+
exclude(group = "com.fasterxml.jackson")
24+
exclude(group = "com.fasterxml.jackson.core")
25+
exclude(group = "com.fasterxml.jackson.datatype")
26+
}
27+
implementation(project(":sdk-request-identity"))
28+
implementation(libs.vertx.core) {
29+
// Let spring bring jackson in
30+
exclude(group = "com.fasterxml.jackson")
31+
exclude(group = "com.fasterxml.jackson.core")
32+
exclude(group = "com.fasterxml.jackson.datatype")
33+
}
34+
35+
implementation(libs.spring.boot.starter)
36+
37+
// Spring is going to bring jackson in with this
38+
implementation(libs.spring.boot.starter.json)
39+
40+
testImplementation(libs.spring.boot.starter.test)
41+
}
42+
43+
tasks.withType<JavaCompile> { options.compilerArgs.add("-parameters") }

0 commit comments

Comments
 (0)