diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 00000000..9e901050 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,289 @@ +# Migrating from the codegen API to the reflection-based API + +The **codegen API** — the `sdk-api-gen` annotation processor / `sdk-api-kotlin-gen` KSP generator, with +handlers taking a `Context` (or `ObjectContext`/`WorkflowContext`/…) first parameter — is **deprecated** +and will be removed in a future release. Prefer the **reflection-based API**: no annotation processor, and +handlers drop the `Context` parameter for the static `dev.restate.sdk.Restate` methods (Java) or the +top-level functions in `dev.restate.sdk.kotlin` (Kotlin). + +Both use the **same** annotations (`@Service`, `@Handler`, …), so migrating is mostly removing the +`Context` parameter and changing how you invoke other services. The two styles coexist, so you can migrate +one service at a time. + +--- + +## TL;DR — API mapping + +### Java: `Context` API → `Restate` API + +| Codegen / `Context` API | Reflection-based `Restate` API | +|-------------------------------------------------|-----------------------------------------------------------------------------------| +| `void greet(Context ctx, String req)` | `void greet(String req)` (drop the `Context` parameter) | +| `ctx.run(...)` / `ctx.runAsync(...)` | `Restate.run(...)` / `Restate.runAsync(...)` | +| `ctx.random()` | `Restate.random()` | +| `ctx.sleep(d)` / `ctx.timer(...)` | `Restate.sleep(d)` / `Restate.timer(...)` | +| `ctx.instantNow()` | `Restate.instantNow()` | +| `ctx.awakeable(...)` / `ctx.awakeableHandle(id)`| `Restate.awakeable(...)` / `Restate.awakeableHandle(id)` | +| `ctx.signal(...)` | `Restate.signal(...)` | +| `ctx.get(key)` / `ctx.set(key, v)` / `ctx.clear(key)` | `Restate.state().get(key)` / `Restate.state().set(key, v)` / `Restate.state().clear(key)` | +| `ctx.key()` | `Restate.key()` | +| `ctx.promise(key)` / `ctx.promiseHandle(key)` | `Restate.promise(key)` / `Restate.promiseHandle(key)` | +| `ctx.invocationHandle(id, ...)` | `Restate.invocationHandle(id, ...)` | +| Code-generated clients (Service) | `Restate.service(Class)` / `Restate.toService(Class)` | +| Code-generated clients (Virtual Object) | `Restate.virtualObject(Class, key)` / `Restate.toVirtualObject(Class, key)` | +| Code-generated clients (Workflow) | `Restate.workflow(Class, key)` / `Restate.toWorkflow(Class, key)` | + +From **outside** a handler (the ingress client), the equivalents live on `dev.restate.client.Client`: +`client.service(Class)` / `client.toService(Class)` / `client.virtualObject(Class, key)` / etc. + +### Kotlin: `Context` API → top-level functions + +| Codegen / `Context` API | Reflection-based top-level functions | +|-----------------------------------------------------|---------------------------------------------------------| +| `suspend fun greet(ctx: Context, req: String)` | `suspend fun greet(req: String)` (drop the `Context`) | +| `ctx.runBlock { ... }` / `ctx.runAsync { ... }` | `runBlock { ... }` / `runAsync { ... }` | +| `ctx.random()` | `random()` | +| `ctx.sleep(d)` / `ctx.timer(...)` | `sleep(d)` / `timer(...)` | +| `ctx.awakeable()` / `ctx.awakeableHandle(id)` | `awakeable()` / `awakeableHandle(id)` | +| `ctx.signal(name)` | `signal(name)` | +| `ctx.get(key)` / `ctx.set(key, v)` / `ctx.clear(key)` | `state().get(key)` / `state().set(key, v)` / `state().clear(key)` | +| `ctx.key()` | `objectKey()` / `workflowKey()` | +| `ctx.promise(key)` / `ctx.promiseHandle(key)` | `promise(key)` / `promiseHandle(key)` | +| Code-generated clients (Service) | `service()` / `toService()` | +| Code-generated clients (Virtual Object) | `virtualObject(key)` / `toVirtualObject(key)` | +| Code-generated clients (Workflow) | `workflow(key)` / `toWorkflow(key)` | + +All the top-level functions are in the `dev.restate.sdk.kotlin` package — add `import dev.restate.sdk.kotlin.*`. + +--- + +## Java migration + +### 1. Remove the annotation processor dependency + +Delete the `sdk-api-gen` annotation processor from your build; the reflection-based API needs no processor. + +```diff +- annotationProcessor("dev.restate:sdk-api-gen:") + implementation("dev.restate:sdk-java-http:") +``` + +### 2. Remove the `Context` parameter from your handlers + +Remove `Context` / `ObjectContext` / `SharedObjectContext` / `WorkflowContext` / `SharedWorkflowContext` +from your `@Handler`-annotated methods. The same applies to interfaces annotated with Restate annotations. + +```java +// Before +@VirtualObject +public class Counter { + @Handler + public void add(ObjectContext ctx, long request) {} + + @Shared + @Handler + public long get(SharedObjectContext ctx) {} +} + +// After +@VirtualObject +public class Counter { + @Handler + public void add(long request) {} + + @Shared + @Handler + public long get() {} +} +``` + +### 3. Replace `ctx.` calls with `Restate.` + +```java +// Before +@Handler +public void add(ObjectContext ctx, long value) { + long currentValue = ctx.get(TOTAL).orElse(0L); + ctx.set(TOTAL, currentValue + value); +} + +// After +@Handler +public void add(long value) { + var state = Restate.state(); + long currentValue = state.get(TOTAL).orElse(0L); + state.set(TOTAL, currentValue + value); +} +``` + +### 4. Replace code-generated clients + +**Simple proxy (direct calls):** + +```java +// Direct method call on a virtual object +Restate.virtualObject(Counter.class, "my-key").add(1); +``` + +**Handle-based (advanced patterns):** + +```java +// call() with a method reference returns a DurableFuture you can await and/or compose +int count = Restate.toVirtualObject(Counter.class, "my-counter") + .call(Counter::increment) + .await(); + +// send() for one-way invocation without waiting +InvocationHandle handle = Restate.toVirtualObject(Counter.class, "my-counter") + .send(Counter::increment); + +// Invocation options such as an idempotency key +int idempotentCount = Restate.toVirtualObject(Counter.class, "my-counter") + .call(Counter::increment, InvocationOptions.idempotencyKey("my-idempotency-key")) + .await(); +``` + +--- + +## Kotlin migration + +### 1. Remove the KSP code generator dependency + +```diff +- ksp("dev.restate:sdk-api-kotlin-gen:") + implementation("dev.restate:sdk-kotlin-http:") +``` + +You can also remove the `com.google.devtools.ksp` Gradle plugin if it's no longer used elsewhere. + +### 2. Remove the `Context` parameter from your handlers + +```kotlin +// Before +@VirtualObject +class Counter { + @Handler + suspend fun add(ctx: ObjectContext, value: Long) {} + + @Shared + @Handler + suspend fun get(ctx: SharedObjectContext): Long {} +} + +// After +import dev.restate.sdk.kotlin.* + +@VirtualObject +class Counter { + @Handler + suspend fun add(value: Long) {} + + @Shared + @Handler + suspend fun get(): Long {} +} +``` + +### 3. Replace `ctx.` calls with the top-level functions + +```kotlin +// Before +@Handler +suspend fun add(ctx: ObjectContext, value: Long) { + val currentValue = ctx.get(TOTAL) ?: 0L + ctx.set(TOTAL, currentValue + value) +} + +// After +@Handler +suspend fun add(value: Long) { + val state = state() + val currentValue = state.get(TOTAL) ?: 0L + state.set(TOTAL, currentValue + value) +} +``` + +### 4. Replace code-generated clients + +**Simple proxy (direct calls):** + +```kotlin +virtualObject("my-key").add(1) // Direct method call +``` + +**Handle-based (advanced patterns):** + +```kotlin +// call() with a lambda returns a DurableFuture you can await and/or compose +val count = toVirtualObject("my-counter") + .request { add(1) } + .call() + .await() + +// send() for one-way invocation without waiting +val handle = toVirtualObject("my-counter") + .request { add(1) } + .send() + +// Invocation options such as an idempotency key +val idempotentCount = toVirtualObject("my-counter") + .request { add(1) } + .options { idempotencyKey = "my-idempotency-key" } + .call() + .await() +``` + +### Kotlin gotcha: proxy clients need non-final classes + +The proxy clients (`service()`, `virtualObject(key)`, `toService()`, …) create a runtime proxy of +`T`. Kotlin classes are `final` by default, which prevents proxy generation. + +> **Using Kotlin + Spring Boot?** You don't need any of the below — the Restate Spring Boot Kotlin starter +> already applies the all-open plugin for the Restate annotations for you. + +Otherwise, pick one of: + +- **Define an interface** carrying the Restate annotations, implement it, and use the interface type for + proxies: `service()`. (Recommended.) +- Mark the annotated classes/methods `open`. +- Apply the [Kotlin all-open compiler plugin](https://kotlinlang.org/docs/all-open-plugin.html): + + ```kotlin + plugins { + kotlin("plugin.allopen") version "" + } + + allOpen { + annotations( + "dev.restate.sdk.annotation.Service", + "dev.restate.sdk.annotation.VirtualObject", + "dev.restate.sdk.annotation.Workflow") + } + ``` + + This makes any class annotated with a Restate annotation (and its methods) `open` automatically. + +--- + +## Deterministic time + +Use the deterministic clock instead of `Instant.now()` / `Clock.System.now()`: + +```java +// Java +Instant now = Restate.instantNow(); +``` + +```kotlin +// Kotlin — requires opting in to the experimental kotlin.time API +import kotlin.time.Clock +import kotlin.time.ExperimentalTime +import dev.restate.sdk.kotlin.* + +@OptIn(ExperimentalTime::class) +@Handler +suspend fun myHandler(): String { + val now = Clock.Restate.now() + return "Current time: $now" +} +``` diff --git a/README.md b/README.md index 3bb9e4e8..b0754248 100644 --- a/README.md +++ b/README.md @@ -43,15 +43,13 @@ Scaffold a project using the build tool of your choice. For example, with Gradle gradle init --type java-application ``` -Add the annotation processor dependency [sdk-api-gen](sdk-api-gen), and then, depending on whether you want to deploy using HTTP or Lambda, use the appropriate dependency: +Depending on whether you want to deploy using HTTP or Lambda, add the appropriate dependency: ```kotlin -annotationProcessor("dev.restate:sdk-api-gen:2.7.0") - // For HTTP services -implementation("dev.restate:sdk-java-http:2.7.0") +implementation("dev.restate:sdk-java-http:2.8.0") // For Lambda services -// implementation("dev.restate:sdk-java-lambda:2.7.0") +// implementation("dev.restate:sdk-java-lambda:2.8.0") ``` ### Setup a project (Kotlin) @@ -62,23 +60,13 @@ Scaffold a project using the build tool of your choice. For example, with Gradle gradle init --type kotlin-application ``` -Add the [Kotlin symbol processing](https://kotlinlang.org/docs/ksp-quickstart.html#use-your-own-processor-in-a-project) plugin: - -``` -plugins { - id("com.google.devtools.ksp") version "2.2.10-2.0.2" -} -``` - -Add the ksp dependency [sdk-api-gen](sdk-api-kotlin-gen), and then, depending on whether you want to deploy using HTTP or Lambda, use the appropriate dependency: +Depending on whether you want to deploy using HTTP or Lambda, add the appropriate dependency: ```kotlin -ksp("dev.restate:sdk-api-kotlin-gen:2.7.0") - // For HTTP services -implementation("dev.restate:sdk-kotlin-http:2.7.0") +implementation("dev.restate:sdk-kotlin-http:2.8.0") // For Lambda services -// implementation("dev.restate:sdk-kotlin-lambda:2.7.0") +// implementation("dev.restate:sdk-kotlin-lambda:2.8.0") ``` ### Implement your first Restate component (Java) @@ -86,7 +74,7 @@ implementation("dev.restate:sdk-kotlin-http:2.7.0") Implement your first virtual object in a new class, for example: ```java -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.common.StateKey; @@ -97,9 +85,10 @@ public class Greeter { private static final StateKey COUNT = StateKey.of("total", Long.class); @Handler - public String greet(ObjectContext ctx, String name) { - long count = ctx.get(COUNT).orElse(0L); - ctx.set(COUNT, count + 1); + public String greet(String name) { + var state = Restate.state(); + long count = state.get(COUNT).orElse(0L); + state.set(COUNT, count + 1); return String.format("Hello %s for the %d time!", name, count); } @@ -123,9 +112,10 @@ class Greeter { } @Handler - suspend fun greet(context: ObjectContext, name: String): String { - val count = context.get(COUNT) ?: 0L - context.set(COUNT, count + 1) + suspend fun greet(name: String): String { + val state = state() + val count = state.get(COUNT) ?: 0L + state.set(COUNT, count + 1) return "Hello $name for the $count time!" } } @@ -212,7 +202,7 @@ You can now upload the generated Jar in AWS Lambda, and configure `MyLambdaHandl The SDK uses log4j2 as logging facade, to configure it add the file `resources/log4j2.properties`: -``` +```properties # Set to debug or trace if log4j initialization is failing status = warn @@ -251,37 +241,6 @@ The dependencies `sdk-java-http`, `sdk-java-lambda`, `sdk-kotlin-http` and `sdk- When assembling fat-jars, make sure to enable merging META-INF/services files. For more info, see https://github.com/apache/logging-log4j2/issues/2099. -#### Tracing with OpenTelemetry - -The SDK automatically propagates the OpenTelemetry `Context` from the `restate-server` into your handler. You can use that to create custom spans. - -To configure the `OpenTelemetry` that should be used by the SDK to publish traces, configure it in the `Endpoint.Builder` object. - -For example, to set up tracing using environment variables, add the following modules to your dependencies: - -``` -implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.38.0") -implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.38.0") -``` - -And then configure it in the endpoint builder: - -```java -.withOpenTelemetry(AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk()) -``` - -By exporting the following environment variables the OpenTelemetry SDK will be automatically configured to push traces: - -```shell -export OTEL_SERVICE_NAME=my-service -export OTEL_TRACES_SAMPLER=always_on -export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:14250 -``` - -Please refer to the [Opentelemetry manual instrumentation documentation](https://opentelemetry.io/docs/instrumentation/java/manual/#manual-instrumentation-setup) and the [autoconfigure documentation](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md) for more info. - -See https://docs.restate.dev/operate/monitoring/tracing to configure Restate tracing. - ## Versions This library follows [Semantic Versioning](https://semver.org/). diff --git a/client-kotlin/src/main/kotlin/dev/restate/client/kotlin/ingress.kt b/client-kotlin/src/main/kotlin/dev/restate/client/kotlin/ingress.kt index cfd2b725..b21f4ec3 100644 --- a/client-kotlin/src/main/kotlin/dev/restate/client/kotlin/ingress.kt +++ b/client-kotlin/src/main/kotlin/dev/restate/client/kotlin/ingress.kt @@ -283,7 +283,6 @@ val SendResponse.sendStatus: SendResponse.SendStatus * @param SVC the service class annotated with @Service * @return a proxy client to invoke the service */ -@org.jetbrains.annotations.ApiStatus.Experimental inline fun Client.service(): SVC { return service(this, SVC::class.java) } @@ -301,7 +300,6 @@ inline fun Client.service(): SVC { * @param key the key identifying the specific virtual object instance * @return a proxy client to invoke the virtual object */ -@org.jetbrains.annotations.ApiStatus.Experimental inline fun Client.virtualObject(key: String): SVC { return virtualObject(this, SVC::class.java, key) } @@ -319,7 +317,6 @@ inline fun Client.virtualObject(key: String): SVC { * @param key the key identifying the specific workflow instance * @return a proxy client to invoke the workflow */ -@org.jetbrains.annotations.ApiStatus.Experimental inline fun Client.workflow(key: String): SVC { return workflow(this, SVC::class.java, key) } @@ -411,7 +408,6 @@ internal fun workflow(client: Client, clazz: Class, key: String * * @param SVC the service/virtual object/workflow class */ -@org.jetbrains.annotations.ApiStatus.Experimental class KClientRequestBuilder @PublishedApi internal constructor( @@ -452,7 +448,6 @@ internal constructor( * @param Req the request type * @param Res the response type */ -@org.jetbrains.annotations.ApiStatus.Experimental interface KClientRequest : Request { /** @@ -492,7 +487,6 @@ interface KClientRequest : Request { * @param SVC the service class annotated with @Service * @return a builder for creating typed requests */ -@org.jetbrains.annotations.ApiStatus.Experimental inline fun Client.toService(): KClientRequestBuilder { ReflectionUtils.mustHaveServiceAnnotation(SVC::class.java) require(ReflectionUtils.isKotlinClass(SVC::class.java)) { @@ -515,7 +509,6 @@ inline fun Client.toService(): KClientRequestBuilder { * @param key the key identifying the specific virtual object instance * @return a builder for creating typed requests */ -@org.jetbrains.annotations.ApiStatus.Experimental inline fun Client.toVirtualObject(key: String): KClientRequestBuilder { ReflectionUtils.mustHaveVirtualObjectAnnotation(SVC::class.java) require(ReflectionUtils.isKotlinClass(SVC::class.java)) { @@ -538,7 +531,6 @@ inline fun Client.toVirtualObject(key: String): KClientReque * @param key the key identifying the specific workflow instance * @return a builder for creating typed requests */ -@org.jetbrains.annotations.ApiStatus.Experimental inline fun Client.toWorkflow(key: String): KClientRequestBuilder { ReflectionUtils.mustHaveWorkflowAnnotation(SVC::class.java) require(ReflectionUtils.isKotlinClass(SVC::class.java)) { diff --git a/client/src/main/java/dev/restate/client/Client.java b/client/src/main/java/dev/restate/client/Client.java index 3fa5cee0..287097e8 100644 --- a/client/src/main/java/dev/restate/client/Client.java +++ b/client/src/main/java/dev/restate/client/Client.java @@ -532,7 +532,7 @@ default Response> getOutput() throws IngressException { } /** - * EXPERIMENTAL API: Simple API to invoke a Restate service from the ingress. + * Simple API to invoke a Restate service from the ingress. * *

