Skip to content

Commit

Permalink
Add spring boot demo application version 2.7.18 (#14)
Browse files Browse the repository at this point in the history
The goal is to build a demo application using spring boot that is equivalent to the kotlin one built with ktor.

- https://start.spring.io/ by default supports spring boot version 3.x. 3.x is not compatible with Java 8, so we downgraded the generated  app to 2.7.18 which is compatible. https://www.marcobehler.com/guides/spring-and-spring-boot-versions
- We changed the toolchain for `inngest-core` to Java 8 so that Java 8 apps can consume it. This meant we couldn't use Java httpclient which [was introduced in Java 11](https://openjdk.org/groups/net/httpclient/intro.html).
- We evaluated a couple of Http clients and went with OkHttp because its API resembles the Java 11 Http client the most (Other clients were Google Http client and Apache Http client)
- The current state is that the app syncs correctly to the dev server but when a function is called it fails on steps execution because of two issues that we'll be looking into next:
```
  Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: kotlin/enums/EnumEntriesKt] with root cause

java.lang.ClassNotFoundException: kotlin.enums.EnumEntriesKt
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[na:na]
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
	at com.inngest.OpCode.<clinit>(Function.kt:26) ~[main/:na]
	at com.inngest.InngestFunction.call(Function.kt:124) ~[main/:na]
	at com.inngest.CommHandler.callFunction(Comm.kt:85) ~[main/:na]
	at com.inngest.springbootdemo.InngestController.handleRequest(InngestController.java:40) ~[main/:na]
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:578) ~[na:na]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) 
```
- The second issue is that reified inline functions [cannot be called from Java](https://stackoverflow.com/a/42742119) and `step.run` currently is. 
https://github.com/inngest/inngest-kt/blob/e9a6a15498d742a775a8a5bbd107cada10d9bb31/inngest-core/src/main/kotlin/com/inngest/Step.kt#L28
  • Loading branch information
KiKoS0 authored Feb 23, 2024
1 parent 70191ad commit 26960ad
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 13 deletions.
7 changes: 6 additions & 1 deletion inngest-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ repositories {
dependencies {
implementation("com.beust:klaxon:5.5")
implementation("com.fasterxml.jackson.core:jackson-core:2.16.1")

implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1")
implementation("com.squareup.okhttp3:okhttp:4.12.0")

testImplementation(kotlin("test"))
}

Expand All @@ -20,4 +23,6 @@ tasks.named<Test>("test") {
}

// TODO - Move this to share conventions gradle file
java { toolchain { languageVersion.set(JavaLanguageVersion.of(20)) } }
java {
toolchain { languageVersion.set(JavaLanguageVersion.of(8)) }
}
36 changes: 25 additions & 11 deletions inngest-core/src/main/kotlin/com/inngest/Comm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package com.inngest

import com.beust.klaxon.Json
import com.beust.klaxon.Klaxon
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException

data class ExecutionRequestPayload(
val ctx: ExecutionContext,
Expand Down Expand Up @@ -125,17 +126,30 @@ class CommHandler(val functions: HashMap<String, InngestFunction>) {
v = "0.0.1",
functions = getFunctionConfigs(),
)
val requestBody = Klaxon().toJsonString(requestPayload)
val jsonRequestBody = Klaxon().toJsonString(requestPayload)

val client = OkHttpClient()

val jsonMediaType = "application/json".toMediaType()
val requestBody = jsonRequestBody.toRequestBody(jsonMediaType)

val client = HttpClient.newBuilder().build()
// TODO - Add headers?
val request =
HttpRequest.newBuilder()
.uri(URI.create(registrationUrl))
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
Request.Builder()
.url(registrationUrl).addHeader("Content-Type", "application/json")
.post(requestBody)
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
// TODO - Decode response and relay any message

client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")

// TODO - Decode response and relay any message
// for ((name, value) in response.headers) {
// println("$name: $value")
// }
//
// println(response.body!!.string())
}

// TODO - Add headers to output
val body: Map<String, Any?> = mapOf()
Expand Down
28 changes: 28 additions & 0 deletions inngest-spring-boot-demo/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
plugins {
java
id("org.springframework.boot") version "2.7.18"
id("io.spring.dependency-management") version "1.1.4"
}

group = "com.inngest"
version = "0.0.1"

java {
sourceCompatibility = JavaVersion.VERSION_1_8
}

repositories {
mavenCentral()
}

dependencies {
implementation(project(":inngest-core"))

implementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
useJUnitPlatform()
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.inngest.springbootdemo;

import com.inngest.CommResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/api", produces = MediaType.APPLICATION_JSON_VALUE)
public class InngestController {

private static final HttpHeaders commonHeaders = new HttpHeaders();

static {
String inngestSdk = "inngest-kt:v0.0.1";
commonHeaders.add("x-inngest-sdk", inngestSdk);
}

@GetMapping("/inngest")
public ResponseEntity<String> index() {
String response = InngestSingleton.getInstance().introspect();

return ResponseEntity.ok().headers(commonHeaders).body(response);
}

@PutMapping("/inngest")
public ResponseEntity<String> put() {
String response = InngestSingleton.getInstance().register();
return ResponseEntity.ok().headers(commonHeaders).body(response);
}

@PostMapping(value = "/inngest", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> handleRequest(
@RequestParam(name = "fnId") String functionId,
@RequestBody String body
) {
try {
CommResponse response = InngestSingleton.getInstance().callFunction(functionId, body);

return ResponseEntity.status(response.getStatusCode().getCode()).headers(commonHeaders)
.body(response.getBody());
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(e.toString());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.inngest.springbootdemo;

import com.inngest.*;
import kotlin.jvm.functions.Function2;

import java.time.Duration;
import java.util.HashMap;

import com.fasterxml.jackson.annotation.JsonProperty;


class Result {
@JsonProperty("sum")
private final int sum;

public Result(@JsonProperty("sum") int sum) {
this.sum = sum;
}

public int getSum() {
return sum;
}
}

// NOTE: We probably don't need this singleton anymore
// when revisiting the SDK's interface.
public class InngestSingleton {
private static CommHandler instance;

public static synchronized CommHandler getInstance() {
if (instance == null) {
FunctionTrigger fnTrigger = new FunctionTrigger("user-signup", null, null);
FunctionTrigger[] triggers = {fnTrigger};
FunctionOptions fnConfig = new FunctionOptions("fn-id-slug", "My function!", triggers);

Function2<FunctionContext, Step, Void> handler = (ctx, step) -> {
int x = 10;

System.out.println("-> handler called " + ctx.getEvent().getName());

// Steps types are problematic for now as it's not possible to called
// reified types from Java.

// int y = step.run("add-ten", () -> x + 10);

// Result res = step.run("cast-to-type-add-ten", () -> {
// System.out.println("-> running step 1!! " + x);
// // throw new Exception("An error!");
// return new Result(y + 10);
// });

// System.out.println("res" + res);
// int add = step.run("add-one-hundred", () -> {
// System.out.println("-> running step 2 :) " + (res != null ? res.getSum() : ""));
// return (res != null ? res.getSum() : 0) + 100;
// });

step.sleep("wait-one-sec", Duration.ofSeconds(2));

// step.run("last-step", () -> (res != null ? res.getSum() : 0) * add);

return null;
};
InngestFunction function = new InngestFunction(fnConfig, handler);

HashMap<String, InngestFunction> functions = new HashMap<>();
functions.put("fn-id-slug", function);

instance = new CommHandler(functions);
}
return instance;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.inngest.springbootdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootDemoApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
4 changes: 4 additions & 0 deletions inngest-spring-boot-demo/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
server:
port: 8080 # We probably want to change this to 8081 to avoid port conflicts
# but I kept it for now because of the hardcoded URL that are sent
# to the inngest dev-server.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.inngest.springbootdemo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(InngestController.class)
public class InngestControllerTest {

@Autowired
private MockMvc mockMvc;

@Test
public void shouldReturnSyncPayload() throws Exception {
mockMvc.perform(get("/api/inngest"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.appName").value("my-app"))
.andExpect(jsonPath("$.sdk").value("kotlin"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.inngest.springbootdemo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBootDemoApplicationTests {

@Test
void contextLoads() {
}

}
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ plugins {

rootProject.name = "inngest-sdk"

include("inngest-core", "inngest-test-server")
include("inngest-core", "inngest-test-server", "inngest-spring-boot-demo")

0 comments on commit 26960ad

Please sign in to comment.