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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ one service at a time.
| `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)` |
| Code-generated clients (Service) | `Restate.service(Class)` / `Restate.serviceHandle(Class)` |
| Code-generated clients (Virtual Object) | `Restate.virtualObject(Class, key)` / `Restate.virtualObjectHandle(Class, key)` |
| Code-generated clients (Workflow) | `Restate.workflow(Class, key)` / `Restate.workflowHandle(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.
`client.service(Class)` / `client.serviceHandle(Class)` / `client.virtualObject(Class, key)` / etc.

### Kotlin: `Context` API → top-level functions

Expand Down Expand Up @@ -129,16 +129,16 @@ Restate.virtualObject(Counter.class, "my-key").add(1);

```java
// call() with a method reference returns a DurableFuture you can await and/or compose
int count = Restate.toVirtualObject(Counter.class, "my-counter")
int count = Restate.virtualObjectHandle(Counter.class, "my-counter")
.call(Counter::increment)
.await();

// send() for one-way invocation without waiting
InvocationHandle<Integer> handle = Restate.toVirtualObject(Counter.class, "my-counter")
InvocationHandle<Integer> handle = Restate.virtualObjectHandle(Counter.class, "my-counter")
.send(Counter::increment);

// Invocation options such as an idempotency key
int idempotentCount = Restate.toVirtualObject(Counter.class, "my-counter")
int idempotentCount = Restate.virtualObjectHandle(Counter.class, "my-counter")
.call(Counter::increment, InvocationOptions.idempotencyKey("my-idempotency-key"))
.await();
```
Expand Down
14 changes: 12 additions & 2 deletions client-kotlin/src/main/kotlin/dev/restate/client/kotlin/ingress.kt
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,12 @@ internal constructor(
return service(client, SVC::class.java, scopeKey)
}

/** @see Client.virtualObject */
/**
* *NOTE:* To use scopes with virtual objects you must enable the additional experimental feature
* in restate-server, via `RESTATE_EXPERIMENTAL_ENABLE_SCOPED_VIRTUAL_OBJECTS=true`.
*
* @see Client.virtualObject
*/
@org.jetbrains.annotations.ApiStatus.Experimental
inline fun <reified SVC : Any> virtualObject(key: String): SVC {
return virtualObject(client, SVC::class.java, key, scopeKey)
Expand All @@ -352,7 +357,12 @@ internal constructor(
return KClientRequestBuilder(client, SVC::class.java, null, scopeKey)
}

/** @see Client.toVirtualObject */
/**
* *NOTE:* To use scopes with virtual objects you must enable the additional experimental feature
* in restate-server, via `RESTATE_EXPERIMENTAL_ENABLE_SCOPED_VIRTUAL_OBJECTS=true`.
*
* @see Client.toVirtualObject
*/
@org.jetbrains.annotations.ApiStatus.Experimental
inline fun <reified SVC : Any> toVirtualObject(key: String): KClientRequestBuilder<SVC> {
ReflectionUtils.mustHaveVirtualObjectAnnotation(SVC::class.java)
Expand Down
48 changes: 12 additions & 36 deletions client/src/main/java/dev/restate/client/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -646,11 +646,11 @@ default <SVC> SVC service(Class<SVC> clazz) {
* Client client = Client.connect("http://localhost:8080");
*
* // Use call() with method reference and wait for the result
* Response<GreetingResponse> response = client.toService(Greeter.class)
* Response<GreetingResponse> response = client.serviceHandle(Greeter.class)
* .call(Greeter::greet, new Greeting("Alice"));
*
* // Use send() for one-way invocation without waiting
* SendResponse<GreetingResponse> sendResponse = client.toService(Greeter.class)
* SendResponse<GreetingResponse> sendResponse = client.serviceHandle(Greeter.class)
* .send(Greeter::greet, new Greeting("Alice"));
* }</pre>
*
Expand All @@ -660,22 +660,14 @@ default <SVC> SVC service(Class<SVC> clazz) {
* @param clazz the service class annotated with {@link Service}
* @return a handle to invoke the service with advanced options
*/
default <SVC> ClientServiceHandle<SVC> toService(Class<SVC> clazz) {
default <SVC> ClientServiceHandle<SVC> serviceHandle(Class<SVC> clazz) {
ReflectionUtils.mustHaveServiceAnnotation(clazz);
if (ReflectionUtils.isKotlinClass(clazz)) {
throw new IllegalArgumentException("Using Kotlin classes with Java's API is not supported");
}
return new ClientServiceHandleImpl<>(this, clazz, null);
}

/**
* @deprecated Renamed to {@link #toService(Class)}.
*/
@Deprecated(since = "2.9", forRemoval = true)
default <SVC> ClientServiceHandle<SVC> serviceHandle(Class<SVC> clazz) {
return toService(clazz);
}

/**
* Simple API to invoke a Restate Virtual Object from the ingress.
*
Expand All @@ -691,8 +683,8 @@ default <SVC> ClientServiceHandle<SVC> serviceHandle(Class<SVC> clazz) {
* }</pre>
*
* <p>For advanced use cases requiring asynchronous request handling, access to {@link Response}
* metadata, or invocation options (such as idempotency keys), use {@link #toVirtualObject(Class,
* String)} instead.
* metadata, or invocation options (such as idempotency keys), use {@link
* #virtualObjectHandle(Class, String)} instead.
*
* @param clazz the virtual object class annotated with {@link VirtualObject}
* @param key the key identifying the specific virtual object instance
Expand Down Expand Up @@ -736,11 +728,11 @@ default <SVC> SVC virtualObject(Class<SVC> clazz, String key) {
* Client client = Client.connect("http://localhost:8080");
*
* // Use call() with method reference and wait for the result
* Response<Integer> response = client.toVirtualObject(Counter.class, "my-counter")
* Response<Integer> response = client.virtualObjectHandle(Counter.class, "my-counter")
* .call(Counter::increment);
*
* // Use send() for one-way invocation without waiting
* SendResponse<Integer> sendResponse = client.toVirtualObject(Counter.class, "my-counter")
* SendResponse<Integer> sendResponse = client.virtualObjectHandle(Counter.class, "my-counter")
* .send(Counter::increment);
* }</pre>
*
Expand All @@ -751,22 +743,14 @@ default <SVC> SVC virtualObject(Class<SVC> clazz, String key) {
* @param key the key identifying the specific virtual object instance
* @return a handle to invoke the virtual object with advanced options
*/
default <SVC> ClientServiceHandle<SVC> toVirtualObject(Class<SVC> clazz, String key) {
default <SVC> ClientServiceHandle<SVC> virtualObjectHandle(Class<SVC> clazz, String key) {
ReflectionUtils.mustHaveVirtualObjectAnnotation(clazz);
if (ReflectionUtils.isKotlinClass(clazz)) {
throw new IllegalArgumentException("Using Kotlin classes with Java's API is not supported");
}
return new ClientServiceHandleImpl<>(this, clazz, key);
}

/**
* @deprecated Renamed to {@link #toVirtualObject(Class, String)}.
*/
@Deprecated(since = "2.9", forRemoval = true)
default <SVC> ClientServiceHandle<SVC> virtualObjectHandle(Class<SVC> clazz, String key) {
return toVirtualObject(clazz, key);
}

/**
* Simple API to invoke a Restate Workflow from the ingress.
*
Expand All @@ -782,7 +766,7 @@ default <SVC> ClientServiceHandle<SVC> virtualObjectHandle(Class<SVC> clazz, Str
* }</pre>
*
* <p>For advanced use cases requiring asynchronous request handling, access to {@link Response}
* metadata, or invocation options (such as idempotency keys), use {@link #toWorkflow(Class,
* metadata, or invocation options (such as idempotency keys), use {@link #workflowHandle(Class,
* String)} instead.
*
* @param clazz the workflow class annotated with {@link Workflow}
Expand Down Expand Up @@ -827,11 +811,11 @@ default <SVC> SVC workflow(Class<SVC> clazz, String key) {
* Client client = Client.connect("http://localhost:8080");
*
* // Use call() with method reference and wait for the result
* Response<OrderResult> response = client.toWorkflow(OrderWorkflow.class, "order-123")
* Response<OrderResult> response = client.workflowHandle(OrderWorkflow.class, "order-123")
* .call(OrderWorkflow::start, new OrderRequest(...));
*
* // Use send() for one-way invocation without waiting
* SendResponse<OrderResult> sendResponse = client.toWorkflow(OrderWorkflow.class, "order-123")
* SendResponse<OrderResult> sendResponse = client.workflowHandle(OrderWorkflow.class, "order-123")
* .send(OrderWorkflow::start, new OrderRequest(...));
* }</pre>
*
Expand All @@ -842,22 +826,14 @@ default <SVC> SVC workflow(Class<SVC> clazz, String key) {
* @param key the key identifying the specific workflow instance
* @return a handle to invoke the workflow with advanced options
*/
default <SVC> ClientServiceHandle<SVC> toWorkflow(Class<SVC> clazz, String key) {
default <SVC> ClientServiceHandle<SVC> workflowHandle(Class<SVC> clazz, String key) {
ReflectionUtils.mustHaveWorkflowAnnotation(clazz);
if (ReflectionUtils.isKotlinClass(clazz)) {
throw new IllegalArgumentException("Using Kotlin classes with Java's API is not supported");
}
return new ClientServiceHandleImpl<>(this, clazz, key);
}

/**
* @deprecated Renamed to {@link #toWorkflow(Class, String)}.
*/
@Deprecated(since = "2.9", forRemoval = true)
default <SVC> ClientServiceHandle<SVC> workflowHandle(Class<SVC> clazz, String key) {
return toWorkflow(clazz, key);
}

/**
* Create a default JDK client.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@
* Client client = Client.connect("http://localhost:8080");
*
* // 1. Use call() with method reference and wait for the result
* Response<GreetingResponse> response = client.toService(Greeter.class)
* Response<GreetingResponse> response = client.serviceHandle(Greeter.class)
* .call(Greeter::greet, new Greeting("Alice"));
*
* // 2. Use send() for one-way invocation without waiting
* SendResponse<GreetingResponse> sendResponse = client.toService(Greeter.class)
* SendResponse<GreetingResponse> sendResponse = client.serviceHandle(Greeter.class)
* .send(Greeter::greet, new Greeting("Alice"));
* }</pre>
*
* <p>Create instances using {@link Client#toService(Class)}, {@link Client#toVirtualObject(Class,
* String)}, or {@link Client#toWorkflow(Class, String)}.
* <p>Create instances using {@link Client#serviceHandle(Class)}, {@link
* Client#virtualObjectHandle(Class, String)}, or {@link Client#workflowHandle(Class, String)}.
*
* <p>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,
Expand Down
6 changes: 6 additions & 0 deletions client/src/main/java/dev/restate/client/ScopedClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ public <SVC> ClientServiceHandle<SVC> serviceHandle(Class<SVC> clazz) {
}

/**
* <b>NOTE:</b> To use scopes with virtual objects you must enable the additional experimental
* feature in restate-server, via {@code RESTATE_EXPERIMENTAL_ENABLE_SCOPED_VIRTUAL_OBJECTS=true}
*
* @see Client#virtualObject(Class, String)
*/
@org.jetbrains.annotations.ApiStatus.Experimental
Expand Down Expand Up @@ -100,6 +103,9 @@ public <SVC> SVC virtualObject(Class<SVC> clazz, String key) {
}

/**
* <b>NOTE:</b> To use scopes with virtual objects you must enable the additional experimental
* feature in restate-server, via {@code RESTATE_EXPERIMENTAL_ENABLE_SCOPED_VIRTUAL_OBJECTS=true}
*
* @see Client#virtualObjectHandle(Class, String)
*/
@org.jetbrains.annotations.ApiStatus.Experimental
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public String run(LoanRequest loanRequest) {
Instant executionTime;
try {
executionTime =
Restate.toService(MockBank.class)
Restate.serviceHandle(MockBank.class)
.call(
MockBank::transfer,
new TransferRequest(loanRequest.customerBankAccount(), loanRequest.amount()))
Expand Down Expand Up @@ -145,7 +145,7 @@ public static void main(String[] args) {
LoanWorkflow loanWorkflow = restateClient.workflow(LoanWorkflow.class, "my-loan");
var handle =
restateClient
.toWorkflow(LoanWorkflow.class, "my-loan")
.workflowHandle(LoanWorkflow.class, "my-loan")
.send(
LoanWorkflow::run,
new LoanRequest(
Expand Down
45 changes: 43 additions & 2 deletions sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,7 @@ class KRequestBuilder<SVC : Any>
internal constructor(
private val clazz: Class<SVC>,
private val key: String?,
private val scope: String? = null,
) {
/**
* Create a request by invoking a method on the target.
Expand All @@ -1374,7 +1375,7 @@ internal constructor(
@Suppress("UNCHECKED_CAST")
suspend fun <Res> request(block: suspend SVC.() -> Res): KRequest<Any?, Res> {
return KRequestImpl(
RequestCaptureProxy(clazz, key).capture(block as suspend SVC.() -> Any?).toRequest()
RequestCaptureProxy(clazz, key, scope).capture(block as suspend SVC.() -> Any?).toRequest()
)
as KRequest<Any?, Res>
}
Expand Down Expand Up @@ -1542,7 +1543,12 @@ internal constructor(
return service(SVC::class.java, scopeKey)
}

/** @see virtualObject */
/**
* *NOTE:* To use scopes with virtual objects you must enable the additional experimental feature
* in restate-server, via `RESTATE_EXPERIMENTAL_ENABLE_SCOPED_VIRTUAL_OBJECTS=true`.
*
* @see virtualObject
*/
@org.jetbrains.annotations.ApiStatus.Experimental
suspend inline fun <reified SVC : Any> virtualObject(key: String): SVC {
return virtualObject(SVC::class.java, key, scopeKey)
Expand All @@ -1553,6 +1559,41 @@ internal constructor(
suspend inline fun <reified SVC : Any> workflow(key: String): SVC {
return workflow(SVC::class.java, key, scopeKey)
}

/** @see toService */
@org.jetbrains.annotations.ApiStatus.Experimental
inline fun <reified SVC : Any> toService(): KRequestBuilder<SVC> {
ReflectionUtils.mustHaveServiceAnnotation(SVC::class.java)
require(ReflectionUtils.isKotlinClass(SVC::class.java)) {
"Using Java classes with Kotlin's API is not supported"
}
return KRequestBuilder(SVC::class.java, null, scopeKey)
}

/**
* *NOTE:* To use scopes with virtual objects you must enable the additional experimental feature
* in restate-server, via `RESTATE_EXPERIMENTAL_ENABLE_SCOPED_VIRTUAL_OBJECTS=true`.
*
* @see toVirtualObject
*/
@org.jetbrains.annotations.ApiStatus.Experimental
inline fun <reified SVC : Any> toVirtualObject(key: String): KRequestBuilder<SVC> {
ReflectionUtils.mustHaveVirtualObjectAnnotation(SVC::class.java)
require(ReflectionUtils.isKotlinClass(SVC::class.java)) {
"Using Java classes with Kotlin's API is not supported"
}
return KRequestBuilder(SVC::class.java, key, scopeKey)
}

/** @see toWorkflow */
@org.jetbrains.annotations.ApiStatus.Experimental
inline fun <reified SVC : Any> toWorkflow(key: String): KRequestBuilder<SVC> {
ReflectionUtils.mustHaveWorkflowAnnotation(SVC::class.java)
require(ReflectionUtils.isKotlinClass(SVC::class.java)) {
"Using Java classes with Kotlin's API is not supported"
}
return KRequestBuilder(SVC::class.java, key, scopeKey)
}
}

/**
Expand Down
Loading
Loading