Create a proxy client that allows calling service methods directly and synchronously, * returning just the output (not wrapped in {@link Response}). This is the recommended approach @@ -552,7 +552,6 @@ default Response> getOutput() throws IngressException { * @param clazz the service class annotated with {@link Service} * @return a proxy client to invoke the service */ - @org.jetbrains.annotations.ApiStatus.Experimental default SVC service(Class clazz) { ReflectionUtils.mustHaveServiceAnnotation(clazz); if (ReflectionUtils.isKotlinClass(clazz)) { @@ -576,8 +575,7 @@ default SVC service(Class clazz) { } /** - * EXPERIMENTAL API: Advanced API to invoke a Restate service from the ingress with full - * control. + * Advanced API to invoke a Restate service from the ingress with full control. * *

Create a handle that provides advanced invocation capabilities including: * @@ -592,11 +590,11 @@ default SVC service(Class clazz) { * Client client = Client.connect("http://localhost:8080"); * * // Use call() with method reference and wait for the result - * Response response = client.serviceHandle(Greeter.class) + * Response response = client.toService(Greeter.class) * .call(Greeter::greet, new Greeting("Alice")); * * // Use send() for one-way invocation without waiting - * SendResponse sendResponse = client.serviceHandle(Greeter.class) + * SendResponse sendResponse = client.toService(Greeter.class) * .send(Greeter::greet, new Greeting("Alice")); * } * @@ -606,8 +604,7 @@ default SVC service(Class clazz) { * @param clazz the service class annotated with {@link Service} * @return a handle to invoke the service with advanced options */ - @org.jetbrains.annotations.ApiStatus.Experimental - default ClientServiceHandle serviceHandle(Class clazz) { + default ClientServiceHandle toService(Class clazz) { ReflectionUtils.mustHaveServiceAnnotation(clazz); if (ReflectionUtils.isKotlinClass(clazz)) { throw new IllegalArgumentException("Using Kotlin classes with Java's API is not supported"); @@ -616,7 +613,15 @@ default ClientServiceHandle serviceHandle(Class clazz) { } /** - * EXPERIMENTAL API: Simple API to invoke a Restate Virtual Object from the ingress. + * @deprecated Renamed to {@link #toService(Class)}. + */ + @Deprecated(since = "2.9", forRemoval = true) + default ClientServiceHandle serviceHandle(Class clazz) { + return toService(clazz); + } + + /** + * Simple API to invoke a Restate Virtual Object from the ingress. * *

Create a proxy client that allows calling virtual object methods directly and synchronously, * returning just the output (not wrapped in {@link Response}). This is the recommended approach @@ -630,14 +635,13 @@ default ClientServiceHandle serviceHandle(Class clazz) { * } * *

For advanced use cases requiring asynchronous request handling, access to {@link Response} - * metadata, or invocation options (such as idempotency keys), use {@link - * #virtualObjectHandle(Class, String)} instead. + * metadata, or invocation options (such as idempotency keys), use {@link #toVirtualObject(Class, + * String)} instead. * * @param clazz the virtual object class annotated with {@link VirtualObject} * @param key the key identifying the specific virtual object instance * @return a proxy client to invoke the virtual object */ - @org.jetbrains.annotations.ApiStatus.Experimental default SVC virtualObject(Class clazz, String key) { ReflectionUtils.mustHaveVirtualObjectAnnotation(clazz); if (ReflectionUtils.isKotlinClass(clazz)) { @@ -661,8 +665,7 @@ default SVC virtualObject(Class clazz, String key) { } /** - * EXPERIMENTAL API: Advanced API to invoke a Restate Virtual Object from the ingress with - * full control. + * Advanced API to invoke a Restate Virtual Object from the ingress with full control. * *

Create a handle that provides advanced invocation capabilities including: * @@ -677,11 +680,11 @@ default SVC virtualObject(Class clazz, String key) { * Client client = Client.connect("http://localhost:8080"); * * // Use call() with method reference and wait for the result - * Response response = client.virtualObjectHandle(Counter.class, "my-counter") + * Response response = client.toVirtualObject(Counter.class, "my-counter") * .call(Counter::increment); * * // Use send() for one-way invocation without waiting - * SendResponse sendResponse = client.virtualObjectHandle(Counter.class, "my-counter") + * SendResponse sendResponse = client.toVirtualObject(Counter.class, "my-counter") * .send(Counter::increment); * } * @@ -692,8 +695,7 @@ default SVC virtualObject(Class clazz, String key) { * @param key the key identifying the specific virtual object instance * @return a handle to invoke the virtual object with advanced options */ - @org.jetbrains.annotations.ApiStatus.Experimental - default ClientServiceHandle virtualObjectHandle(Class clazz, String key) { + default ClientServiceHandle toVirtualObject(Class clazz, String key) { ReflectionUtils.mustHaveVirtualObjectAnnotation(clazz); if (ReflectionUtils.isKotlinClass(clazz)) { throw new IllegalArgumentException("Using Kotlin classes with Java's API is not supported"); @@ -702,7 +704,15 @@ default ClientServiceHandle virtualObjectHandle(Class clazz, Str } /** - * EXPERIMENTAL API: Simple API to invoke a Restate Workflow from the ingress. + * @deprecated Renamed to {@link #toVirtualObject(Class, String)}. + */ + @Deprecated(since = "2.9", forRemoval = true) + default ClientServiceHandle virtualObjectHandle(Class clazz, String key) { + return toVirtualObject(clazz, key); + } + + /** + * Simple API to invoke a Restate Workflow from the ingress. * *

Create a proxy client that allows calling workflow methods directly and synchronously, * returning just the output (not wrapped in {@link Response}). This is the recommended approach @@ -716,14 +726,13 @@ default ClientServiceHandle virtualObjectHandle(Class clazz, Str * } * *

For advanced use cases requiring asynchronous request handling, access to {@link Response} - * metadata, or invocation options (such as idempotency keys), use {@link #workflowHandle(Class, + * metadata, or invocation options (such as idempotency keys), use {@link #toWorkflow(Class, * String)} instead. * * @param clazz the workflow class annotated with {@link Workflow} * @param key the key identifying the specific workflow instance * @return a proxy client to invoke the workflow */ - @org.jetbrains.annotations.ApiStatus.Experimental default SVC workflow(Class clazz, String key) { ReflectionUtils.mustHaveWorkflowAnnotation(clazz); if (ReflectionUtils.isKotlinClass(clazz)) { @@ -747,8 +756,7 @@ default SVC workflow(Class clazz, String key) { } /** - * EXPERIMENTAL API: Advanced API to invoke a Restate Workflow from the ingress with full - * control. + * Advanced API to invoke a Restate Workflow from the ingress with full control. * *

Create a handle that provides advanced invocation capabilities including: * @@ -763,11 +771,11 @@ default SVC workflow(Class clazz, String key) { * Client client = Client.connect("http://localhost:8080"); * * // Use call() with method reference and wait for the result - * Response response = client.workflowHandle(OrderWorkflow.class, "order-123") + * Response response = client.toWorkflow(OrderWorkflow.class, "order-123") * .call(OrderWorkflow::start, new OrderRequest(...)); * * // Use send() for one-way invocation without waiting - * SendResponse sendResponse = client.workflowHandle(OrderWorkflow.class, "order-123") + * SendResponse sendResponse = client.toWorkflow(OrderWorkflow.class, "order-123") * .send(OrderWorkflow::start, new OrderRequest(...)); * } * @@ -778,8 +786,7 @@ default SVC workflow(Class clazz, String key) { * @param key the key identifying the specific workflow instance * @return a handle to invoke the workflow with advanced options */ - @org.jetbrains.annotations.ApiStatus.Experimental - default ClientServiceHandle workflowHandle(Class clazz, String key) { + default ClientServiceHandle toWorkflow(Class clazz, String key) { ReflectionUtils.mustHaveWorkflowAnnotation(clazz); if (ReflectionUtils.isKotlinClass(clazz)) { throw new IllegalArgumentException("Using Kotlin classes with Java's API is not supported"); @@ -787,6 +794,14 @@ default ClientServiceHandle workflowHandle(Class clazz, String k return new ClientServiceHandleImpl<>(this, clazz, key); } + /** + * @deprecated Renamed to {@link #toWorkflow(Class, String)}. + */ + @Deprecated(since = "2.9", forRemoval = true) + default ClientServiceHandle workflowHandle(Class clazz, String key) { + return toWorkflow(clazz, key); + } + /** * Create a default JDK client. * diff --git a/client/src/main/java/dev/restate/client/ClientServiceHandle.java b/client/src/main/java/dev/restate/client/ClientServiceHandle.java index dba5e153..39019a44 100644 --- a/client/src/main/java/dev/restate/client/ClientServiceHandle.java +++ b/client/src/main/java/dev/restate/client/ClientServiceHandle.java @@ -18,11 +18,8 @@ import java.util.function.Function; /** - * EXPERIMENTAL API: This interface is part of the new reflection-based API and may change in - * future releases. - * - *

Advanced API handle for invoking Restate services, virtual objects, or workflows from the - * ingress (outside of a handler). This handle provides advanced invocation capabilities including: + * Advanced API handle for invoking Restate services, virtual objects, or workflows from the ingress + * (outside of a handler). This handle provides advanced invocation capabilities including: * *

    *
  • Async request handling with {@link CompletableFuture} @@ -37,16 +34,16 @@ * Client client = Client.connect("http://localhost:8080"); * * // 1. Use call() with method reference and wait for the result - * Response response = client.serviceHandle(Greeter.class) + * Response response = client.toService(Greeter.class) * .call(Greeter::greet, new Greeting("Alice")); * * // 2. Use send() for one-way invocation without waiting - * SendResponse sendResponse = client.serviceHandle(Greeter.class) + * SendResponse sendResponse = client.toService(Greeter.class) * .send(Greeter::greet, new Greeting("Alice")); * } * - *

    Create instances using {@link Client#serviceHandle(Class)}, {@link - * Client#virtualObjectHandle(Class, String)}, or {@link Client#workflowHandle(Class, String)}. + *

    Create instances using {@link Client#toService(Class)}, {@link Client#toVirtualObject(Class, + * String)}, or {@link Client#toWorkflow(Class, String)}. * *

    For simple synchronous request-response interactions returning just the output, consider using * the simple proxy API instead: {@link Client#service(Class)}, {@link Client#virtualObject(Class, @@ -54,10 +51,9 @@ * * @param the service interface type */ -@org.jetbrains.annotations.ApiStatus.Experimental public interface ClientServiceHandle { /** - * EXPERIMENTAL API: Invoke a service method with input and wait for the response. + * Invoke a service method with input and wait for the response. * *

    {@code
        * // Call with method reference and input
    @@ -69,18 +65,15 @@ public interface ClientServiceHandle {
        * @param input the input parameter to pass to the method
        * @return a {@link Response} wrapping the result
        */
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  Response call(BiFunction s, I input) {
         return call(s, input, InvocationOptions.DEFAULT);
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  Response call(
           BiFunction s, I input, InvocationOptions.Builder options) {
         return call(s, input, options.build());
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  Response call(
           BiFunction s, I input, InvocationOptions invocationOptions) {
         try {
    @@ -94,18 +87,15 @@ default  Response call(
       }
     
       // call - BiConsumer variants
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  Response call(BiConsumer s, I input) {
         return call(s, input, InvocationOptions.DEFAULT);
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  Response call(
           BiConsumer s, I input, InvocationOptions.Builder options) {
         return call(s, input, options.build());
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  Response call(
           BiConsumer s, I input, InvocationOptions invocationOptions) {
         try {
    @@ -119,17 +109,14 @@ default  Response call(
       }
     
       // call - Function variants
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  Response call(Function s) {
         return call(s, InvocationOptions.DEFAULT);
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  Response call(Function s, InvocationOptions.Builder options) {
         return call(s, options.build());
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  Response call(Function s, InvocationOptions invocationOptions) {
         try {
           return callAsync(s, invocationOptions).join();
    @@ -142,17 +129,14 @@ default  Response call(Function s, InvocationOptions invocationOpt
       }
     
       // call - Consumer variants
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default Response call(Consumer s) {
         return call(s, InvocationOptions.DEFAULT);
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default Response call(Consumer s, InvocationOptions.Builder options) {
         return call(s, options.build());
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default Response call(Consumer s, InvocationOptions invocationOptions) {
         try {
           return callAsync(s, invocationOptions).join();
    @@ -165,71 +149,59 @@ default Response call(Consumer s, InvocationOptions invocationOptions
       }
     
       // callAsync - BiFunction variants
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  CompletableFuture> callAsync(BiFunction s, I input) {
         return callAsync(s, input, InvocationOptions.DEFAULT);
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  CompletableFuture> callAsync(
           BiFunction s, I input, InvocationOptions.Builder options) {
         return callAsync(s, input, options.build());
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
        CompletableFuture> callAsync(
           BiFunction s, I input, InvocationOptions invocationOptions);
     
       // callAsync - BiConsumer variants
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  CompletableFuture> callAsync(BiConsumer s, I input) {
         return callAsync(s, input, InvocationOptions.DEFAULT);
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  CompletableFuture> callAsync(
           BiConsumer s, I input, InvocationOptions.Builder options) {
         return callAsync(s, input, options.build());
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
        CompletableFuture> callAsync(
           BiConsumer s, I input, InvocationOptions invocationOptions);
     
       // callAsync - Function variants
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  CompletableFuture> callAsync(Function s) {
         return callAsync(s, InvocationOptions.DEFAULT);
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default  CompletableFuture> callAsync(
           Function s, InvocationOptions.Builder options) {
         return callAsync(s, options.build());
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
        CompletableFuture> callAsync(
           Function s, InvocationOptions invocationOptions);
     
       // callAsync - Consumer variants
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default CompletableFuture> callAsync(Consumer s) {
         return callAsync(s, InvocationOptions.DEFAULT);
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       default CompletableFuture> callAsync(
           Consumer s, InvocationOptions.Builder options) {
         return callAsync(s, options.build());
       }
     
    -  @org.jetbrains.annotations.ApiStatus.Experimental
       CompletableFuture> callAsync(Consumer s, InvocationOptions invocationOptions);
     
       // send - BiFunction variants
       /**
    -   * EXPERIMENTAL API: Send a one-way invocation without waiting for the response.
    +   * Send a one-way invocation without waiting for the response.
        *
        * 
    {@code
        * // Send without waiting for response
    @@ -237,46 +209,34 @@ default CompletableFuture> callAsync(
        *   .send(Greeter::greet, new Greeting("Alice"));
        * }
    */ - @org.jetbrains.annotations.ApiStatus.Experimental default SendResponse send(BiFunction s, I input) { return send(s, input, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiFunction, Object)}, with invocation options. */ default SendResponse send( BiFunction s, I input, InvocationOptions.Builder options) { return send(s, input, options.build()); } - /** EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiFunction, Object)}, with invocation options. */ default SendResponse send( BiFunction s, I input, InvocationOptions invocationOptions) { return send(s, input, null, invocationOptions); } - /** EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, with a delay. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiFunction, Object)}, with a delay. */ default SendResponse send(BiFunction s, I input, Duration delay) { return send(s, input, delay, InvocationOptions.DEFAULT); } - /** - * EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, with a delay and invocation - * options. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiFunction, Object)}, with a delay and invocation options. */ default SendResponse send( BiFunction s, I input, Duration delay, InvocationOptions.Builder options) { return send(s, input, delay, options.build()); } - /** - * EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, with a delay and invocation - * options. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiFunction, Object)}, with a delay and invocation options. */ default SendResponse send( BiFunction s, I input, Duration delay, InvocationOptions invocationOptions) { try { @@ -290,50 +250,35 @@ default SendResponse send( } // send - BiConsumer variants - /** - * EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, for methods without a return - * value. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiFunction, Object)}, for methods without a return value. */ default SendResponse send(BiConsumer s, I input) { return send(s, input, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #send(BiConsumer, Object)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiConsumer, Object)}, with invocation options. */ default SendResponse send( BiConsumer s, I input, InvocationOptions.Builder options) { return send(s, input, options.build()); } - /** EXPERIMENTAL API: Like {@link #send(BiConsumer, Object)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiConsumer, Object)}, with invocation options. */ default SendResponse send( BiConsumer s, I input, InvocationOptions invocationOptions) { return send(s, input, null, invocationOptions); } - /** EXPERIMENTAL API: Like {@link #send(BiConsumer, Object)}, with a delay. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiConsumer, Object)}, with a delay. */ default SendResponse send(BiConsumer s, I input, Duration delay) { return send(s, input, delay, InvocationOptions.DEFAULT); } - /** - * EXPERIMENTAL API: Like {@link #send(BiConsumer, Object)}, with a delay and invocation - * options. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiConsumer, Object)}, with a delay and invocation options. */ default SendResponse send( BiConsumer s, I input, Duration delay, InvocationOptions.Builder options) { return send(s, input, delay, options.build()); } - /** - * EXPERIMENTAL API: Like {@link #send(BiConsumer, Object)}, with a delay and invocation - * options. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiConsumer, Object)}, with a delay and invocation options. */ default SendResponse send( BiConsumer s, I input, Duration delay, InvocationOptions invocationOptions) { try { @@ -347,39 +292,33 @@ default SendResponse send( } // send - Function variants - /** EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, for methods without input. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiFunction, Object)}, for methods without input. */ default SendResponse send(Function s) { return send(s, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #send(Function)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(Function)}, with invocation options. */ default SendResponse send(Function s, InvocationOptions.Builder options) { return send(s, options.build()); } - /** EXPERIMENTAL API: Like {@link #send(Function)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(Function)}, with invocation options. */ default SendResponse send(Function s, InvocationOptions invocationOptions) { return send(s, null, invocationOptions); } - /** EXPERIMENTAL API: Like {@link #send(Function)}, with a delay. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(Function)}, with a delay. */ default SendResponse send(Function s, Duration delay) { return send(s, delay, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #send(Function)}, with a delay and invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(Function)}, with a delay and invocation options. */ default SendResponse send( Function s, Duration delay, InvocationOptions.Builder options) { return send(s, delay, options.build()); } - /** EXPERIMENTAL API: Like {@link #send(Function)}, with a delay and invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(Function)}, with a delay and invocation options. */ default SendResponse send( Function s, Duration delay, InvocationOptions invocationOptions) { try { @@ -393,42 +332,33 @@ default SendResponse send( } // send - Consumer variants - /** - * EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, for methods without input or - * return value. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(BiFunction, Object)}, for methods without input or return value. */ default SendResponse send(Consumer s) { return send(s, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #send(Consumer)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(Consumer)}, with invocation options. */ default SendResponse send(Consumer s, InvocationOptions.Builder options) { return send(s, options.build()); } - /** EXPERIMENTAL API: Like {@link #send(Consumer)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(Consumer)}, with invocation options. */ default SendResponse send(Consumer s, InvocationOptions invocationOptions) { return send(s, null, invocationOptions); } - /** EXPERIMENTAL API: Like {@link #send(Consumer)}, with a delay. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(Consumer)}, with a delay. */ default SendResponse send(Consumer s, Duration delay) { return send(s, delay, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #send(Consumer)}, with a delay and invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(Consumer)}, with a delay and invocation options. */ default SendResponse send( Consumer s, Duration delay, InvocationOptions.Builder options) { return send(s, delay, options.build()); } - /** EXPERIMENTAL API: Like {@link #send(Consumer)}, with a delay and invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #send(Consumer)}, with a delay and invocation options. */ default SendResponse send( Consumer s, Duration delay, InvocationOptions invocationOptions) { try { @@ -442,171 +372,136 @@ default SendResponse send( } // sendAsync - BiFunction variants - /** EXPERIMENTAL API: Async version of {@link #send(BiFunction, Object)}. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Async version of {@link #send(BiFunction, Object)}. */ default CompletableFuture> sendAsync(BiFunction s, I input) { return sendAsync(s, input, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #sendAsync(BiFunction, Object)}, with options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiFunction, Object)}, with options. */ default CompletableFuture> sendAsync( BiFunction s, I input, InvocationOptions.Builder options) { return sendAsync(s, input, options.build()); } - /** EXPERIMENTAL API: Like {@link #sendAsync(BiFunction, Object)}, with options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiFunction, Object)}, with options. */ default CompletableFuture> sendAsync( BiFunction s, I input, InvocationOptions invocationOptions) { return sendAsync(s, input, null, invocationOptions); } - /** EXPERIMENTAL API: Like {@link #sendAsync(BiFunction, Object)}, with a delay. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiFunction, Object)}, with a delay. */ default CompletableFuture> sendAsync( BiFunction s, I input, Duration delay) { return sendAsync(s, input, delay, InvocationOptions.DEFAULT); } - /** - * EXPERIMENTAL API: Like {@link #sendAsync(BiFunction, Object)}, with delay and options. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiFunction, Object)}, with delay and options. */ default CompletableFuture> sendAsync( BiFunction s, I input, Duration delay, InvocationOptions.Builder options) { return sendAsync(s, input, delay, options.build()); } - /** - * EXPERIMENTAL API: Like {@link #sendAsync(BiFunction, Object)}, with delay and options. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiFunction, Object)}, with delay and options. */ CompletableFuture> sendAsync( BiFunction s, I input, Duration delay, InvocationOptions invocationOptions); // sendAsync - BiConsumer variants - /** EXPERIMENTAL API: Like {@link #sendAsync(BiFunction, Object)}, for void methods. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiFunction, Object)}, for void methods. */ default CompletableFuture> sendAsync(BiConsumer s, I input) { return sendAsync(s, input, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #sendAsync(BiConsumer, Object)}, with options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiConsumer, Object)}, with options. */ default CompletableFuture> sendAsync( BiConsumer s, I input, InvocationOptions.Builder options) { return sendAsync(s, input, options.build()); } - /** EXPERIMENTAL API: Like {@link #sendAsync(BiConsumer, Object)}, with options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiConsumer, Object)}, with options. */ default CompletableFuture> sendAsync( BiConsumer s, I input, InvocationOptions invocationOptions) { return sendAsync(s, input, null, invocationOptions); } - /** EXPERIMENTAL API: Like {@link #sendAsync(BiConsumer, Object)}, with a delay. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiConsumer, Object)}, with a delay. */ default CompletableFuture> sendAsync( BiConsumer s, I input, Duration delay) { return sendAsync(s, input, delay, InvocationOptions.DEFAULT); } - /** - * EXPERIMENTAL API: Like {@link #sendAsync(BiConsumer, Object)}, with delay and options. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiConsumer, Object)}, with delay and options. */ default CompletableFuture> sendAsync( BiConsumer s, I input, Duration delay, InvocationOptions.Builder options) { return sendAsync(s, input, delay, options.build()); } - /** - * EXPERIMENTAL API: Like {@link #sendAsync(BiConsumer, Object)}, with delay and options. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiConsumer, Object)}, with delay and options. */ CompletableFuture> sendAsync( BiConsumer s, I input, Duration delay, InvocationOptions invocationOptions); // sendAsync - Function variants - /** EXPERIMENTAL API: Like {@link #sendAsync(BiFunction, Object)}, for no-input methods. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiFunction, Object)}, for no-input methods. */ default CompletableFuture> sendAsync(Function s) { return sendAsync(s, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #sendAsync(Function)}, with options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(Function)}, with options. */ default CompletableFuture> sendAsync( Function s, InvocationOptions.Builder options) { return sendAsync(s, options.build()); } - /** EXPERIMENTAL API: Like {@link #sendAsync(Function)}, with options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(Function)}, with options. */ default CompletableFuture> sendAsync( Function s, InvocationOptions invocationOptions) { return sendAsync(s, null, invocationOptions); } - /** EXPERIMENTAL API: Like {@link #sendAsync(Function)}, with a delay. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(Function)}, with a delay. */ default CompletableFuture> sendAsync(Function s, Duration delay) { return sendAsync(s, delay, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #sendAsync(Function)}, with delay and options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(Function)}, with delay and options. */ default CompletableFuture> sendAsync( Function s, Duration delay, InvocationOptions.Builder options) { return sendAsync(s, delay, options.build()); } - /** EXPERIMENTAL API: Like {@link #sendAsync(Function)}, with delay and options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(Function)}, with delay and options. */ CompletableFuture> sendAsync( Function s, Duration delay, InvocationOptions invocationOptions); // sendAsync - Consumer variants - /** - * EXPERIMENTAL API: Like {@link #sendAsync(BiFunction, Object)}, for no-input/void - * methods. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(BiFunction, Object)}, for no-input/void methods. */ default CompletableFuture> sendAsync(Consumer s) { return sendAsync(s, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #sendAsync(Consumer)}, with options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(Consumer)}, with options. */ default CompletableFuture> sendAsync( Consumer s, InvocationOptions.Builder options) { return sendAsync(s, options.build()); } - /** EXPERIMENTAL API: Like {@link #sendAsync(Consumer)}, with options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(Consumer)}, with options. */ default CompletableFuture> sendAsync( Consumer s, InvocationOptions invocationOptions) { return sendAsync(s, null, invocationOptions); } - /** EXPERIMENTAL API: Like {@link #sendAsync(Consumer)}, with a delay. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(Consumer)}, with a delay. */ default CompletableFuture> sendAsync(Consumer s, Duration delay) { return sendAsync(s, delay, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #sendAsync(Consumer)}, with delay and options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(Consumer)}, with delay and options. */ default CompletableFuture> sendAsync( Consumer s, Duration delay, InvocationOptions.Builder options) { return sendAsync(s, delay, options.build()); } - /** EXPERIMENTAL API: Like {@link #sendAsync(Consumer)}, with delay and options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #sendAsync(Consumer)}, with delay and options. */ CompletableFuture> sendAsync( Consumer s, Duration delay, InvocationOptions invocationOptions); } diff --git a/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java b/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java index ab8f0a8c..df5fec2a 100644 --- a/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java +++ b/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java @@ -84,7 +84,7 @@ public String run(LoanRequest loanRequest) { Instant executionTime; try { executionTime = - Restate.serviceHandle(MockBank.class) + Restate.toService(MockBank.class) .call( MockBank::transfer, new TransferRequest(loanRequest.customerBankAccount(), loanRequest.amount())) @@ -145,7 +145,7 @@ public static void main(String[] args) { LoanWorkflow loanWorkflow = restateClient.workflow(LoanWorkflow.class, "my-loan"); var handle = restateClient - .workflowHandle(LoanWorkflow.class, "my-loan") + .toWorkflow(LoanWorkflow.class, "my-loan") .send( LoanWorkflow::run, new LoanRequest( diff --git a/sdk-api-gen-common/build.gradle.kts b/sdk-api-gen-common/build.gradle.kts index 25d1d923..91d9be0b 100644 --- a/sdk-api-gen-common/build.gradle.kts +++ b/sdk-api-gen-common/build.gradle.kts @@ -4,7 +4,8 @@ plugins { `library-publishing-conventions` } -description = "Restate SDK API Gen Common" +description = + "Restate SDK API Gen Common (DEPRECATED: shared utilities for the deprecated codegen API; see https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md)" dependencies { compileOnly(libs.jspecify) diff --git a/sdk-api-gen/build.gradle.kts b/sdk-api-gen/build.gradle.kts index d2d1d492..93161cd4 100644 --- a/sdk-api-gen/build.gradle.kts +++ b/sdk-api-gen/build.gradle.kts @@ -4,7 +4,8 @@ plugins { `library-publishing-conventions` } -description = "Restate SDK API Gen" +description = + "Restate SDK API Gen (DEPRECATED: the annotation-processor/codegen API is deprecated in favor of the reflection-based API; see https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md)" dependencies { compileOnly(libs.jspecify) diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ServiceProcessor.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ServiceProcessor.java index b2fadc2b..27e84637 100644 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ServiceProcessor.java +++ b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ServiceProcessor.java @@ -25,6 +25,7 @@ import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; @@ -35,6 +36,7 @@ public class ServiceProcessor extends AbstractProcessor { private HandlebarsTemplateEngine clientCodegen; private HandlebarsTemplateEngine handlersCodegen; private AnnotationProcessingOptions options; + private boolean deprecationWarningEmitted = false; private static final Set RESERVED_METHOD_NAMES = Set.of("send", "submit", "workflowHandle"); @@ -111,6 +113,19 @@ public boolean process(Set annotations, RoundEnvironment (Element) e, converter.fromTypeElement(metaAnnotation, e)))) .collect(Collectors.toList()); + if (!parsedServices.isEmpty() && !deprecationWarningEmitted) { + deprecationWarningEmitted = true; + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.WARNING, + "The Restate annotation-processor (codegen) API is deprecated and will be removed in a" + + " future release. Migrate to the reflection-based API: drop the sdk-api-gen" + + " dependency, remove the Context parameters from your @Handler methods and use" + + " the static dev.restate.sdk.Restate methods instead. See the migration guide:" + + " https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md"); + } + Filer filer = processingEnv.getFiler(); // Run code generation diff --git a/sdk-api-gen/src/main/resources/templates/Client.hbs b/sdk-api-gen/src/main/resources/templates/Client.hbs index 81325f4d..cdda2be8 100644 --- a/sdk-api-gen/src/main/resources/templates/Client.hbs +++ b/sdk-api-gen/src/main/resources/templates/Client.hbs @@ -13,7 +13,11 @@ import java.util.function.Consumer; * Clients for {@link {{originalClassFqcn}} } * * @see {{originalClassFqcn}} + * @deprecated The Restate codegen API is deprecated; migrate to the reflection-based API. See the + * migration guide: https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md */ +@Deprecated +@SuppressWarnings("deprecation") public class {{generatedClassSimpleName}} { /** diff --git a/sdk-api-gen/src/main/resources/templates/Handlers.hbs b/sdk-api-gen/src/main/resources/templates/Handlers.hbs index 379ee545..8f8e6011 100644 --- a/sdk-api-gen/src/main/resources/templates/Handlers.hbs +++ b/sdk-api-gen/src/main/resources/templates/Handlers.hbs @@ -1,7 +1,13 @@ {{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} -/** Handler request factories for {@link {{originalClassFqcn}} } **/ -@SuppressWarnings("unchecked") +/** + * Handler request factories for {@link {{originalClassFqcn}} } + * + * @deprecated The Restate codegen API is deprecated; migrate to the reflection-based API. See the + * migration guide: https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md + **/ +@Deprecated +@SuppressWarnings({"unchecked", "deprecation"}) public final class {{generatedClassSimpleName}} { private {{generatedClassSimpleName}}() {} diff --git a/sdk-api-gen/src/main/resources/templates/ServiceDefinitionFactory.hbs b/sdk-api-gen/src/main/resources/templates/ServiceDefinitionFactory.hbs index 5651d83d..6bdabeda 100644 --- a/sdk-api-gen/src/main/resources/templates/ServiceDefinitionFactory.hbs +++ b/sdk-api-gen/src/main/resources/templates/ServiceDefinitionFactory.hbs @@ -1,6 +1,13 @@ {{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} -/** Service definition factory to bind {@link {{originalClassFqcn}} } **/ +/** + * Service definition factory to bind {@link {{originalClassFqcn}} } + * + * @deprecated The Restate codegen API is deprecated; migrate to the reflection-based API. See the + * migration guide: https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md + **/ +@Deprecated +@SuppressWarnings("deprecation") public class {{generatedClassSimpleName}} implements dev.restate.sdk.endpoint.definition.ServiceDefinitionFactory<{{originalClassFqcn}}> { @java.lang.Override diff --git a/sdk-api-kotlin-gen/build.gradle.kts b/sdk-api-kotlin-gen/build.gradle.kts index df744d4e..a452b942 100644 --- a/sdk-api-kotlin-gen/build.gradle.kts +++ b/sdk-api-kotlin-gen/build.gradle.kts @@ -4,7 +4,8 @@ plugins { alias(libs.plugins.ksp) } -description = "Restate SDK API Kotlin Gen" +description = + "Restate SDK API Kotlin Gen (DEPRECATED: the KSP code-generator/codegen API is deprecated in favor of the reflection-based API; see https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md)" dependencies { compileOnly(libs.jspecify) diff --git a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt index 06a1bff0..0be975b0 100644 --- a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt +++ b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt @@ -6,6 +6,8 @@ // You can find a copy of the license in file LICENSE in the root // directory of this repository or package, or at // https://github.com/restatedev/sdk-java/blob/main/LICENSE +@file:Suppress("DEPRECATION") + package dev.restate.sdk.kotlin.gen import com.google.devtools.ksp.* diff --git a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessor.kt b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessor.kt index 16947cf7..aa0ec72a 100644 --- a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessor.kt +++ b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessor.kt @@ -72,6 +72,8 @@ class ServiceProcessor( RESERVED_METHOD_NAMES, ) + private var deprecationWarningEmitted = false + @OptIn(KspExperimental::class) override fun process(resolver: Resolver): List { val converter = @@ -102,6 +104,17 @@ class ServiceProcessor( } .toList() + if (services.isNotEmpty() && !deprecationWarningEmitted) { + deprecationWarningEmitted = true + logger.warn( + "The Restate KSP code-generator (codegen) API is deprecated and will be removed in a " + + "future release. Migrate to the reflection-based API: drop the sdk-api-kotlin-gen " + + "dependency, remove the Context parameters from your @Handler methods and use the " + + "top-level functions in dev.restate.sdk.kotlin instead. See the migration guide: " + + "https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md" + ) + } + // Run code generation for (service in services) { try { diff --git a/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs b/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs index d5597f1c..fa29788a 100644 --- a/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs +++ b/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs @@ -8,6 +8,8 @@ import dev.restate.common.Target import kotlin.time.Duration import dev.restate.client.kotlin.* +@Deprecated("The Restate codegen API is deprecated; migrate to the reflection-based API. See https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md") +@Suppress("DEPRECATION") object {{generatedClassSimpleName}} { fun fromContext(ctx: Context{{#isKeyed}}, key: String{{/isKeyed}}): ContextClient { diff --git a/sdk-api-kotlin-gen/src/main/resources/templates/Handlers.hbs b/sdk-api-kotlin-gen/src/main/resources/templates/Handlers.hbs index aa889fb7..402c7e99 100644 --- a/sdk-api-kotlin-gen/src/main/resources/templates/Handlers.hbs +++ b/sdk-api-kotlin-gen/src/main/resources/templates/Handlers.hbs @@ -1,5 +1,7 @@ {{#if originalClassPkg}}package {{originalClassPkg}}{{/if}} +@Deprecated("The Restate codegen API is deprecated; migrate to the reflection-based API. See https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md") +@Suppress("DEPRECATION") object {{generatedClassSimpleName}} { {{#handlers}} diff --git a/sdk-api-kotlin-gen/src/main/resources/templates/ServiceDefinitionFactory.hbs b/sdk-api-kotlin-gen/src/main/resources/templates/ServiceDefinitionFactory.hbs index da99775c..e4371141 100644 --- a/sdk-api-kotlin-gen/src/main/resources/templates/ServiceDefinitionFactory.hbs +++ b/sdk-api-kotlin-gen/src/main/resources/templates/ServiceDefinitionFactory.hbs @@ -1,5 +1,7 @@ {{#if originalClassPkg}}package {{originalClassPkg}}{{/if}} +@Deprecated("The Restate codegen API is deprecated; migrate to the reflection-based API. See https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md") +@Suppress("DEPRECATION") class {{generatedClassSimpleName}}: dev.restate.sdk.endpoint.definition.ServiceDefinitionFactory<{{originalClassFqcn}}> { override fun create(bindableService: {{originalClassFqcn}}, overrideHandlerOptions: dev.restate.sdk.endpoint.definition.HandlerRunner.Options?): dev.restate.sdk.endpoint.definition.ServiceDefinition { diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ContextImpl.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ContextImpl.kt index 09ae5cd3..ab4c145d 100644 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ContextImpl.kt +++ b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ContextImpl.kt @@ -6,6 +6,8 @@ // You can find a copy of the license in file LICENSE in the root // directory of this repository or package, or at // https://github.com/restatedev/sdk-java/blob/main/LICENSE +@file:Suppress("DEPRECATION") + package dev.restate.sdk.kotlin import dev.restate.common.Output diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/HandlerRunner.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/HandlerRunner.kt index a3dde880..ce910d70 100644 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/HandlerRunner.kt +++ b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/HandlerRunner.kt @@ -6,6 +6,8 @@ // You can find a copy of the license in file LICENSE in the root // directory of this repository or package, or at // https://github.com/restatedev/sdk-java/blob/main/LICENSE +@file:Suppress("DEPRECATION") + package dev.restate.sdk.kotlin import dev.restate.common.Slice diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt index 020f3855..dd7e04df 100644 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt +++ b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt @@ -6,6 +6,8 @@ // You can find a copy of the license in file LICENSE in the root // directory of this repository or package, or at // https://github.com/restatedev/sdk-java/blob/main/LICENSE +@file:Suppress("DEPRECATION") + package dev.restate.sdk.kotlin import dev.restate.common.InvocationOptions @@ -46,6 +48,10 @@ import kotlinx.coroutines.currentCoroutineContext * NOTE: This interface MUST NOT be accessed concurrently since it can lead to different orderings * of user actions, corrupting the execution of the invocation. */ +@Deprecated( + "The Context-parameter programming model is superseded by the reflection-based API. Rather than accepting a Context (or its subtypes) as a handler parameter, use the top-level functions (e.g. runBlock { }, state(), awakeable()). See the migration guide: https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md", + level = DeprecationLevel.WARNING, +) sealed interface Context { fun request(): HandlerRequest @@ -364,6 +370,10 @@ suspend inline fun Context.signal(name: String): DurableFuture * This interface can be used only within shared handlers of virtual objects. It extends [Context] * adding access to the virtual object instance key-value state storage. */ +@Deprecated( + "The Context-parameter programming model is superseded by the reflection-based API. Rather than accepting a SharedObjectContext parameter, use state() and objectKey() inside the handler. See the migration guide: https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md", + level = DeprecationLevel.WARNING, +) sealed interface SharedObjectContext : Context { /** @return the key of this object */ @@ -398,6 +408,10 @@ suspend inline fun SharedObjectContext.get(key: String): T? { * This interface can be used only within exclusive handlers of virtual objects. It extends * [Context] adding access to the virtual object instance key-value state storage. */ +@Deprecated( + "The Context-parameter programming model is superseded by the reflection-based API. Rather than accepting an ObjectContext parameter, use state() (which supports both reads and writes) inside the handler. See the migration guide: https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md", + level = DeprecationLevel.WARNING, +) sealed interface ObjectContext : SharedObjectContext { /** @@ -433,6 +447,10 @@ suspend inline fun ObjectContext.set(key: String, value: T) { * @see Context * @see SharedObjectContext */ +@Deprecated( + "The Context-parameter programming model is superseded by the reflection-based API. Rather than accepting a SharedWorkflowContext parameter, use promise(), promiseHandle() and state() inside the handler. See the migration guide: https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md", + level = DeprecationLevel.WARNING, +) sealed interface SharedWorkflowContext : SharedObjectContext { /** * Create a [DurablePromise] for the given key. @@ -463,6 +481,10 @@ sealed interface SharedWorkflowContext : SharedObjectContext { * @see Context * @see ObjectContext */ +@Deprecated( + "The Context-parameter programming model is superseded by the reflection-based API. Rather than accepting a WorkflowContext parameter, use promise(), promiseHandle() and state() inside the handler. See the migration guide: https://github.com/restatedev/sdk-java/blob/main/MIGRATION.md", + level = DeprecationLevel.WARNING, +) sealed interface WorkflowContext : SharedWorkflowContext, ObjectContext class RestateRandom(seed: Long) : Random() { @@ -848,7 +870,6 @@ val HandlerRequest.headers: Map * * @throws IllegalStateException if called outside of a Restate handler */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun context(): Context { val element = currentCoroutineContext()[dev.restate.sdk.kotlin.internal.RestateContextElement] @@ -862,7 +883,6 @@ suspend fun context(): Context { * @throws IllegalStateException if called outside of a Restate handler * @see Context.request */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun request(): HandlerRequest { return context().request() } @@ -873,7 +893,6 @@ suspend fun request(): HandlerRequest { * @throws IllegalStateException if called outside of a Restate handler * @see Context.random */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun random(): RestateRandom { return context().random() } @@ -884,13 +903,11 @@ suspend fun random(): RestateRandom { * @see RestateClock.now */ @ExperimentalTime -@org.jetbrains.annotations.ApiStatus.Experimental fun clock(): RestateClock { return RestateClockImpl } @ExperimentalTime -@org.jetbrains.annotations.ApiStatus.Experimental interface RestateClock { /** * Returns the current time as a deterministic [Instant]. @@ -907,7 +924,6 @@ interface RestateClock { } @ExperimentalTime -@org.jetbrains.annotations.ApiStatus.Experimental private object RestateClockImpl : RestateClock { override suspend fun now(): Instant { return context().instantNow() @@ -920,7 +936,6 @@ private object RestateClockImpl : RestateClock { * @see RestateClock.now */ @ExperimentalTime -@get:org.jetbrains.annotations.ApiStatus.Experimental val Clock.Companion.Restate: RestateClock get() = clock() @@ -931,7 +946,6 @@ val Clock.Companion.Restate: RestateClock * @throws IllegalStateException if called outside of a Restate handler * @see Context.sleep */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun sleep(duration: Duration) { context().sleep(duration) } @@ -944,7 +958,6 @@ suspend fun sleep(duration: Duration) { * @throws IllegalStateException if called outside of a Restate handler * @see Context.timer */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun timer(name: String = "", duration: Duration): DurableFuture { return context().timer(duration, name) } @@ -959,7 +972,6 @@ suspend fun timer(name: String = "", duration: Duration): DurableFuture { * @throws IllegalStateException if called outside of a Restate handler * @see Context.runBlock */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend inline fun runBlock( name: String = "", retryPolicy: RetryPolicy? = null, @@ -978,7 +990,6 @@ suspend inline fun runBlock( * @throws IllegalStateException if called outside of a Restate handler * @see Context.runAsync */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend inline fun runAsync( name: String = "", retryPolicy: RetryPolicy? = null, @@ -994,7 +1005,6 @@ suspend inline fun runAsync( * @throws IllegalStateException if called outside of a Restate handler * @see Context.awakeable */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend inline fun awakeable(): Awakeable { return context().awakeable(typeTag()) } @@ -1010,7 +1020,6 @@ suspend inline fun awakeable(): Awakeable { * @return the [Awakeable] to await on. * @see Awakeable */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun awakeable(typeTag: TypeTag): Awakeable { return context().awakeable(typeTag) } @@ -1021,7 +1030,6 @@ suspend fun awakeable(typeTag: TypeTag): Awakeable { * @throws IllegalStateException if called outside of a Restate handler * @see Context.awakeableHandle */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun awakeableHandle(id: String): AwakeableHandle { return context().awakeableHandle(id) } @@ -1032,7 +1040,6 @@ suspend fun awakeableHandle(id: String): AwakeableHandle { * @throws IllegalStateException if called outside of a Restate handler * @see Context.signal */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend inline fun signal(name: String): DurableFuture { return context().signal(name, typeTag()) } @@ -1044,7 +1051,6 @@ suspend inline fun signal(name: String): DurableFuture { * @throws IllegalStateException if called outside of a Restate handler * @see Context.invocationHandle */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend inline fun invocationHandle(invocationId: String): InvocationHandle { return context().invocationHandle(invocationId, typeTag()) } @@ -1056,7 +1062,6 @@ suspend inline fun invocationHandle(invocationId: String): In * @throws IllegalStateException if called from a regular Service handler or outside of a Restate * handler */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun objectKey(): String { val ctx = context() val handlerContext = @@ -1080,7 +1085,6 @@ suspend fun objectKey(): String { * @throws IllegalStateException if called from a regular Service handler, or from a virtual object * handler, or outside of a Restate handler */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun workflowKey(): String { val ctx = context() val handlerContext = @@ -1104,7 +1108,6 @@ suspend fun workflowKey(): String { * @throws IllegalStateException if called from a regular Service handler or outside of a Restate * handler */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun state(): KotlinState { val ctx = context() val handlerContext = @@ -1128,7 +1131,6 @@ suspend fun state(): KotlinState { * handler * @see SharedWorkflowContext.promise */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun promise(key: DurablePromiseKey): DurablePromise { val ctx = context() val handlerContext = @@ -1152,7 +1154,6 @@ suspend fun promise(key: DurablePromiseKey): DurablePromise { * handler * @see SharedWorkflowContext.promiseHandle */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend fun promiseHandle(key: DurablePromiseKey): DurablePromiseHandle { val ctx = context() val handlerContext = @@ -1193,7 +1194,6 @@ suspend fun promiseHandle(key: DurablePromiseKey): DurablePromiseHa * } * ``` */ -@org.jetbrains.annotations.ApiStatus.Experimental interface KotlinState { /** * Gets the state stored under key, deserializing the raw value using the [StateKey.serdeInfo]. @@ -1202,7 +1202,7 @@ interface KotlinState { * @return the value containing the stored state deserialized, or null if not set. * @throws RuntimeException when the state cannot be deserialized. */ - @org.jetbrains.annotations.ApiStatus.Experimental suspend fun get(key: StateKey): T? + suspend fun get(key: StateKey): T? /** * Sets the given value under the given key, serializing the value using the [StateKey.serdeInfo]. @@ -1211,7 +1211,6 @@ interface KotlinState { * @param value to store under the given key. * @throws IllegalStateException if called from a Shared handler */ - @org.jetbrains.annotations.ApiStatus.Experimental suspend fun set(key: StateKey, value: T) /** @@ -1220,21 +1219,21 @@ interface KotlinState { * @param key identifying the state to clear. * @throws IllegalStateException if called from a Shared handler */ - @org.jetbrains.annotations.ApiStatus.Experimental suspend fun clear(key: StateKey<*>) + suspend fun clear(key: StateKey<*>) /** * Clears all the state of this virtual object instance key-value state storage. * * @throws IllegalStateException if called from a Shared handler */ - @org.jetbrains.annotations.ApiStatus.Experimental suspend fun clearAll() + suspend fun clearAll() /** * Gets all the known state keys for this virtual object instance. * * @return the immutable collection of known state keys. */ - @org.jetbrains.annotations.ApiStatus.Experimental suspend fun keys(): Collection + suspend fun keys(): Collection } /** @@ -1243,7 +1242,6 @@ interface KotlinState { * @param key the name of the state key. * @return the value containing the stored state deserialized, or null if not set. */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend inline fun KotlinState.get(key: String): T? { return this.get(StateKey.of(key, typeTag())) } @@ -1255,7 +1253,6 @@ suspend inline fun KotlinState.get(key: String): T? { * @param value to store under the given key. * @throws IllegalStateException if called from a Shared handler */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend inline fun KotlinState.set(key: String, value: T) { this.set(StateKey.of(key, typeTag()), value) } @@ -1312,7 +1309,6 @@ private class KotlinStateImpl( * @param Req the request type * @param Res the response type */ -@org.jetbrains.annotations.ApiStatus.Experimental interface KRequest : Request { /** @@ -1321,7 +1317,6 @@ interface KRequest : Request { * @param block builder block for options * @return a new request with the configured options */ - @org.jetbrains.annotations.ApiStatus.Experimental fun options(block: InvocationOptions.Builder.() -> Unit): KRequest /** @@ -1329,7 +1324,7 @@ interface KRequest : Request { * * @return a [CallDurableFuture] that will contain the response */ - @org.jetbrains.annotations.ApiStatus.Experimental suspend fun call(): CallDurableFuture + suspend fun call(): CallDurableFuture /** * Send the request without waiting for the response. @@ -1337,7 +1332,6 @@ interface KRequest : Request { * @param delay optional delay before the invocation is executed * @return an [InvocationHandle] to interact with the sent request */ - @org.jetbrains.annotations.ApiStatus.Experimental suspend fun send(delay: Duration? = null): InvocationHandle } @@ -1348,7 +1342,6 @@ interface KRequest : Request { * * @param SVC the service/virtual object/workflow class */ -@org.jetbrains.annotations.ApiStatus.Experimental class KRequestBuilder @PublishedApi internal constructor( @@ -1391,7 +1384,6 @@ internal constructor( * @param SVC the service class annotated with @Service * @return a builder for creating typed requests */ -@org.jetbrains.annotations.ApiStatus.Experimental inline fun toService(): KRequestBuilder { ReflectionUtils.mustHaveServiceAnnotation(SVC::class.java) require(ReflectionUtils.isKotlinClass(SVC::class.java)) { @@ -1419,7 +1411,6 @@ inline fun toService(): KRequestBuilder { * @param key the key identifying the specific virtual object instance * @return a builder for creating typed requests */ -@org.jetbrains.annotations.ApiStatus.Experimental inline fun toVirtualObject(key: String): KRequestBuilder { ReflectionUtils.mustHaveVirtualObjectAnnotation(SVC::class.java) require(ReflectionUtils.isKotlinClass(SVC::class.java)) { @@ -1447,7 +1438,6 @@ inline fun toVirtualObject(key: String): KRequestBuilder toWorkflow(key: String): KRequestBuilder { ReflectionUtils.mustHaveWorkflowAnnotation(SVC::class.java) require(ReflectionUtils.isKotlinClass(SVC::class.java)) { @@ -1495,7 +1485,6 @@ private class KRequestImpl(private val request: Request) : * @param SVC the service class annotated with @Service * @return a proxy client to invoke the service */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend inline fun service(): SVC { return service(SVC::class.java) } @@ -1516,7 +1505,6 @@ suspend inline fun service(): SVC { * @param key the key identifying the specific virtual object instance * @return a proxy client to invoke the virtual object */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend inline fun virtualObject(key: String): SVC { return virtualObject(SVC::class.java, key) } @@ -1528,7 +1516,6 @@ suspend inline fun virtualObject(key: String): SVC { * @param key the key identifying the specific workflow instance * @return a proxy client to invoke the workflow */ -@org.jetbrains.annotations.ApiStatus.Experimental suspend inline fun workflow(key: String): SVC { return workflow(SVC::class.java, key) } diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/internal/ReflectionServiceDefinitionFactory.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/internal/ReflectionServiceDefinitionFactory.kt index fa19ee73..28cd18c3 100644 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/internal/ReflectionServiceDefinitionFactory.kt +++ b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/internal/ReflectionServiceDefinitionFactory.kt @@ -6,6 +6,8 @@ // You can find a copy of the license in file LICENSE in the root // directory of this repository or package, or at // https://github.com/restatedev/sdk-java/blob/main/LICENSE +@file:Suppress("DEPRECATION") + package dev.restate.sdk.kotlin.internal import dev.restate.common.reflections.ReflectionUtils diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/internal/RestateContextElement.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/internal/RestateContextElement.kt index e421c6a2..0e820513 100644 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/internal/RestateContextElement.kt +++ b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/internal/RestateContextElement.kt @@ -6,6 +6,8 @@ // You can find a copy of the license in file LICENSE in the root // directory of this repository or package, or at // https://github.com/restatedev/sdk-java/blob/main/LICENSE +@file:Suppress("DEPRECATION") + package dev.restate.sdk.kotlin.internal import dev.restate.sdk.kotlin.Context diff --git a/sdk-api/src/main/java/dev/restate/sdk/Context.java b/sdk-api/src/main/java/dev/restate/sdk/Context.java index 1ff5fc1b..a856548c 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/Context.java +++ b/sdk-api/src/main/java/dev/restate/sdk/Context.java @@ -60,7 +60,14 @@ * * This interface MUST NOT be accessed concurrently since it can lead to different orderings * of user actions, corrupting the execution of the invocation. + * + * @deprecated The {@code Context}-parameter programming model is superseded by the reflection-based + * API. Rather than accepting a {@code Context} (or its subtypes) as a handler parameter, use + * the static methods on {@link Restate} (e.g. {@code Restate.run(...)}, {@code + * Restate.state()}, {@code Restate.awakeable(...)}). See the migration guide. */ +@Deprecated(since = "2.9", forRemoval = true) public interface Context { HandlerRequest request(); diff --git a/sdk-api/src/main/java/dev/restate/sdk/ContextImpl.java b/sdk-api/src/main/java/dev/restate/sdk/ContextImpl.java index 9a0259af..e02c26dc 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/ContextImpl.java +++ b/sdk-api/src/main/java/dev/restate/sdk/ContextImpl.java @@ -33,6 +33,7 @@ import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +@SuppressWarnings("deprecation") class ContextImpl implements ObjectContext, WorkflowContext { private static final ThreadLocal INSIDE_RUN = new ThreadLocal<>(); diff --git a/sdk-api/src/main/java/dev/restate/sdk/HandlerRunner.java b/sdk-api/src/main/java/dev/restate/sdk/HandlerRunner.java index f51bd9f0..fde1e8a3 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/HandlerRunner.java +++ b/sdk-api/src/main/java/dev/restate/sdk/HandlerRunner.java @@ -35,6 +35,7 @@ /** * Adapter class for {@link dev.restate.sdk.endpoint.definition.HandlerRunner} to use the Java API. */ +@SuppressWarnings("deprecation") public class HandlerRunner implements dev.restate.sdk.endpoint.definition.HandlerRunner { private final ThrowingBiFunction runner; diff --git a/sdk-api/src/main/java/dev/restate/sdk/ObjectContext.java b/sdk-api/src/main/java/dev/restate/sdk/ObjectContext.java index 13c2779a..886fcc39 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/ObjectContext.java +++ b/sdk-api/src/main/java/dev/restate/sdk/ObjectContext.java @@ -20,7 +20,12 @@ * orderings of user actions, corrupting the execution of the invocation. * * @see Context + * @deprecated The {@code Context}-parameter programming model is superseded by the reflection-based + * API. Rather than accepting an {@code ObjectContext} parameter, use {@code Restate.state()} + * (which supports both reads and writes) inside the handler. See the migration guide. */ +@Deprecated(since = "2.9", forRemoval = true) public interface ObjectContext extends SharedObjectContext { /** * Clears the state stored under key. diff --git a/sdk-api/src/main/java/dev/restate/sdk/Restate.java b/sdk-api/src/main/java/dev/restate/sdk/Restate.java index acfee597..a3d26c17 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/Restate.java +++ b/sdk-api/src/main/java/dev/restate/sdk/Restate.java @@ -78,12 +78,11 @@ * * @see Context */ -@org.jetbrains.annotations.ApiStatus.Experimental +@SuppressWarnings("deprecation") public final class Restate { /** * @see Context#request() */ - @org.jetbrains.annotations.ApiStatus.Experimental public static HandlerRequest request() { return Context.current().request(); } @@ -94,7 +93,6 @@ public static HandlerRequest request() { * @see RestateRandom * @see Context#random() */ - @org.jetbrains.annotations.ApiStatus.Experimental public static RestateRandom random() { return Context.current().random(); } @@ -109,7 +107,6 @@ public static RestateRandom random() { * @return the recorded {@link Instant} * @see Instant#now() */ - @org.jetbrains.annotations.ApiStatus.Experimental public static Instant instantNow() { return Context.current().instantNow(); } @@ -117,7 +114,6 @@ public static Instant instantNow() { /** * @see Context#invocationHandle(String, TypeTag) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static InvocationHandle invocationHandle( String invocationId, TypeTag responseTypeTag) { return Context.current().invocationHandle(invocationId, responseTypeTag); @@ -131,7 +127,6 @@ public static InvocationHandle invocationHandle( * @param responseClazz The response class. * @see Context#invocationHandle(String, Class) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static InvocationHandle invocationHandle( String invocationId, Class responseClazz) { return Context.current().invocationHandle(invocationId, responseClazz); @@ -142,7 +137,6 @@ public static InvocationHandle invocationHandle( * * @see Context#invocationHandle(String) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static InvocationHandle invocationHandle(String invocationId) { return Context.current().invocationHandle(invocationId); } @@ -153,7 +147,6 @@ public static InvocationHandle invocationHandle(String invocationId) { * @param duration for which to sleep. * @see Context#sleep(Duration) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static void sleep(Duration duration) { Context.current().sleep(duration); } @@ -166,7 +159,6 @@ public static void sleep(Duration duration) { * @param duration for which to sleep. * @see Context#timer(String, Duration) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static DurableFuture timer(String name, Duration duration) { return Context.current().timer(name, duration); } @@ -198,7 +190,6 @@ public static DurableFuture timer(String name, Duration duration) { * @return value of the run operation. * @see Context#run(String, Class, ThrowingSupplier) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static T run(String name, Class clazz, ThrowingSupplier action) throws TerminalException { return Context.current().run(name, clazz, action); @@ -215,7 +206,6 @@ public static T run(String name, Class clazz, ThrowingSupplier action) * @see RetryPolicy * @see Context#run(String, TypeTag, RetryPolicy, ThrowingSupplier) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static T run( String name, TypeTag typeTag, RetryPolicy retryPolicy, ThrowingSupplier action) throws TerminalException { @@ -233,7 +223,6 @@ public static T run( * @see RetryPolicy * @see Context#run(String, Class, RetryPolicy, ThrowingSupplier) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static T run( String name, Class clazz, RetryPolicy retryPolicy, ThrowingSupplier action) throws TerminalException { @@ -248,7 +237,6 @@ public static T run( * @see #run(String, Class, ThrowingSupplier) * @see Context#run(String, TypeTag, ThrowingSupplier) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static T run(String name, TypeTag typeTag, ThrowingSupplier action) throws TerminalException { return Context.current().run(name, typeTag, action); @@ -266,7 +254,6 @@ public static T run(String name, TypeTag typeTag, ThrowingSupplier act * @see RetryPolicy * @see Context#run(String, RetryPolicy, ThrowingRunnable) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static void run(String name, RetryPolicy retryPolicy, ThrowingRunnable runnable) throws TerminalException { Context.current().run(name, retryPolicy, runnable); @@ -278,7 +265,6 @@ public static void run(String name, RetryPolicy retryPolicy, ThrowingRunnable ru * @see #run(String, Class, ThrowingSupplier) * @see Context#run(String, ThrowingRunnable) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static void run(String name, ThrowingRunnable runnable) throws TerminalException { Context.current().run(name, runnable); } @@ -290,7 +276,6 @@ public static void run(String name, ThrowingRunnable runnable) throws TerminalEx * @see #run(String, Class, ThrowingSupplier) * @see Context#runAsync(String, Class, ThrowingSupplier) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static DurableFuture runAsync( String name, Class clazz, ThrowingSupplier action) throws TerminalException { return Context.current().runAsync(name, clazz, action); @@ -304,7 +289,6 @@ public static DurableFuture runAsync( * @see #runAsync(String, Class, ThrowingSupplier) * @see Context#runAsync(String, TypeTag, ThrowingSupplier) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static DurableFuture runAsync( String name, TypeTag typeTag, ThrowingSupplier action) throws TerminalException { return Context.current().runAsync(name, typeTag, action); @@ -321,7 +305,6 @@ public static DurableFuture runAsync( * @see RetryPolicy * @see Context#runAsync(String, Class, RetryPolicy, ThrowingSupplier) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static DurableFuture runAsync( String name, Class clazz, RetryPolicy retryPolicy, ThrowingSupplier action) throws TerminalException { @@ -339,7 +322,6 @@ public static DurableFuture runAsync( * @see RetryPolicy * @see Context#runAsync(String, TypeTag, RetryPolicy, ThrowingSupplier) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static DurableFuture runAsync( String name, TypeTag typeTag, RetryPolicy retryPolicy, ThrowingSupplier action) throws TerminalException { @@ -358,7 +340,6 @@ public static DurableFuture runAsync( * @see RetryPolicy * @see Context#runAsync(String, RetryPolicy, ThrowingRunnable) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static DurableFuture runAsync( String name, RetryPolicy retryPolicy, ThrowingRunnable runnable) throws TerminalException { return Context.current().runAsync(name, retryPolicy, runnable); @@ -369,7 +350,6 @@ public static DurableFuture runAsync( * * @see Context#runAsync(String, ThrowingRunnable) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static DurableFuture runAsync(String name, ThrowingRunnable runnable) throws TerminalException { return Context.current().runAsync(name, runnable); @@ -389,7 +369,6 @@ public static DurableFuture runAsync(String name, ThrowingRunnable runnabl * @see Awakeable * @see Context#awakeable(Class) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static Awakeable awakeable(Class clazz) { return Context.current().awakeable(clazz); } @@ -407,7 +386,6 @@ public static Awakeable awakeable(Class clazz) { * @see Awakeable * @see Context#awakeable(TypeTag) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static Awakeable awakeable(TypeTag typeTag) { return Context.current().awakeable(typeTag); } @@ -420,13 +398,53 @@ public static Awakeable awakeable(TypeTag typeTag) { * @see Awakeable * @see Context#awakeableHandle(String) */ - @org.jetbrains.annotations.ApiStatus.Experimental public static AwakeableHandle awakeableHandle(String id) { return Context.current().awakeableHandle(id); } /** - * EXPERIMENTAL API: Simple API to invoke a Restate service. + * Create a {@link DurableFuture} waiting on a named signal targeting the current invocation. + * + *

    Signals are identified by {@code (invocationId, name)}. The resolution can arrive before or + * after the handler starts waiting on the signal — there's no need to pre-register. + * + * @param name the signal name. + * @param clazz the response type to use for deserializing the signal result. When using generic + * types, use {@link #signal(String, TypeTag)} instead. + * @return a {@link DurableFuture} that resolves to the signal value (or rejects with a {@link + * TerminalException}). + * @see Context#signal(String, Class) + */ + public static DurableFuture signal(String name, Class clazz) { + return Context.current().signal(name, clazz); + } + + /** + * Create a {@link DurableFuture} waiting on a named signal targeting the current invocation. + * + * @param name the signal name. + * @param typeTag the response type tag to use for deserializing the signal result. + * @return a {@link DurableFuture} that resolves to the signal value (or rejects with a {@link + * TerminalException}). + * @see Context#signal(String, TypeTag) + */ + public static DurableFuture signal(String name, TypeTag typeTag) { + return Context.current().signal(name, typeTag); + } + + /** + * Causes the start of a timer for the given duration. You can await on the timer end by invoking + * {@link DurableFuture#await()}. + * + * @param duration for which to sleep. + * @see Context#timer(Duration) + */ + public static DurableFuture timer(Duration duration) { + return Context.current().timer(duration); + } + + /** + * Simple API to invoke a Restate service. * *

    Create a proxy client that allows calling service methods directly and synchronously. This * is the recommended approach for straightforward request-response interactions. @@ -437,12 +455,11 @@ public static AwakeableHandle awakeableHandle(String id) { * }

    * *

    For advanced use cases requiring asynchronous request handling, composable futures, or - * invocation options (such as idempotency keys), use {@link #serviceHandle(Class)} instead. + * invocation options (such as idempotency keys), use {@link #toService(Class)} instead. * * @param clazz the service class annotated with {@link Service} * @return a proxy client to invoke the service */ - @org.jetbrains.annotations.ApiStatus.Experimental public static SVC service(Class clazz) { ReflectionUtils.mustHaveServiceAnnotation(clazz); String serviceName = ReflectionUtils.extractServiceName(clazz); @@ -464,7 +481,7 @@ public static SVC service(Class clazz) { } /** - * EXPERIMENTAL API: Advanced API to invoke a Restate service with full control. + * Advanced API to invoke a Restate service with full control. * *

    Create a handle that provides advanced invocation capabilities including: * @@ -478,12 +495,12 @@ public static SVC service(Class clazz) { * *

    {@code
        * // 1. Use call() with method reference and await the result
    -   * GreetingResponse response = Restate.serviceHandle(Greeter.class)
    +   * GreetingResponse response = Restate.toService(Greeter.class)
        *   .call(Greeter::greet, new Greeting("Alice"))
        *   .await();
        *
        * // 2. Use send() for one-way invocation without waiting
    -   * InvocationHandle handle = Restate.serviceHandle(Greeter.class)
    +   * InvocationHandle handle = Restate.toService(Greeter.class)
        *   .send(Greeter::greet, new Greeting("Alice"));
        * }
    * @@ -493,14 +510,21 @@ public static SVC service(Class clazz) { * @param clazz the service class annotated with {@link Service} * @return a handle to invoke the service with advanced options */ - @org.jetbrains.annotations.ApiStatus.Experimental - public static ServiceHandle serviceHandle(Class clazz) { + public static ServiceHandle toService(Class clazz) { ReflectionUtils.mustHaveServiceAnnotation(clazz); return new ServiceHandleImpl<>(clazz, null); } /** - * EXPERIMENTAL API: Simple API to invoke a Restate Virtual Object. + * @deprecated Renamed to {@link #toService(Class)}. + */ + @Deprecated(since = "2.9", forRemoval = true) + public static ServiceHandle serviceHandle(Class clazz) { + return toService(clazz); + } + + /** + * Simple API to invoke a Restate Virtual Object. * *

    Create a proxy client that allows calling virtual object methods directly and synchronously. * This is the recommended approach for straightforward request-response interactions. @@ -511,14 +535,13 @@ public static ServiceHandle serviceHandle(Class clazz) { * } * *

    For advanced use cases requiring asynchronous request handling, composable futures, or - * invocation options (such as idempotency keys), use {@link #virtualObjectHandle(Class, String)} + * invocation options (such as idempotency keys), use {@link #toVirtualObject(Class, String)} * instead. * * @param clazz the virtual object class annotated with {@link VirtualObject} * @param key the key identifying the specific virtual object instance * @return a proxy client to invoke the virtual object */ - @org.jetbrains.annotations.ApiStatus.Experimental public static SVC virtualObject(Class clazz, String key) { ReflectionUtils.mustHaveVirtualObjectAnnotation(clazz); String serviceName = ReflectionUtils.extractServiceName(clazz); @@ -540,7 +563,7 @@ public static SVC virtualObject(Class clazz, String key) { } /** - * EXPERIMENTAL API: Advanced API to invoke a Restate Virtual Object with full control. + * Advanced API to invoke a Restate Virtual Object with full control. * *

    Create a handle that provides advanced invocation capabilities including: * @@ -554,12 +577,12 @@ public static SVC virtualObject(Class clazz, String key) { * *

    {@code
        * // 1. Use call() with method reference and await the result
    -   * int count = Restate.virtualObjectHandle(Counter.class, "my-counter")
    +   * int count = Restate.toVirtualObject(Counter.class, "my-counter")
        *   .call(Counter::increment)
        *   .await();
        *
        * // 2. Use send() for one-way invocation without waiting
    -   * InvocationHandle handle = Restate.virtualObjectHandle(Counter.class, "my-counter")
    +   * InvocationHandle handle = Restate.toVirtualObject(Counter.class, "my-counter")
        *   .send(Counter::increment);
        * }
    * @@ -570,14 +593,21 @@ public static SVC virtualObject(Class clazz, String key) { * @param key the key identifying the specific virtual object instance * @return a handle to invoke the virtual object with advanced options */ - @org.jetbrains.annotations.ApiStatus.Experimental - public static ServiceHandle virtualObjectHandle(Class clazz, String key) { + public static ServiceHandle toVirtualObject(Class clazz, String key) { ReflectionUtils.mustHaveVirtualObjectAnnotation(clazz); return new ServiceHandleImpl<>(clazz, key); } /** - * EXPERIMENTAL API: Simple API to invoke a Restate Workflow. + * @deprecated Renamed to {@link #toVirtualObject(Class, String)}. + */ + @Deprecated(since = "2.9", forRemoval = true) + public static ServiceHandle virtualObjectHandle(Class clazz, String key) { + return toVirtualObject(clazz, key); + } + + /** + * Simple API to invoke a Restate Workflow. * *

    Create a proxy client that allows calling workflow methods directly and synchronously. This * is the recommended approach for straightforward request-response interactions. @@ -588,14 +618,12 @@ public static ServiceHandle virtualObjectHandle(Class clazz, Str * } * *

    For advanced use cases requiring asynchronous request handling, composable futures, or - * invocation options (such as idempotency keys), use {@link #workflowHandle(Class, String)} - * instead. + * invocation options (such as idempotency keys), use {@link #toWorkflow(Class, String)} instead. * * @param clazz the workflow class annotated with {@link Workflow} * @param key the key identifying the specific workflow instance * @return a proxy client to invoke the workflow */ - @org.jetbrains.annotations.ApiStatus.Experimental public static SVC workflow(Class clazz, String key) { ReflectionUtils.mustHaveWorkflowAnnotation(clazz); String serviceName = ReflectionUtils.extractServiceName(clazz); @@ -617,7 +645,7 @@ public static SVC workflow(Class clazz, String key) { } /** - * EXPERIMENTAL API: Advanced API to invoke a Restate Workflow with full control. + * Advanced API to invoke a Restate Workflow with full control. * *

    Create a handle that provides advanced invocation capabilities including: * @@ -631,12 +659,12 @@ public static SVC workflow(Class clazz, String key) { * *

    {@code
        * // 1. Use call() with method reference and await the result
    -   * Restate.workflowHandle(OrderWorkflow.class, "order-123")
    +   * Restate.toWorkflow(OrderWorkflow.class, "order-123")
        *   .call(OrderWorkflow::start, new OrderRequest(...))
        *   .await();
        *
        * // 2. Use send() for one-way invocation without waiting
    -   * InvocationHandle handle = Restate.workflowHandle(OrderWorkflow.class, "order-123")
    +   * InvocationHandle handle = Restate.toWorkflow(OrderWorkflow.class, "order-123")
        *   .send(OrderWorkflow::start, new OrderRequest(...));
        * }
    * @@ -647,63 +675,63 @@ public static SVC workflow(Class clazz, String key) { * @param key the key identifying the specific workflow instance * @return a handle to invoke the workflow with advanced options */ - @org.jetbrains.annotations.ApiStatus.Experimental - public static ServiceHandle workflowHandle(Class clazz, String key) { + public static ServiceHandle toWorkflow(Class clazz, String key) { ReflectionUtils.mustHaveWorkflowAnnotation(clazz); return new ServiceHandleImpl<>(clazz, key); } - /** EXPERIMENTAL API: Interface to interact with this Virtual Object/Workflow state. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** + * @deprecated Renamed to {@link #toWorkflow(Class, String)}. + */ + @Deprecated(since = "2.9", forRemoval = true) + public static ServiceHandle workflowHandle(Class clazz, String key) { + return toWorkflow(clazz, key); + } + + /** Interface to interact with this Virtual Object/Workflow state. */ public interface State { /** - * EXPERIMENTAL API: Gets the state stored under key, deserializing the raw value using - * the {@link Serde} in the {@link StateKey}. + * Gets the state stored under key, deserializing the raw value using the {@link Serde} in the + * {@link StateKey}. * * @param key identifying the state to get and its type. * @return an {@link Optional} containing the stored state deserialized or an empty {@link * Optional} if not set yet. * @throws RuntimeException when the state cannot be deserialized. */ - @org.jetbrains.annotations.ApiStatus.Experimental Optional get(StateKey key); /** - * EXPERIMENTAL API: Sets the given value under the given key, serializing the value - * using the {@link Serde} in the {@link StateKey}. + * Sets the given value under the given key, serializing the value using the {@link Serde} in + * the {@link StateKey}. * * @param key identifying the value to store and its type. * @param value to store under the given key. MUST NOT be null. * @throws IllegalStateException if called from a Shared handler */ - @org.jetbrains.annotations.ApiStatus.Experimental void set(StateKey key, @NonNull T value); /** - * EXPERIMENTAL API: Clears the state stored under key. + * Clears the state stored under key. * * @param key identifying the state to clear. * @throws IllegalStateException if called from a Shared handler */ - @org.jetbrains.annotations.ApiStatus.Experimental void clear(StateKey key); /** - * EXPERIMENTAL API: Gets all the known state keys for this virtual object instance. + * Gets all the known state keys for this virtual object instance. * * @return the immutable collection of known state keys. */ - @org.jetbrains.annotations.ApiStatus.Experimental Collection getAllKeys(); /** - * EXPERIMENTAL API: Clears all the state of this virtual object instance key-value state - * storage + * Clears all the state of this virtual object instance key-value state storage * * @throws IllegalStateException if called from a Shared handler */ - @org.jetbrains.annotations.ApiStatus.Experimental void clearAll(); } @@ -749,12 +777,9 @@ private void checkCanWriteState(String opName) { }; /** - * EXPERIMENTAL API - * * @return this Virtual Object/Workflow key * @throws IllegalStateException if called from a regular Service handler. */ - @org.jetbrains.annotations.ApiStatus.Experimental public static String key() { var handlerContext = HandlerRunner.getHandlerContext(); @@ -767,12 +792,11 @@ public static String key() { } /** - * EXPERIMENTAL API: Access to this Virtual Object/Workflow state. + * Access to this Virtual Object/Workflow state. * * @return {@link State} for this Virtual Object/Workflow * @throws IllegalStateException if called from a regular Service handler. */ - @org.jetbrains.annotations.ApiStatus.Experimental public static State state() { var handlerContext = HandlerRunner.getHandlerContext(); @@ -784,7 +808,7 @@ public static State state() { } /** - * EXPERIMENTAL API: Create a {@link DurablePromise} for the given key. + * Create a {@link DurablePromise} for the given key. * *

    You can use this feature to implement interaction between different workflow handlers, e.g. * to send a signal from a shared handler to the workflow handler. @@ -793,7 +817,6 @@ public static State state() { * @see DurablePromise * @throws IllegalStateException if called from a non-Workflow handler */ - @org.jetbrains.annotations.ApiStatus.Experimental public static DurablePromise promise(DurablePromiseKey key) { var handlerContext = HandlerRunner.getHandlerContext(); @@ -807,14 +830,13 @@ public static DurablePromise promise(DurablePromiseKey key) { } /** - * EXPERIMENTAL API: Create a new {@link DurablePromiseHandle} for the provided key. You - * can use it to {@link DurablePromiseHandle#resolve(Object)} or {@link - * DurablePromiseHandle#reject(String)} the given {@link DurablePromise}. + * Create a new {@link DurablePromiseHandle} for the provided key. You can use it to {@link + * DurablePromiseHandle#resolve(Object)} or {@link DurablePromiseHandle#reject(String)} the given + * {@link DurablePromise}. * * @see DurablePromise * @throws IllegalStateException if called from a non-Workflow handler */ - @org.jetbrains.annotations.ApiStatus.Experimental public static DurablePromiseHandle promiseHandle(DurablePromiseKey key) { var handlerContext = HandlerRunner.getHandlerContext(); diff --git a/sdk-api/src/main/java/dev/restate/sdk/ServiceHandle.java b/sdk-api/src/main/java/dev/restate/sdk/ServiceHandle.java index 08d63fc9..f116f4c2 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/ServiceHandle.java +++ b/sdk-api/src/main/java/dev/restate/sdk/ServiceHandle.java @@ -16,10 +16,7 @@ import java.util.function.Function; /** - * EXPERIMENTAL API: This interface is part of the new reflection-based API and may change in - * future releases. - * - *

    Advanced API handle for invoking Restate services, virtual objects, or workflows with full + * Advanced API handle for invoking Restate services, virtual objects, or workflows with full * control. This handle provides advanced invocation capabilities including: * *

      @@ -33,17 +30,17 @@ * *
      {@code
        * // 1. Use call() with method reference and await the result
      - * GreetingResponse response = Restate.serviceHandle(Greeter.class)
      + * GreetingResponse response = Restate.toService(Greeter.class)
        *   .call(Greeter::greet, new Greeting("Alice"))
        *   .await();
        *
        * // 2. Use send() for one-way invocation without waiting
      - * InvocationHandle handle = Restate.serviceHandle(Greeter.class)
      + * InvocationHandle handle = Restate.toService(Greeter.class)
        *   .send(Greeter::greet, new Greeting("Alice"));
        * }
      * - *

      Create instances using {@link Restate#serviceHandle(Class)}, {@link - * Restate#virtualObjectHandle(Class, String)}, or {@link Restate#workflowHandle(Class, String)}. + *

      Create instances using {@link Restate#toService(Class)}, {@link Restate#toVirtualObject(Class, + * String)}, or {@link Restate#toWorkflow(Class, String)}. * *

      For simple synchronous request-response interactions, consider using the simple proxy API * instead: {@link Restate#service(Class)}, {@link Restate#virtualObject(Class, String)}, or {@link @@ -51,10 +48,9 @@ * * @param the service interface type */ -@org.jetbrains.annotations.ApiStatus.Experimental public interface ServiceHandle { /** - * EXPERIMENTAL API: Invoke a service method with input and return a future for the result. + * Invoke a service method with input and return a future for the result. * *

      {@code
          * // Call with method reference and input
      @@ -67,13 +63,12 @@ public interface ServiceHandle {
          * @param input the input parameter to pass to the method
          * @return a {@link DurableFuture} wrapping the result
          */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
         default  DurableFuture call(BiFunction methodReference, I input) {
           return call(methodReference, input, InvocationOptions.DEFAULT);
         }
       
         /**
      -   * EXPERIMENTAL API: Like {@link #call(BiFunction, Object)}, with invocation options.
      +   * Like {@link #call(BiFunction, Object)}, with invocation options.
          *
          * 
      {@code
          * // Call with custom options
      @@ -85,41 +80,32 @@ default  DurableFuture call(BiFunction methodReference, I in
          *   .await();
          * }
      */ - @org.jetbrains.annotations.ApiStatus.Experimental default DurableFuture call( BiFunction methodReference, I input, InvocationOptions.Builder options) { return call(methodReference, input, options.build()); } - /** EXPERIMENTAL API: Like {@link #call(BiFunction, Object)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #call(BiFunction, Object)}, with invocation options. */ DurableFuture call( BiFunction methodReference, I input, InvocationOptions options); - /** - * EXPERIMENTAL API: Like {@link #call(BiFunction, Object)}, for methods without a return - * value. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #call(BiFunction, Object)}, for methods without a return value. */ default DurableFuture call(BiConsumer methodReference, I input) { return call(methodReference, input, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #call(BiConsumer, Object)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #call(BiConsumer, Object)}, with invocation options. */ default DurableFuture call( BiConsumer methodReference, I input, InvocationOptions.Builder options) { return call(methodReference, input, options.build()); } - /** EXPERIMENTAL API: Like {@link #call(BiConsumer, Object)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #call(BiConsumer, Object)}, with invocation options. */ DurableFuture call( BiConsumer methodReference, I input, InvocationOptions options); /** - * EXPERIMENTAL API: Invoke a service method without input and return a future for the - * result. + * Invoke a service method without input and return a future for the result. * *
      {@code
          * // Call method without input
      @@ -128,44 +114,35 @@  DurableFuture call(
          *   .await();
          * }
      */ - @org.jetbrains.annotations.ApiStatus.Experimental default DurableFuture call(Function methodReference) { return call(methodReference, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #call(Function)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #call(Function)}, with invocation options. */ default DurableFuture call( Function methodReference, InvocationOptions.Builder options) { return call(methodReference, options.build()); } - /** EXPERIMENTAL API: Like {@link #call(Function)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #call(Function)}, with invocation options. */ DurableFuture call(Function methodReference, InvocationOptions options); - /** - * EXPERIMENTAL API: Like {@link #call(BiFunction, Object)}, for methods without input or - * return value. - */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #call(BiFunction, Object)}, for methods without input or return value. */ default DurableFuture call(Consumer methodReference) { return call(methodReference, InvocationOptions.DEFAULT); } - /** EXPERIMENTAL API: Like {@link #call(Consumer)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #call(Consumer)}, with invocation options. */ default DurableFuture call( Consumer methodReference, InvocationOptions.Builder options) { return call(methodReference, options.build()); } - /** EXPERIMENTAL API: Like {@link #call(Consumer)}, with invocation options. */ - @org.jetbrains.annotations.ApiStatus.Experimental + /** Like {@link #call(Consumer)}, with invocation options. */ DurableFuture call(Consumer methodReference, InvocationOptions options); /** - * EXPERIMENTAL API: Send a one-way invocation without waiting for the response. + * Send a one-way invocation without waiting for the response. * *
      {@code
          * // Send without waiting for response
      @@ -182,37 +159,29 @@ default DurableFuture call(
          * @param input the input parameter to pass to the method
          * @return an {@link InvocationHandle} for the invocation
          */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
         default  InvocationHandle send(BiFunction methodReference, I input) {
           return send(methodReference, input, InvocationOptions.DEFAULT);
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, with invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiFunction, Object)}, with invocation options. */
         default  InvocationHandle send(
             BiFunction methodReference, I input, InvocationOptions.Builder options) {
           return send(methodReference, input, options.build());
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, with invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiFunction, Object)}, with invocation options. */
         default  InvocationHandle send(
             BiFunction methodReference, I input, InvocationOptions options) {
           return send(methodReference, input, null, options);
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, with a delay. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiFunction, Object)}, with a delay. */
         default  InvocationHandle send(
             BiFunction methodReference, I input, Duration delay) {
           return send(methodReference, input, delay, InvocationOptions.DEFAULT);
         }
       
      -  /**
      -   * EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, with a delay and invocation
      -   * options.
      -   */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiFunction, Object)}, with a delay and invocation options. */
         default  InvocationHandle send(
             BiFunction methodReference,
             I input,
      @@ -221,49 +190,34 @@ default  InvocationHandle send(
           return send(methodReference, input, delay, options.build());
         }
       
      -  /**
      -   * EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, with a delay and invocation
      -   * options.
      -   */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiFunction, Object)}, with a delay and invocation options. */
          InvocationHandle send(
             BiFunction methodReference, I input, Duration delay, InvocationOptions options);
       
      -  /**
      -   * EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, for methods without a return
      -   * value.
      -   */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiFunction, Object)}, for methods without a return value. */
         default  InvocationHandle send(BiConsumer methodReference, I input) {
           return send(methodReference, input, InvocationOptions.DEFAULT);
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(BiConsumer, Object)}, with invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiConsumer, Object)}, with invocation options. */
         default  InvocationHandle send(
             BiConsumer methodReference, I input, InvocationOptions.Builder options) {
           return send(methodReference, input, options.build());
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(BiConsumer, Object)}, with invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiConsumer, Object)}, with invocation options. */
         default  InvocationHandle send(
             BiConsumer methodReference, I input, InvocationOptions options) {
           return send(methodReference, input, null, options);
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(BiConsumer, Object)}, with a delay. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiConsumer, Object)}, with a delay. */
         default  InvocationHandle send(
             BiConsumer methodReference, I input, Duration delay) {
           return send(methodReference, input, delay, InvocationOptions.DEFAULT);
         }
       
      -  /**
      -   * EXPERIMENTAL API: Like {@link #send(BiConsumer, Object)}, with a delay and invocation
      -   * options.
      -   */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiConsumer, Object)}, with a delay and invocation options. */
         default  InvocationHandle send(
             BiConsumer methodReference,
             I input,
      @@ -272,88 +226,69 @@ default  InvocationHandle send(
           return send(methodReference, input, delay, options.build());
         }
       
      -  /**
      -   * EXPERIMENTAL API: Like {@link #send(BiConsumer, Object)}, with a delay and invocation
      -   * options.
      -   */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiConsumer, Object)}, with a delay and invocation options. */
          InvocationHandle send(
             BiConsumer methodReference, I input, Duration delay, InvocationOptions options);
       
      -  /** EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, for methods without input. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiFunction, Object)}, for methods without input. */
         default  InvocationHandle send(Function s) {
           return send(s, InvocationOptions.DEFAULT);
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(Function)}, with invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(Function)}, with invocation options. */
         default  InvocationHandle send(
             Function methodReference, InvocationOptions.Builder options) {
           return send(methodReference, options.build());
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(Function)}, with invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(Function)}, with invocation options. */
         default  InvocationHandle send(Function s, InvocationOptions options) {
           return send(s, null, options);
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(Function)}, with a delay. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(Function)}, with a delay. */
         default  InvocationHandle send(Function s, Duration delay) {
           return send(s, delay, InvocationOptions.DEFAULT);
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(Function)}, with a delay and invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(Function)}, with a delay and invocation options. */
         default  InvocationHandle send(
             Function methodReference, Duration delay, InvocationOptions.Builder options) {
           return send(methodReference, delay, options.build());
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(Function)}, with a delay and invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(Function)}, with a delay and invocation options. */
          InvocationHandle send(
             Function methodReference, Duration delay, InvocationOptions options);
       
      -  /**
      -   * EXPERIMENTAL API: Like {@link #send(BiFunction, Object)}, for methods without input or
      -   * return value.
      -   */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(BiFunction, Object)}, for methods without input or return value. */
         default InvocationHandle send(Consumer methodReference) {
           return send(methodReference, InvocationOptions.DEFAULT);
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(Consumer)}, with invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(Consumer)}, with invocation options. */
         default InvocationHandle send(
             Consumer methodReference, InvocationOptions.Builder options) {
           return send(methodReference, options.build());
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(Consumer)}, with invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(Consumer)}, with invocation options. */
         default InvocationHandle send(Consumer methodReference, InvocationOptions options) {
           return send(methodReference, null, options);
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(Consumer)}, with a delay. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(Consumer)}, with a delay. */
         default InvocationHandle send(Consumer methodReference, Duration delay) {
           return send(methodReference, delay, InvocationOptions.DEFAULT);
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(Consumer)}, with a delay and invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(Consumer)}, with a delay and invocation options. */
         default InvocationHandle send(
             Consumer methodReference, Duration delay, InvocationOptions.Builder options) {
           return send(methodReference, delay, options.build());
         }
       
      -  /** EXPERIMENTAL API: Like {@link #send(Consumer)}, with a delay and invocation options. */
      -  @org.jetbrains.annotations.ApiStatus.Experimental
      +  /** Like {@link #send(Consumer)}, with a delay and invocation options. */
         InvocationHandle send(
             Consumer methodReference, Duration delay, InvocationOptions options);
       }
      diff --git a/sdk-api/src/main/java/dev/restate/sdk/ServiceHandleImpl.java b/sdk-api/src/main/java/dev/restate/sdk/ServiceHandleImpl.java
      index f4d8eccd..bedfab09 100644
      --- a/sdk-api/src/main/java/dev/restate/sdk/ServiceHandleImpl.java
      +++ b/sdk-api/src/main/java/dev/restate/sdk/ServiceHandleImpl.java
      @@ -21,6 +21,7 @@
       import java.util.function.Function;
       import org.jspecify.annotations.Nullable;
       
      +@SuppressWarnings("deprecation")
       final class ServiceHandleImpl implements ServiceHandle {
       
         private final Class clazz;
      diff --git a/sdk-api/src/main/java/dev/restate/sdk/SharedObjectContext.java b/sdk-api/src/main/java/dev/restate/sdk/SharedObjectContext.java
      index 426390e5..5c99dfd8 100644
      --- a/sdk-api/src/main/java/dev/restate/sdk/SharedObjectContext.java
      +++ b/sdk-api/src/main/java/dev/restate/sdk/SharedObjectContext.java
      @@ -21,7 +21,12 @@
        * orderings of user actions, corrupting the execution of the invocation.
        *
        * @see Context
      + * @deprecated The {@code Context}-parameter programming model is superseded by the reflection-based
      + *     API. Rather than accepting a {@code SharedObjectContext} parameter, use {@code
      + *     Restate.state()} and {@code Restate.key()} inside the handler. See the migration guide.
        */
      +@Deprecated(since = "2.9", forRemoval = true)
       public interface SharedObjectContext extends Context {
         /**
          * @return the key of this object
      diff --git a/sdk-api/src/main/java/dev/restate/sdk/SharedWorkflowContext.java b/sdk-api/src/main/java/dev/restate/sdk/SharedWorkflowContext.java
      index e4f266ef..c226ea9c 100644
      --- a/sdk-api/src/main/java/dev/restate/sdk/SharedWorkflowContext.java
      +++ b/sdk-api/src/main/java/dev/restate/sdk/SharedWorkflowContext.java
      @@ -20,7 +20,13 @@
        *
        * @see Context
        * @see SharedObjectContext
      + * @deprecated The {@code Context}-parameter programming model is superseded by the reflection-based
      + *     API. Rather than accepting a {@code SharedWorkflowContext} parameter, use {@code
      + *     Restate.promise(...)}, {@code Restate.promiseHandle(...)} and {@code Restate.state()} inside
      + *     the handler. See the migration guide.
        */
      +@Deprecated(since = "2.9", forRemoval = true)
       public interface SharedWorkflowContext extends SharedObjectContext {
         /**
          * Create a {@link DurablePromise} for the given key.
      diff --git a/sdk-api/src/main/java/dev/restate/sdk/WorkflowContext.java b/sdk-api/src/main/java/dev/restate/sdk/WorkflowContext.java
      index 102c65da..f4e0aa15 100644
      --- a/sdk-api/src/main/java/dev/restate/sdk/WorkflowContext.java
      +++ b/sdk-api/src/main/java/dev/restate/sdk/WorkflowContext.java
      @@ -18,5 +18,11 @@
        *
        * @see Context
        * @see ObjectContext
      + * @deprecated The {@code Context}-parameter programming model is superseded by the reflection-based
      + *     API. Rather than accepting a {@code WorkflowContext} parameter, use {@code
      + *     Restate.promise(...)}, {@code Restate.promiseHandle(...)} and {@code Restate.state()} inside
      + *     the handler. See the migration guide.
        */
      +@Deprecated(since = "2.9", forRemoval = true)
       public interface WorkflowContext extends SharedWorkflowContext, ObjectContext {}
      diff --git a/sdk-api/src/main/java/dev/restate/sdk/internal/ContextThreadLocal.java b/sdk-api/src/main/java/dev/restate/sdk/internal/ContextThreadLocal.java
      index 4a761951..46eba47b 100644
      --- a/sdk-api/src/main/java/dev/restate/sdk/internal/ContextThreadLocal.java
      +++ b/sdk-api/src/main/java/dev/restate/sdk/internal/ContextThreadLocal.java
      @@ -13,6 +13,7 @@
       
       @org.jetbrains.annotations.ApiStatus.Internal
       @org.jetbrains.annotations.ApiStatus.Experimental
      +@SuppressWarnings("deprecation")
       public final class ContextThreadLocal {
         public static final ThreadLocal CONTEXT_THREAD_LOCAL = new ThreadLocal<>();
       
      diff --git a/sdk-api/src/main/java/dev/restate/sdk/internal/ReflectionServiceDefinitionFactory.java b/sdk-api/src/main/java/dev/restate/sdk/internal/ReflectionServiceDefinitionFactory.java
      index e6077b6f..178fbbc1 100644
      --- a/sdk-api/src/main/java/dev/restate/sdk/internal/ReflectionServiceDefinitionFactory.java
      +++ b/sdk-api/src/main/java/dev/restate/sdk/internal/ReflectionServiceDefinitionFactory.java
      @@ -27,8 +27,8 @@
       import java.util.stream.Collectors;
       import org.jspecify.annotations.Nullable;
       
      -@org.jetbrains.annotations.ApiStatus.Experimental
       @org.jetbrains.annotations.ApiStatus.Internal
      +@SuppressWarnings("deprecation")
       public final class ReflectionServiceDefinitionFactory implements ServiceDefinitionFactory {
       
         private volatile SerdeFactory cachedDefaultSerdeFactory;
      diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/CodegenDiscoveryTest.java b/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/CodegenDiscoveryTest.java
      index e9dc2e2f..d0df8238 100644
      --- a/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/CodegenDiscoveryTest.java
      +++ b/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/CodegenDiscoveryTest.java
      @@ -19,6 +19,7 @@
       import dev.restate.sdk.endpoint.Endpoint;
       import org.junit.jupiter.api.Test;
       
      +@SuppressWarnings("deprecation") // Intentionally exercises the deprecated codegen/Context API.
       public class CodegenDiscoveryTest {
       
         @Test
      diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/CodegenTest.java b/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/CodegenTest.java
      index 75929608..138208fd 100644
      --- a/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/CodegenTest.java
      +++ b/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/CodegenTest.java
      @@ -23,6 +23,7 @@
       import java.nio.charset.StandardCharsets;
       import java.util.stream.Stream;
       
      +@SuppressWarnings("deprecation") // Intentionally exercises the deprecated codegen/Context API.
       public class CodegenTest implements TestSuite {
       
         @Service
      diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/reflections/MyWorkflow.java b/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/reflections/MyWorkflow.java
      index 605c931c..1dae3f94 100644
      --- a/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/reflections/MyWorkflow.java
      +++ b/sdk-core/src/test/java/dev/restate/sdk/core/javaapi/reflections/MyWorkflow.java
      @@ -19,8 +19,7 @@ public class MyWorkflow {
       
         @Workflow
         public void run(String myInput) {
      -    Restate.workflowHandle(MyWorkflow.class, Restate.key())
      -        .send(MyWorkflow::sharedHandler, myInput);
      +    Restate.toWorkflow(MyWorkflow.class, Restate.key()).send(MyWorkflow::sharedHandler, myInput);
         }
       
         @Handler
      diff --git a/sdk-core/src/test/kotlin/dev/restate/sdk/core/kotlinapi/CodegenDiscoveryTest.kt b/sdk-core/src/test/kotlin/dev/restate/sdk/core/kotlinapi/CodegenDiscoveryTest.kt
      index b2783f59..bf53452d 100644
      --- a/sdk-core/src/test/kotlin/dev/restate/sdk/core/kotlinapi/CodegenDiscoveryTest.kt
      +++ b/sdk-core/src/test/kotlin/dev/restate/sdk/core/kotlinapi/CodegenDiscoveryTest.kt
      @@ -6,6 +6,8 @@
       // You can find a copy of the license in file LICENSE in the root
       // directory of this repository or package, or at
       // https://github.com/restatedev/sdk-java/blob/main/LICENSE
      +@file:Suppress("DEPRECATION") // Intentionally exercises the deprecated codegen/Context API.
      +
       package dev.restate.sdk.core.kotlinapi
       
       import dev.restate.sdk.core.AssertUtils.assertThatDiscovery
      diff --git a/sdk-core/src/test/kotlin/dev/restate/sdk/core/kotlinapi/CodegenTest.kt b/sdk-core/src/test/kotlin/dev/restate/sdk/core/kotlinapi/CodegenTest.kt
      index 9ee50f07..6812bc35 100644
      --- a/sdk-core/src/test/kotlin/dev/restate/sdk/core/kotlinapi/CodegenTest.kt
      +++ b/sdk-core/src/test/kotlin/dev/restate/sdk/core/kotlinapi/CodegenTest.kt
      @@ -6,6 +6,8 @@
       // You can find a copy of the license in file LICENSE in the root
       // directory of this repository or package, or at
       // https://github.com/restatedev/sdk-java/blob/main/LICENSE
      +@file:Suppress("DEPRECATION") // Intentionally exercises the deprecated codegen/Context API.
      +
       package dev.restate.sdk.core.kotlinapi
       
       import dev.restate.common.Slice