From bb0612ef62c32400789748f75edaed7d53e91a65 Mon Sep 17 00:00:00 2001 From: Francesco Guardiani Date: Tue, 27 Feb 2024 09:24:03 +0100 Subject: [PATCH] Handler API (#227) * Codegen for Handler API * Reuse code between workflow and sdk-api * Use ServiceLoader to load the ServiceAdapter * Add explicit key passing * Put in place basic stuff for the new manifest * Renamings all around. --- examples/README.md | 10 +- .../dev/restate/sdk/examples/Counter.java | 10 +- .../sdk/examples/VanillaGrpcCounter.java | 14 +- .../java/my/restate/sdk/examples/Counter.java | 76 ++++ .../my/restate/sdk/examples/LoanWorkflow.java | 7 +- .../dev/restate/sdk/examples/CounterKt.kt | 12 +- .../dev/restate/sdk/protocgen/RestateGen.java | 2 +- .../src/main/resources/javaStub.mustache | 20 +- .../src/main/resources/ktStub.mustache | 14 +- sdk-api-gen/build.gradle.kts | 24 ++ .../restate/sdk/gen/ComponentProcessor.java | 131 +++++++ .../restate/sdk/gen/HandlebarsCodegen.java | 152 ++++++-- .../dev/restate/sdk/gen/ServiceProcessor.java | 71 ---- .../dev/restate/sdk/gen/model/Method.java | 149 +++++--- .../dev/restate/sdk/gen/model/MethodType.java | 4 +- .../dev/restate/sdk/gen/model/Service.java | 146 ++++++-- .../javax.annotation.processing.Processor | 2 +- .../src/main/resources/templates/Client.hbs | 109 ++++++ .../resources/templates/ComponentAdapter.hbs | 30 ++ .../resources/templates/ExternalClient.hbs | 71 ---- .../resources/templates/RestateClient.hbs | 95 ----- .../resources/templates/ServiceAdapter.hbs | 37 -- .../resources/templates/workflow/Client.hbs | 159 ++++++++ .../templates/workflow/ComponentAdapter.hbs | 42 +++ .../java/dev/restate/sdk/CodegenTest.java | 80 +++++ .../dev/restate/sdk/JavaCodegenTests.java | 29 ++ .../java/dev/restate/sdk/JsonProtoUtils.java | 52 +++ .../dev/restate/sdk/kotlin/ContextImpl.kt | 6 +- .../main/kotlin/dev/restate/sdk/kotlin/api.kt | 23 +- .../dev/restate/sdk/kotlin/AwaitableTest.kt | 32 +- .../dev/restate/sdk/kotlin/AwakeableIdTest.kt | 4 +- .../dev/restate/sdk/kotlin/EagerStateTest.kt | 22 +- .../restate/sdk/kotlin/InvocationIdTest.kt | 2 +- .../sdk/kotlin/OnlyInputAndOutputTest.kt | 2 +- .../dev/restate/sdk/kotlin/RandomTest.kt | 8 +- .../restate/sdk/kotlin/RestateCodegenTest.kt | 2 +- .../dev/restate/sdk/kotlin/SideEffectTest.kt | 16 +- .../dev/restate/sdk/kotlin/SleepTest.kt | 8 +- .../sdk/kotlin/StateMachineFailuresTest.kt | 8 +- .../dev/restate/sdk/kotlin/StateTest.kt | 10 +- .../restate/sdk/kotlin/UserFailuresTest.kt | 12 +- sdk-api/build.gradle.kts | 16 +- .../main/java/dev/restate/sdk/Awakeable.java | 2 +- .../{RestateService.java => Component.java} | 6 +- .../main/java/dev/restate/sdk/Context.java | 54 ++- .../java/dev/restate/sdk/ContextImpl.java | 28 +- .../{KeyedContext.java => ObjectContext.java} | 20 +- .../java/dev/restate/sdk/RestateRandom.java | 4 +- .../dev/restate/sdk/dynrpc/CodegenUtils.java | 218 +++++++++++ .../restate/sdk/dynrpc/DescriptorUtils.java | 125 +++++++ .../dev/restate/sdk/dynrpc/JavaComponent.java | 338 ++++++++++++++++++ .../proto/dev/restate/sdk/dynrpc/dynrpc.proto | 31 ++ sdk-api/src/main/proto/template_dynrpc.proto | 31 ++ .../java/dev/restate/sdk/AwaitableTest.java | 35 +- .../java/dev/restate/sdk/AwakeableIdTest.java | 5 +- .../java/dev/restate/sdk/EagerStateTest.java | 25 +- .../restate/sdk/GrpcChannelAdapterTest.java | 8 +- .../dev/restate/sdk/InvocationIdTest.java | 3 +- .../restate/sdk/OnlyInputAndOutputTest.java | 3 +- .../test/java/dev/restate/sdk/RandomTest.java | 4 +- .../dev/restate/sdk/RestateCodegenTest.java | 2 +- .../java/dev/restate/sdk/SideEffectTest.java | 17 +- .../test/java/dev/restate/sdk/SleepTest.java | 8 +- .../restate/sdk/StateMachineFailuresTest.java | 9 +- .../test/java/dev/restate/sdk/StateTest.java | 13 +- .../dev/restate/sdk/UserFailuresTest.java | 16 +- .../dev/restate/sdk/annotation/Exclusive.java | 7 +- .../dev/restate/sdk/annotation/Handler.java | 2 +- .../dev/restate/sdk/annotation/Service.java | 23 ++ .../dev/restate/sdk/annotation/Shared.java | 0 .../restate/sdk/annotation/VirtualObject.java | 24 ++ .../dev/restate/sdk/annotation/Workflow.java | 24 ++ ...ingService.java => BlockingComponent.java} | 8 +- .../dev/restate/sdk/common/Component.java | 19 + .../{Service.java => ComponentAdapter.java} | 8 +- ...rvicesBundle.java => ComponentBundle.java} | 6 +- ...ServiceAdapter.java => ComponentType.java} | 8 +- ...Service.java => NonBlockingComponent.java} | 8 +- .../java/dev/restate/sdk/common/Serde.java | 7 + .../java/dev/restate/sdk/common/Target.java | 71 ++++ .../common/syscalls/ComponentDefinition.java | 116 ++++++ .../common/syscalls/InvocationHandler.java | 9 +- .../restate/sdk/common/syscalls/Syscalls.java | 11 +- sdk-core/build.gradle.kts | 24 ++ .../restate/sdk/core/DeploymentManifest.java | 65 ++++ .../sdk/core/ExecutorSwitchingSyscalls.java | 21 +- .../dev/restate/sdk/core/RestateEndpoint.java | 72 ++-- .../dev/restate/sdk/core/SyscallsImpl.java | 53 ++- .../deployment_manifest_schema.json | 59 +++ ...ava => ComponentDiscoveryHandlerTest.java} | 2 +- .../dev/restate/sdk/core/TestDefinitions.java | 28 +- sdk-http-vertx/build.gradle.kts | 1 + .../vertx/RestateHttpEndpointBuilder.java | 32 +- .../testservices/BlockingGreeterService.java | 12 +- .../sdk/http/vertx/HttpVertxTestExecutor.kt | 28 +- .../restate/sdk/http/vertx/HttpVertxTests.kt | 24 +- .../sdk/http/vertx/RestateHttpEndpointTest.kt | 32 +- ...eterKtService.kt => GreeterKtComponent.kt} | 16 +- .../lambda/RestateLambdaEndpointBuilder.java | 18 +- .../testservices/JavaCounterService.java | 9 +- .../testservices/KotlinCounterService.kt | 20 +- .../sdk/testing/RestateRunnerBuilder.java | 26 +- .../java/dev/restate/sdk/testing/Counter.java | 14 +- .../sdk/workflow/impl/DescriptorUtils.java | 5 - .../impl/DurablePromiseHandleImpl.java | 6 +- .../sdk/workflow/impl/DurablePromiseImpl.java | 8 +- .../workflow/impl/WorkflowCodegenUtil.java | 33 +- ...ndle.java => WorkflowComponentBundle.java} | 78 ++-- .../workflow/impl/WorkflowContextImpl.java | 21 +- .../sdk/workflow/impl/WorkflowImpl.java | 50 +-- .../workflow/impl/WorkflowManagerImpl.java | 26 +- .../impl/WorkflowMangledDescriptors.java | 2 +- 112 files changed, 2936 insertions(+), 924 deletions(-) create mode 100644 examples/src/main/java/my/restate/sdk/examples/Counter.java create mode 100644 sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java delete mode 100644 sdk-api-gen/src/main/java/dev/restate/sdk/gen/ServiceProcessor.java create mode 100644 sdk-api-gen/src/main/resources/templates/Client.hbs create mode 100644 sdk-api-gen/src/main/resources/templates/ComponentAdapter.hbs delete mode 100644 sdk-api-gen/src/main/resources/templates/ExternalClient.hbs delete mode 100644 sdk-api-gen/src/main/resources/templates/RestateClient.hbs delete mode 100644 sdk-api-gen/src/main/resources/templates/ServiceAdapter.hbs create mode 100644 sdk-api-gen/src/main/resources/templates/workflow/Client.hbs create mode 100644 sdk-api-gen/src/main/resources/templates/workflow/ComponentAdapter.hbs create mode 100644 sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java create mode 100644 sdk-api-gen/src/test/java/dev/restate/sdk/JavaCodegenTests.java create mode 100644 sdk-api-gen/src/test/java/dev/restate/sdk/JsonProtoUtils.java rename sdk-api/src/main/java/dev/restate/sdk/{RestateService.java => Component.java} (84%) rename sdk-api/src/main/java/dev/restate/sdk/{KeyedContext.java => ObjectContext.java} (74%) create mode 100644 sdk-api/src/main/java/dev/restate/sdk/dynrpc/CodegenUtils.java create mode 100644 sdk-api/src/main/java/dev/restate/sdk/dynrpc/DescriptorUtils.java create mode 100644 sdk-api/src/main/java/dev/restate/sdk/dynrpc/JavaComponent.java create mode 100644 sdk-api/src/main/proto/dev/restate/sdk/dynrpc/dynrpc.proto create mode 100644 sdk-api/src/main/proto/template_dynrpc.proto rename sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/Service.java => sdk-common/src/main/java/dev/restate/sdk/annotation/Exclusive.java (87%) rename sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/Workflow.java => sdk-common/src/main/java/dev/restate/sdk/annotation/Handler.java (95%) create mode 100644 sdk-common/src/main/java/dev/restate/sdk/annotation/Service.java rename {sdk-workflow-api => sdk-common}/src/main/java/dev/restate/sdk/annotation/Shared.java (100%) create mode 100644 sdk-common/src/main/java/dev/restate/sdk/annotation/VirtualObject.java create mode 100644 sdk-common/src/main/java/dev/restate/sdk/annotation/Workflow.java rename sdk-common/src/main/java/dev/restate/sdk/common/{BlockingService.java => BlockingComponent.java} (55%) create mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/Component.java rename sdk-common/src/main/java/dev/restate/sdk/common/{Service.java => ComponentAdapter.java} (72%) rename sdk-common/src/main/java/dev/restate/sdk/common/{ServicesBundle.java => ComponentBundle.java} (76%) rename sdk-common/src/main/java/dev/restate/sdk/common/{ServiceAdapter.java => ComponentType.java} (79%) rename sdk-common/src/main/java/dev/restate/sdk/common/{NonBlockingService.java => NonBlockingComponent.java} (53%) create mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/Target.java create mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ComponentDefinition.java rename sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/ServiceType.java => sdk-common/src/main/java/dev/restate/sdk/common/syscalls/InvocationHandler.java (61%) create mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java create mode 100644 sdk-core/src/main/resources/json_schema/deployment_manifest_schema.json rename sdk-core/src/test/java/dev/restate/sdk/core/{ServiceDiscoveryHandlerTest.java => ComponentDiscoveryHandlerTest.java} (97%) rename sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/testservices/{GreeterKtService.kt => GreeterKtComponent.kt} (69%) rename sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/{WorkflowServicesBundle.java => WorkflowComponentBundle.java} (59%) diff --git a/examples/README.md b/examples/README.md index 4e1d59dd..7736a1a6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,7 +6,7 @@ For a sample project configuration and more elaborated examples, check out the t Available examples: -* [`Counter`](src/main/java/dev/restate/sdk/examples/Counter.java): Shows a simple service using state primitives. +* [`Counter`](src/main/java/dev/restate/sdk/examples/Counter.java): Shows a simple virtual object using state primitives. * [`VanillaGrpcCounter`](src/main/java/dev/restate/sdk/examples/VanillaGrpcCounter.java): Same as `Counter` but using the vanilla gRPC code generator output. * [`CounterKt`](src/main/kotlin/dev/restate/sdk/examples/CounterKt.kt): Same as `Counter` but using Kotlin. @@ -22,11 +22,11 @@ You'll find the shadowed jar in the `build` directory. The class to configure in Lambda is `dev.restate.sdk.examples.LambdaHandler`. -By default, the [`dev.restate.sdk.examples.Counter`](src/main/java/dev/restate/sdk/examples/Counter.java) service is deployed. Set the env variable `LAMBDA_FACTORY_SERVICE_CLASS` to one of the available example classes to change the deployed class. +By default, the [`dev.restate.sdk.examples.Counter`](src/main/java/dev/restate/sdk/examples/Counter.java) component is deployed. Set the env variable `LAMBDA_FACTORY_SERVICE_CLASS` to one of the available example classes to change the deployed class. ## Running the examples (HTTP) -You can run the Java counter service via: +You can run the Java counter component via: ```shell ./gradlew :examples:run @@ -38,9 +38,9 @@ You can modify the class to run setting `-PmainClass=`, for example, in or ./gradlew :examples:run -PmainClass=dev.restate.sdk.examples.CounterKt ``` -## Invoking the counter service +## Invoking the counter component -If you want to invoke the counter service via [grpcurl](https://github.com/fullstorydev/grpcurl): +If you want to invoke the counter component via [grpcurl](https://github.com/fullstorydev/grpcurl): ```shell grpcurl -plaintext -d '{"counter_name": "my_counter"}' localhost:9090 counter.Counter/Get diff --git a/examples/src/main/java/dev/restate/sdk/examples/Counter.java b/examples/src/main/java/dev/restate/sdk/examples/Counter.java index 9d4a112b..0d854238 100644 --- a/examples/src/main/java/dev/restate/sdk/examples/Counter.java +++ b/examples/src/main/java/dev/restate/sdk/examples/Counter.java @@ -8,7 +8,7 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.examples; -import dev.restate.sdk.KeyedContext; +import dev.restate.sdk.ObjectContext; import dev.restate.sdk.common.CoreSerdes; import dev.restate.sdk.common.StateKey; import dev.restate.sdk.examples.generated.*; @@ -23,26 +23,26 @@ public class Counter extends CounterRestate.CounterRestateImplBase { private static final StateKey TOTAL = StateKey.of("total", CoreSerdes.JSON_LONG); @Override - public void reset(KeyedContext ctx, CounterRequest request) { + public void reset(ObjectContext ctx, CounterRequest request) { ctx.clear(TOTAL); } @Override - public void add(KeyedContext ctx, CounterAddRequest request) { + public void add(ObjectContext ctx, CounterAddRequest request) { long currentValue = ctx.get(TOTAL).orElse(0L); long newValue = currentValue + request.getValue(); ctx.set(TOTAL, newValue); } @Override - public GetResponse get(KeyedContext ctx, CounterRequest request) { + public GetResponse get(ObjectContext ctx, CounterRequest request) { long currentValue = ctx.get(TOTAL).orElse(0L); return GetResponse.newBuilder().setValue(currentValue).build(); } @Override - public CounterUpdateResult getAndAdd(KeyedContext ctx, CounterAddRequest request) { + public CounterUpdateResult getAndAdd(ObjectContext ctx, CounterAddRequest request) { LOG.info("Invoked get and add with " + request.getValue()); long currentValue = ctx.get(TOTAL).orElse(0L); diff --git a/examples/src/main/java/dev/restate/sdk/examples/VanillaGrpcCounter.java b/examples/src/main/java/dev/restate/sdk/examples/VanillaGrpcCounter.java index fc8611ab..3c7c712b 100644 --- a/examples/src/main/java/dev/restate/sdk/examples/VanillaGrpcCounter.java +++ b/examples/src/main/java/dev/restate/sdk/examples/VanillaGrpcCounter.java @@ -9,8 +9,8 @@ package dev.restate.sdk.examples; import com.google.protobuf.Empty; -import dev.restate.sdk.KeyedContext; -import dev.restate.sdk.RestateService; +import dev.restate.sdk.Component; +import dev.restate.sdk.ObjectContext; import dev.restate.sdk.common.CoreSerdes; import dev.restate.sdk.common.StateKey; import dev.restate.sdk.examples.generated.*; @@ -19,7 +19,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public class VanillaGrpcCounter extends CounterGrpc.CounterImplBase implements RestateService { +public class VanillaGrpcCounter extends CounterGrpc.CounterImplBase implements Component { private static final Logger LOG = LogManager.getLogger(VanillaGrpcCounter.class); @@ -27,7 +27,7 @@ public class VanillaGrpcCounter extends CounterGrpc.CounterImplBase implements R @Override public void reset(CounterRequest request, StreamObserver responseObserver) { - KeyedContext.current().clear(TOTAL); + ObjectContext.current().clear(TOTAL); responseObserver.onNext(Empty.getDefaultInstance()); responseObserver.onCompleted(); @@ -35,7 +35,7 @@ public void reset(CounterRequest request, StreamObserver responseObserver @Override public void add(CounterAddRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); long currentValue = ctx.get(TOTAL).orElse(0L); long newValue = currentValue + request.getValue(); @@ -47,7 +47,7 @@ public void add(CounterAddRequest request, StreamObserver responseObserve @Override public void get(CounterRequest request, StreamObserver responseObserver) { - long currentValue = KeyedContext.current().get(TOTAL).orElse(0L); + long currentValue = ObjectContext.current().get(TOTAL).orElse(0L); responseObserver.onNext(GetResponse.newBuilder().setValue(currentValue).build()); responseObserver.onCompleted(); @@ -58,7 +58,7 @@ public void getAndAdd( CounterAddRequest request, StreamObserver responseObserver) { LOG.info("Invoked get and add with " + request.getValue()); - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); long currentValue = ctx.get(TOTAL).orElse(0L); long newValue = currentValue + request.getValue(); diff --git a/examples/src/main/java/my/restate/sdk/examples/Counter.java b/examples/src/main/java/my/restate/sdk/examples/Counter.java new file mode 100644 index 00000000..631082bf --- /dev/null +++ b/examples/src/main/java/my/restate/sdk/examples/Counter.java @@ -0,0 +1,76 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package my.restate.sdk.examples; + +import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.annotation.Handler; +import dev.restate.sdk.annotation.VirtualObject; +import dev.restate.sdk.common.CoreSerdes; +import dev.restate.sdk.common.StateKey; +import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@VirtualObject +public class Counter { + + private static final Logger LOG = LogManager.getLogger(Counter.class); + + private static final StateKey TOTAL = StateKey.of("total", CoreSerdes.JSON_LONG); + + @Handler + public void reset(ObjectContext ctx) { + ctx.clearAll(); + } + + @Handler + public void add(ObjectContext ctx, Long request) { + long currentValue = ctx.get(TOTAL).orElse(0L); + long newValue = currentValue + request; + ctx.set(TOTAL, newValue); + } + + @Handler + public Long get(ObjectContext ctx) { + return ctx.get(TOTAL).orElse(0L); + } + + @Handler + public CounterUpdateResult getAndAdd(ObjectContext ctx, Long request) { + LOG.info("Invoked get and add with " + request); + + long currentValue = ctx.get(TOTAL).orElse(0L); + long newValue = currentValue + request; + ctx.set(TOTAL, newValue); + + return new CounterUpdateResult(newValue, currentValue); + } + + public static void main(String[] args) { + RestateHttpEndpointBuilder.builder().with(new Counter()).buildAndListen(); + } + + public static class CounterUpdateResult { + private final Long newValue; + private final Long oldValue; + + public CounterUpdateResult(Long newValue, Long oldValue) { + this.newValue = newValue; + this.oldValue = oldValue; + } + + public Long getNewValue() { + return newValue; + } + + public Long getOldValue() { + return oldValue; + } + } +} 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 97482593..a0a784b4 100644 --- a/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java +++ b/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java @@ -11,8 +11,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import dev.restate.sdk.Context; -import dev.restate.sdk.annotation.Service; -import dev.restate.sdk.annotation.ServiceType; import dev.restate.sdk.annotation.Shared; import dev.restate.sdk.annotation.Workflow; import dev.restate.sdk.common.CoreSerdes; @@ -36,7 +34,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -@Service(ServiceType.WORKFLOW) +@Workflow public class LoanWorkflow { // --- Data types used by the Loan Worfklow @@ -176,7 +174,8 @@ public static void main(String[] args) { // To invoke the workflow: Channel restateChannel = NettyChannelBuilder.forAddress("127.0.0.1", 8080).usePlaintext().build(); - LoanWorkflowExternalClient client = new LoanWorkflowExternalClient(restateChannel, "my-loan"); + LoanWorkflowClient.IngressClient client = + LoanWorkflowClient.fromIngress(restateChannel, "my-loan"); WorkflowExecutionState state = client.submit( diff --git a/examples/src/main/kotlin/dev/restate/sdk/examples/CounterKt.kt b/examples/src/main/kotlin/dev/restate/sdk/examples/CounterKt.kt index 3137a15a..cd58248c 100644 --- a/examples/src/main/kotlin/dev/restate/sdk/examples/CounterKt.kt +++ b/examples/src/main/kotlin/dev/restate/sdk/examples/CounterKt.kt @@ -11,8 +11,8 @@ package dev.restate.sdk.examples import dev.restate.sdk.common.StateKey import dev.restate.sdk.examples.generated.* import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder -import dev.restate.sdk.kotlin.KeyedContext import dev.restate.sdk.kotlin.KtSerdes +import dev.restate.sdk.kotlin.ObjectContext import org.apache.logging.log4j.LogManager class CounterKt : CounterRestateKt.CounterRestateKtImplBase() { @@ -21,20 +21,20 @@ class CounterKt : CounterRestateKt.CounterRestateKtImplBase() { private val TOTAL = StateKey.of("total", KtSerdes.json()) - override suspend fun reset(context: KeyedContext, request: CounterRequest) { + override suspend fun reset(context: ObjectContext, request: CounterRequest) { context.clear(TOTAL) } - override suspend fun add(context: KeyedContext, request: CounterAddRequest) { + override suspend fun add(context: ObjectContext, request: CounterAddRequest) { updateCounter(context, request.value) } - override suspend fun get(context: KeyedContext, request: CounterRequest): GetResponse { + override suspend fun get(context: ObjectContext, request: CounterRequest): GetResponse { return getResponse { value = context.get(TOTAL) ?: 0L } } override suspend fun getAndAdd( - context: KeyedContext, + context: ObjectContext, request: CounterAddRequest ): CounterUpdateResult { LOG.info("Invoked get and add with " + request.value) @@ -45,7 +45,7 @@ class CounterKt : CounterRestateKt.CounterRestateKtImplBase() { } } - private suspend fun updateCounter(context: KeyedContext, add: Long): Pair { + private suspend fun updateCounter(context: ObjectContext, add: Long): Pair { val currentValue = context.get(TOTAL) ?: 0L val newValue = currentValue + add diff --git a/protoc-gen-restate/src/main/java/dev/restate/sdk/protocgen/RestateGen.java b/protoc-gen-restate/src/main/java/dev/restate/sdk/protocgen/RestateGen.java index 508fb338..2373d1bc 100644 --- a/protoc-gen-restate/src/main/java/dev/restate/sdk/protocgen/RestateGen.java +++ b/protoc-gen-restate/src/main/java/dev/restate/sdk/protocgen/RestateGen.java @@ -125,7 +125,7 @@ private ServiceContext buildServiceContext( serviceContext.contextType = serviceProto.getOptions().getExtension(Ext.serviceType) == ServiceType.UNKEYED ? "Context" - : "KeyedContext"; + : "ObjectContext"; // Resolve javadoc DescriptorProtos.SourceCodeInfo.Location serviceLocation = diff --git a/protoc-gen-restate/src/main/resources/javaStub.mustache b/protoc-gen-restate/src/main/resources/javaStub.mustache index d2da2251..e66f6bc9 100644 --- a/protoc-gen-restate/src/main/resources/javaStub.mustache +++ b/protoc-gen-restate/src/main/resources/javaStub.mustache @@ -3,7 +3,7 @@ package {{packageName}}; {{/packageName}} import dev.restate.sdk.Context; -import dev.restate.sdk.KeyedContext; +import dev.restate.sdk.ObjectContext; import dev.restate.sdk.Awaitable; import dev.restate.sdk.common.syscalls.Syscalls; import java.time.Duration; @@ -16,7 +16,7 @@ public class {{className}} { private {{className}}() {} /** - * Create a new client from the given {@link KeyedContext}. + * Create a new client from the given {@link ObjectContext}. */ public static {{serviceName}}RestateClient newClient(Context ctx) { return new {{serviceName}}RestateClient(ctx); @@ -87,7 +87,7 @@ public class {{className}} { } {{{apidoc}}} - public static abstract class {{serviceName}}RestateImplBase implements dev.restate.sdk.RestateService { + public static abstract class {{serviceName}}RestateImplBase implements dev.restate.sdk.Component { {{#methods}} {{#deprecated}} @@ -114,34 +114,34 @@ public class {{className}} { private static final class HandlerAdapter implements io.grpc.stub.ServerCalls.UnaryMethod { - private final java.util.function.BiFunction handler; + private final java.util.function.BiFunction handler; - private HandlerAdapter(java.util.function.BiFunction handler) { + private HandlerAdapter(java.util.function.BiFunction handler) { this.handler = handler; } @Override public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { - responseObserver.onNext(handler.apply(KeyedContext.fromSyscalls(Syscalls.current()), request)); + responseObserver.onNext(handler.apply(ObjectContext.fromSyscalls(Syscalls.current()), request)); responseObserver.onCompleted(); } - private static HandlerAdapter of(java.util.function.BiFunction handler) { + private static HandlerAdapter of(java.util.function.BiFunction handler) { return new HandlerAdapter<>(handler); } - private static HandlerAdapter of(java.util.function.Function handler) { + private static HandlerAdapter of(java.util.function.Function handler) { return new HandlerAdapter<>((ctx, e) -> handler.apply(ctx)); } - private static HandlerAdapter of(java.util.function.BiConsumer handler) { + private static HandlerAdapter of(java.util.function.BiConsumer handler) { return new HandlerAdapter<>((ctx, req) -> { handler.accept(ctx, req); return com.google.protobuf.Empty.getDefaultInstance(); }); } - private static HandlerAdapter of(java.util.function.Consumer handler) { + private static HandlerAdapter of(java.util.function.Consumer handler) { return new HandlerAdapter<>((ctx, req) -> { handler.accept(ctx); return com.google.protobuf.Empty.getDefaultInstance(); diff --git a/protoc-gen-restate/src/main/resources/ktStub.mustache b/protoc-gen-restate/src/main/resources/ktStub.mustache index 4080839a..3015fa4a 100644 --- a/protoc-gen-restate/src/main/resources/ktStub.mustache +++ b/protoc-gen-restate/src/main/resources/ktStub.mustache @@ -3,9 +3,9 @@ package {{packageName}}; {{/packageName}} import dev.restate.sdk.kotlin.Context; -import dev.restate.sdk.kotlin.KeyedContext; +import dev.restate.sdk.kotlin.ObjectContext; import dev.restate.sdk.kotlin.Awaitable; -import dev.restate.sdk.kotlin.RestateKtService; +import dev.restate.sdk.kotlin.RestateKtComponent; import dev.restate.sdk.common.syscalls.Syscalls; import io.grpc.kotlin.ClientCalls.unaryRpc import io.grpc.kotlin.ServerCalls.unaryServerMethodDefinition @@ -71,7 +71,7 @@ public object {{className}} { {{{javadoc}}} public abstract class {{serviceName}}RestateKtImplBase( private val coroutineContext: kotlin.coroutines.CoroutineContext = kotlinx.coroutines.Dispatchers.Unconfined, - ): RestateKtService { + ): RestateKtComponent { {{#methods}} {{#deprecated}} @@ -91,18 +91,18 @@ public object {{className}} { descriptor = {{packageName}}.{{serviceName}}Grpc.{{methodDescriptorGetter}}(), implementation = { {{#isInputEmpty}}{{#isOutputEmpty}} - {{methodName}}(KeyedContext.fromSyscalls(Syscalls.current())) + {{methodName}}(ObjectContext.fromSyscalls(Syscalls.current())) return@unaryServerMethodDefinition com.google.protobuf.Empty.getDefaultInstance() {{/isOutputEmpty}}{{/isInputEmpty}} {{#isInputEmpty}}{{^isOutputEmpty}} - return@unaryServerMethodDefinition {{methodName}}(KeyedContext.fromSyscalls(Syscalls.current())) + return@unaryServerMethodDefinition {{methodName}}(ObjectContext.fromSyscalls(Syscalls.current())) {{/isOutputEmpty}}{{/isInputEmpty}} {{^isInputEmpty}}{{#isOutputEmpty}} - {{methodName}}(KeyedContext.fromSyscalls(Syscalls.current()), it) + {{methodName}}(ObjectContext.fromSyscalls(Syscalls.current()), it) return@unaryServerMethodDefinition com.google.protobuf.Empty.getDefaultInstance() {{/isOutputEmpty}}{{/isInputEmpty}} {{^isInputEmpty}}{{^isOutputEmpty}} - return@unaryServerMethodDefinition {{methodName}}(KeyedContext.fromSyscalls(Syscalls.current()), it) + return@unaryServerMethodDefinition {{methodName}}(ObjectContext.fromSyscalls(Syscalls.current()), it) {{/isOutputEmpty}}{{/isInputEmpty}} } )) diff --git a/sdk-api-gen/build.gradle.kts b/sdk-api-gen/build.gradle.kts index 7856f308..bc22b5ef 100644 --- a/sdk-api-gen/build.gradle.kts +++ b/sdk-api-gen/build.gradle.kts @@ -13,4 +13,28 @@ dependencies { implementation(project(":sdk-serde-jackson")) implementation("com.github.jknack:handlebars:4.3.1") + + testAnnotationProcessor(project(":sdk-api-gen")) + testImplementation(project(":sdk-core")) + testImplementation(testingLibs.junit.jupiter) + testImplementation(testingLibs.assertj) + testImplementation(coreLibs.protobuf.java) + testImplementation(coreLibs.log4j.core) + testCompileOnly(coreLibs.javax.annotation.api) + + // Import test suites from sdk-core + testImplementation(project(":sdk-core", "testArchive")) + testProtobuf(project(":sdk-core", "testArchive")) +} + +// Generate test jar + +configurations { register("testArchive") } + +tasks.register("testJar") { + archiveClassifier.set("tests") + + from(project.the()["test"].output) } + +artifacts { add("testArchive", tasks["testJar"]) } diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java new file mode 100644 index 00000000..4002fac8 --- /dev/null +++ b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java @@ -0,0 +1,131 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk.gen; + +import dev.restate.sdk.common.ComponentAdapter; +import dev.restate.sdk.common.ComponentType; +import dev.restate.sdk.gen.model.Service; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.*; +import java.util.stream.Collectors; +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import javax.tools.FileObject; +import javax.tools.StandardLocation; + +@SupportedAnnotationTypes({ + "dev.restate.sdk.annotation.Service", + "dev.restate.sdk.annotation.Workflow", + "dev.restate.sdk.annotation.VirtualObject" +}) +@SupportedSourceVersion(SourceVersion.RELEASE_11) +public class ComponentProcessor extends AbstractProcessor { + + private HandlebarsCodegen serviceAdapterCodegen; + private HandlebarsCodegen clientCodegen; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + + this.serviceAdapterCodegen = + new HandlebarsCodegen( + processingEnv.getFiler(), + "ComponentAdapter", + Map.of( + ComponentType.WORKFLOW, + "templates.workflow", + ComponentType.SERVICE, + "templates", + ComponentType.VIRTUAL_OBJECT, + "templates")); + this.clientCodegen = + new HandlebarsCodegen( + processingEnv.getFiler(), + "Client", + Map.of( + ComponentType.WORKFLOW, + "templates.workflow", + ComponentType.SERVICE, + "templates", + ComponentType.VIRTUAL_OBJECT, + "templates")); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + // Parsing phase + List parsedServices = + annotations.stream() + .flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream()) + .filter(e -> e.getKind().isClass() || e.getKind().isInterface()) + .map( + e -> + Service.fromTypeElement( + (TypeElement) e, + processingEnv.getMessager(), + processingEnv.getElementUtils(), + processingEnv.getTypeUtils())) + .collect(Collectors.toList()); + + // Run code generation + for (Service e : parsedServices) { + try { + this.serviceAdapterCodegen.generate(e); + this.clientCodegen.generate(e); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + // META-INF + Path resourceFilePath; + try { + resourceFilePath = + readOrCreateResource( + processingEnv.getFiler(), + "META-INF/services/" + ComponentAdapter.class.getCanonicalName()); + Files.createDirectories(resourceFilePath.getParent()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try (BufferedWriter writer = + Files.newBufferedWriter( + resourceFilePath, + StandardCharsets.UTF_8, + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND)) { + for (Service svc : parsedServices) { + writer.write(svc.getGeneratedClassFqcnPrefix() + "ComponentAdapter"); + writer.write('\n'); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + return false; + } + + public static Path readOrCreateResource(Filer filer, String file) throws IOException { + try { + FileObject fileObject = filer.getResource(StandardLocation.CLASS_OUTPUT, "", file); + return new File(fileObject.toUri()).toPath(); + } catch (IOException e) { + FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", file); + return new File(fileObject.toUri()).toPath(); + } + } +} diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/HandlebarsCodegen.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/HandlebarsCodegen.java index b06adcfc..b16d211c 100644 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/HandlebarsCodegen.java +++ b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/HandlebarsCodegen.java @@ -12,8 +12,10 @@ import com.github.jknack.handlebars.Handlebars; import com.github.jknack.handlebars.Template; import com.github.jknack.handlebars.context.FieldValueResolver; +import com.github.jknack.handlebars.helper.StringHelpers; import com.github.jknack.handlebars.io.AbstractTemplateLoader; import com.github.jknack.handlebars.io.TemplateSource; +import dev.restate.sdk.common.ComponentType; import dev.restate.sdk.gen.model.Method; import dev.restate.sdk.gen.model.MethodType; import dev.restate.sdk.gen.model.Service; @@ -21,6 +23,7 @@ import java.io.Writer; import java.nio.charset.Charset; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.annotation.processing.Filer; @@ -31,79 +34,121 @@ public class HandlebarsCodegen { private final Filer filer; - private final String templateName; - private final Template template; + private final String baseTemplateName; + private final Map templates; - public HandlebarsCodegen(Filer filer, String templateName) throws IOException { + public HandlebarsCodegen( + Filer filer, String baseTemplateName, Map templates) { this.filer = filer; - this.templateName = templateName; - - Handlebars handlebars = new Handlebars(new FilerTemplateLoader(filer)); - this.template = handlebars.compile(templateName); + this.baseTemplateName = baseTemplateName; + + Handlebars handlebars = new Handlebars(new FilerTemplateLoader(filer, this.baseTemplateName)); + handlebars.registerHelpers(StringHelpers.class); + + this.templates = + templates.entrySet().stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + e -> { + try { + return handlebars.compile(e.getValue()); + } catch (IOException ex) { + throw new RuntimeException( + "Can't compile template for service " + + e.getKey() + + " with base template name " + + baseTemplateName, + ex); + } + })); } public void generate(Service service) throws IOException { JavaFileObject entityAdapterFile = - filer.createSourceFile(service.getFqcn() + this.templateName); + filer.createSourceFile(service.getGeneratedClassFqcnPrefix() + this.baseTemplateName); try (Writer out = entityAdapterFile.openWriter()) { - this.template.apply( - Context.newBuilder(new EntityTemplateModel(service)) - .resolver(FieldValueResolver.INSTANCE) - .build(), - out); + this.templates + .get(service.getComponentType()) + .apply( + Context.newBuilder(new EntityTemplateModel(service, this.baseTemplateName)) + .resolver(FieldValueResolver.INSTANCE) + .build(), + out); } } // --- classes to interact with the handlebars template static class EntityTemplateModel { - public final String packageName; - public final String className; - public final String fqcn; + public final String originalClassPkg; + public final String originalClassFqcn; + public final String generatedClassSimpleNamePrefix; + public final String generatedClassSimpleName; + public final String componentName; + public final String componentType; + public final boolean isWorkflow; + public final boolean isObject; + public final boolean isService; public final List methods; - private EntityTemplateModel(Service inner) { - this.packageName = inner.getPkg() != null ? inner.getPkg().toString() : null; - this.className = inner.getSimpleClassName().toString(); - this.fqcn = inner.getFqcn().toString(); + private EntityTemplateModel(Service inner, String baseTemplateName) { + this.originalClassPkg = inner.getTargetPkg().toString(); + this.originalClassFqcn = inner.getTargetFqcn().toString(); + this.generatedClassSimpleNamePrefix = inner.getSimpleComponentName(); + this.generatedClassSimpleName = this.generatedClassSimpleNamePrefix + baseTemplateName; + this.componentName = inner.getFullyQualifiedComponentName(); + + this.componentType = inner.getComponentType().toString(); + this.isWorkflow = inner.getComponentType() == ComponentType.WORKFLOW; + this.isObject = inner.getComponentType() == ComponentType.VIRTUAL_OBJECT; + this.isService = inner.getComponentType() == ComponentType.SERVICE; + this.methods = inner.getMethods().stream().map(MethodTemplateModel::new).collect(Collectors.toList()); } } static class MethodTemplateModel { - public final String builderMethod; public final String name; public final String descFieldName; + public final String methodType; public final boolean isWorkflow; public final boolean isShared; + public final boolean isStateless; + public final boolean isExclusive; public final boolean inputEmpty; public final String inputFqcn; public final String inputSerdeDecl; + public final String boxedInputFqcn; public final String inputSerdeFieldName; public final boolean outputEmpty; public final String outputFqcn; public final String outputSerdeDecl; + public final String boxedOutputFqcn; public final String outputSerdeFieldName; private MethodTemplateModel(Method inner) { - this.builderMethod = - inner.getMethodType().equals(MethodType.SHARED) ? "withShared" : "withExclusive"; this.name = inner.getName().toString(); this.descFieldName = "DESC_" + this.name.toUpperCase(); + this.methodType = inner.getMethodType().toString(); this.isWorkflow = inner.getMethodType() == MethodType.WORKFLOW; this.isShared = inner.getMethodType() == MethodType.SHARED; + this.isExclusive = inner.getMethodType() == MethodType.EXCLUSIVE; + this.isStateless = inner.getMethodType() == MethodType.STATELESS; this.inputEmpty = inner.getInputType() == null; this.inputFqcn = this.inputEmpty ? "" : inner.getInputType().toString(); this.inputSerdeDecl = serdeDecl(inner.getInputType()); + this.boxedInputFqcn = boxedType(inner.getInputType()); this.inputSerdeFieldName = "SERDE_" + this.name.toUpperCase() + "_INPUT"; this.outputEmpty = inner.getOutputType() == null; this.outputFqcn = this.outputEmpty ? "" : inner.getOutputType().toString(); this.outputSerdeDecl = serdeDecl(inner.getOutputType()); + this.boxedOutputFqcn = boxedType(inner.getOutputType()); this.outputSerdeFieldName = "SERDE_" + this.name.toUpperCase() + "_OUTPUT"; } @@ -111,9 +156,55 @@ private static String serdeDecl(@Nullable TypeMirror ty) { if (ty == null) { return "dev.restate.sdk.common.CoreSerdes.VOID"; } - return "dev.restate.sdk.serde.jackson.JacksonSerdes.of(new com.fasterxml.jackson.core.type.TypeReference<" - + ty - + ">() {})"; + switch (ty.getKind()) { + case BOOLEAN: + return "dev.restate.sdk.common.CoreSerdes.JSON_BOOLEAN"; + case BYTE: + return "dev.restate.sdk.common.CoreSerdes.JSON_BYTE"; + case SHORT: + return "dev.restate.sdk.common.CoreSerdes.JSON_SHORT"; + case INT: + return "dev.restate.sdk.common.CoreSerdes.JSON_INT"; + case LONG: + return "dev.restate.sdk.common.CoreSerdes.JSON_LONG"; + case CHAR: + return "dev.restate.sdk.common.CoreSerdes.JSON_CHAR"; + case FLOAT: + return "dev.restate.sdk.common.CoreSerdes.JSON_FLOAT"; + case DOUBLE: + return "dev.restate.sdk.common.CoreSerdes.JSON_DOUBLE"; + default: + // Default to Jackson type reference serde + return "dev.restate.sdk.serde.jackson.JacksonSerdes.of(new com.fasterxml.jackson.core.type.TypeReference<" + + ty + + ">() {})"; + } + } + + private static String boxedType(@Nullable TypeMirror ty) { + if (ty == null) { + return "Void"; + } + switch (ty.getKind()) { + case BOOLEAN: + return "Boolean"; + case BYTE: + return "Byte"; + case SHORT: + return "Short"; + case INT: + return "Integer"; + case LONG: + return "Long"; + case CHAR: + return "Char"; + case FLOAT: + return "Float"; + case DOUBLE: + return "Double"; + default: + return ty.toString(); + } } } @@ -121,9 +212,11 @@ private static String serdeDecl(@Nullable TypeMirror ty) { // processor context private static class FilerTemplateLoader extends AbstractTemplateLoader { private final Filer filer; + private final String templateName; - public FilerTemplateLoader(Filer filer) { + public FilerTemplateLoader(Filer filer, String baseTemplateName) { this.filer = filer; + this.templateName = baseTemplateName + ".hbs"; } @Override @@ -132,15 +225,14 @@ public TemplateSource sourceAt(String location) { @Override public String content(Charset charset) throws IOException { return filer - .getResource( - StandardLocation.ANNOTATION_PROCESSOR_PATH, "templates", location + ".hbs") + .getResource(StandardLocation.ANNOTATION_PROCESSOR_PATH, location, templateName) .getCharContent(true) .toString(); } @Override public String filename() { - return "/templates/" + location + ".hbs"; + return "/" + location.replace('.', '/') + "/" + templateName; } @Override 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 deleted file mode 100644 index 6d8f56d9..00000000 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ServiceProcessor.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// 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 -package dev.restate.sdk.gen; - -import dev.restate.sdk.gen.model.Service; -import java.io.IOException; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import javax.annotation.processing.*; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.TypeElement; - -@SupportedAnnotationTypes("dev.restate.sdk.annotation.Service") -@SupportedSourceVersion(SourceVersion.RELEASE_11) -public class ServiceProcessor extends AbstractProcessor { - - private HandlebarsCodegen serviceAdapterCodegen; - private HandlebarsCodegen externalClientCodegen; - private HandlebarsCodegen restateClientCodegen; - - @Override - public synchronized void init(ProcessingEnvironment processingEnv) { - super.init(processingEnv); - - try { - this.serviceAdapterCodegen = - new HandlebarsCodegen(processingEnv.getFiler(), "ServiceAdapter"); - this.externalClientCodegen = - new HandlebarsCodegen(processingEnv.getFiler(), "ExternalClient"); - this.restateClientCodegen = new HandlebarsCodegen(processingEnv.getFiler(), "RestateClient"); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - // Parsing phase - List parsedServices = - annotations.stream() - .flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream()) - .map( - e -> - Service.fromTypeElement( - (TypeElement) e, - processingEnv.getMessager(), - processingEnv.getElementUtils(), - processingEnv.getTypeUtils())) - .collect(Collectors.toList()); - - // Run code generation - for (Service e : parsedServices) { - try { - this.serviceAdapterCodegen.generate(e); - this.externalClientCodegen.generate(e); - this.restateClientCodegen.generate(e); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - return true; - } -} diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/Method.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/Method.java index ae0bf64a..9db38bd4 100644 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/Method.java +++ b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/Method.java @@ -8,15 +8,16 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.gen.model; -import dev.restate.sdk.annotation.Shared; -import dev.restate.sdk.annotation.Workflow; +import dev.restate.sdk.Context; +import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.annotation.*; +import dev.restate.sdk.common.ComponentType; import dev.restate.sdk.workflow.WorkflowContext; import dev.restate.sdk.workflow.WorkflowSharedContext; import javax.annotation.Nullable; import javax.annotation.processing.Messager; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; @@ -60,7 +61,11 @@ public TypeMirror getOutputType() { } public static Method fromExecutableElement( - ExecutableElement element, Messager messager, Elements elements, Types types) { + ComponentType componentType, + ExecutableElement element, + Messager messager, + Elements elements, + Types types) { if (!element.getTypeParameters().isEmpty()) { messager.printMessage( Diagnostic.Kind.ERROR, @@ -69,60 +74,124 @@ public static Method fromExecutableElement( } if (element.getKind().equals(ElementKind.CONSTRUCTOR)) { messager.printMessage( - Diagnostic.Kind.ERROR, "You cannot annotate a constructor as @Workflow and @Shared"); + Diagnostic.Kind.ERROR, "You cannot annotate a constructor as Restate method"); } if (element.getKind().equals(ElementKind.STATIC_INIT)) { messager.printMessage( - Diagnostic.Kind.ERROR, "You cannot annotate a static init as @Workflow and @Shared"); + Diagnostic.Kind.ERROR, "You cannot annotate a static init as Restate method"); } boolean isAnnotatedWithShared = element.getAnnotation(Shared.class) != null; + boolean isAnnotatedWithExclusive = element.getAnnotation(Exclusive.class) != null; boolean isAnnotatedWithWorkflow = element.getAnnotation(Workflow.class) != null; - if (isAnnotatedWithShared && isAnnotatedWithWorkflow) { + // Check there's no more than one annotation + boolean hasAnyAnnotation = + isAnnotatedWithExclusive || isAnnotatedWithShared || isAnnotatedWithWorkflow; + boolean hasExactlyOneAnnotation = + Boolean.logicalXor( + isAnnotatedWithShared, + Boolean.logicalXor(isAnnotatedWithWorkflow, isAnnotatedWithExclusive)); + if (!(!hasAnyAnnotation || hasExactlyOneAnnotation)) { messager.printMessage( - Diagnostic.Kind.ERROR, "You cannot annotate a method both as @Workflow and @Shared"); - } - if (!isAnnotatedWithWorkflow && !isAnnotatedWithShared) { - messager.printMessage( - Diagnostic.Kind.ERROR, "The method should be annotated either with @Workflow or @Shared"); - } - if (element.getParameters().isEmpty()) { - messager.printMessage( - Diagnostic.Kind.ERROR, "The method signature must have at least one parameter"); + Diagnostic.Kind.ERROR, + "You can have only one annotation between @Shared, @Exclusive and @Workflow to a method", + element); } - boolean firstParameterIsExclusiveContext = - types.isSameType( - element.getParameters().get(0).asType(), - elements.getTypeElement(WorkflowContext.class.getCanonicalName()).asType()); - boolean firstParameterIsSharedContext = - types.isSameType( - element.getParameters().get(0).asType(), - elements.getTypeElement(WorkflowSharedContext.class.getCanonicalName()).asType()); + MethodType methodType = + isAnnotatedWithWorkflow + ? MethodType.WORKFLOW + : isAnnotatedWithShared + ? MethodType.SHARED + : isAnnotatedWithExclusive + ? MethodType.EXCLUSIVE + : defaultMethodType(componentType, element, messager); - if (isAnnotatedWithShared && !firstParameterIsSharedContext) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The method signature must have WorkflowSharedContext as first parameter"); + validateMethodSignature(componentType, methodType, element, messager, elements, types); + + return new Method( + element.getSimpleName(), + methodType, + element.getParameters().size() > 1 ? element.getParameters().get(1).asType() : null, + !element.getReturnType().getKind().equals(TypeKind.VOID) ? element.getReturnType() : null); + } + + private static MethodType defaultMethodType( + ComponentType componentType, ExecutableElement element, Messager messager) { + switch (componentType) { + case SERVICE: + return MethodType.STATELESS; + case VIRTUAL_OBJECT: + return MethodType.EXCLUSIVE; + case WORKFLOW: + messager.printMessage( + Diagnostic.Kind.ERROR, + "Workflow methods MUST be annotated with either @Shared or @Workflow", + element); } - if (isAnnotatedWithWorkflow && !firstParameterIsExclusiveContext) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The method signature must have WorkflowContext as first parameter"); + throw new IllegalStateException( + "Workflow methods MUST be annotated with either @Shared or @Workflow"); + } + + private static void validateMethodSignature( + ComponentType componentType, + MethodType methodType, + ExecutableElement element, + Messager messager, + Elements elements, + Types types) { + switch (methodType) { + case SHARED: + if (componentType == ComponentType.WORKFLOW) { + validateFirstParameterType( + WorkflowSharedContext.class, element, messager, elements, types); + } else { + messager.printMessage( + Diagnostic.Kind.ERROR, + "The annotation @Shared is not supported by the service type " + componentType, + element); + } + break; + case EXCLUSIVE: + if (componentType == ComponentType.VIRTUAL_OBJECT) { + validateFirstParameterType(ObjectContext.class, element, messager, elements, types); + } else { + messager.printMessage( + Diagnostic.Kind.ERROR, + "The annotation @Exclusive is not supported by the service type " + componentType, + element); + } + break; + case STATELESS: + validateFirstParameterType(Context.class, element, messager, elements, types); + break; + case WORKFLOW: + if (componentType == ComponentType.WORKFLOW) { + validateFirstParameterType(WorkflowContext.class, element, messager, elements, types); + } else { + messager.printMessage( + Diagnostic.Kind.ERROR, + "The annotation @Shared is not supported by the service type " + componentType, + element); + } + break; } + } - if (element.getModifiers().contains(Modifier.PRIVATE)) { + private static void validateFirstParameterType( + Class clazz, + ExecutableElement element, + Messager messager, + Elements elements, + Types types) { + if (!types.isSameType( + element.getParameters().get(0).asType(), + elements.getTypeElement(clazz.getCanonicalName()).asType())) { messager.printMessage( Diagnostic.Kind.ERROR, - "The annotated method is private. The method must be at least package-private to be accessible from the code-generated classes", + "The method signature must have " + clazz.getCanonicalName() + " as first parameter", element); } - - return new Method( - element.getSimpleName(), - isAnnotatedWithShared ? MethodType.SHARED : MethodType.WORKFLOW, - element.getParameters().size() > 1 ? element.getParameters().get(1).asType() : null, - !element.getReturnType().getKind().equals(TypeKind.VOID) ? element.getReturnType() : null); } } diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/MethodType.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/MethodType.java index 4d06e285..9586523e 100644 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/MethodType.java +++ b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/MethodType.java @@ -10,5 +10,7 @@ public enum MethodType { SHARED, - WORKFLOW + EXCLUSIVE, + STATELESS, + WORKFLOW; } diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/Service.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/Service.java index 567aac89..76b17c08 100644 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/Service.java +++ b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/model/Service.java @@ -8,10 +8,10 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.gen.model; -import dev.restate.sdk.annotation.ServiceType; -import dev.restate.sdk.annotation.Shared; -import dev.restate.sdk.annotation.Workflow; +import dev.restate.sdk.annotation.*; +import dev.restate.sdk.common.ComponentType; import java.util.List; +import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.processing.Messager; import javax.lang.model.element.ElementKind; @@ -24,39 +24,51 @@ public class Service { - private final CharSequence pkg; - private final CharSequence simpleClassName; - private final ServiceType serviceType; + private final CharSequence targetPkg; + private final CharSequence targetFqcn; + private final String componentName; + private final ComponentType componentType; private final List methods; Service( - CharSequence pkg, - CharSequence simpleClassName, - ServiceType serviceType, + CharSequence targetPkg, + CharSequence targetFqcn, + String componentName, + ComponentType componentType, List methods) { - this.pkg = pkg; - this.simpleClassName = simpleClassName; - this.serviceType = serviceType; + this.targetPkg = targetPkg; + this.targetFqcn = targetFqcn; + this.componentName = componentName; + + this.componentType = componentType; this.methods = methods; } - public CharSequence getPkg() { - return pkg; + public CharSequence getTargetPkg() { + return this.targetPkg; + } + + public CharSequence getTargetFqcn() { + return this.targetFqcn; + } + + public String getFullyQualifiedComponentName() { + return this.componentName; } - public CharSequence getSimpleClassName() { - return simpleClassName; + public String getSimpleComponentName() { + return this.componentName.substring(this.componentName.lastIndexOf('.') + 1); } - public CharSequence getFqcn() { - if (pkg.length() == 0) { - return simpleClassName; + public CharSequence getGeneratedClassFqcnPrefix() { + if (this.targetPkg == null || this.targetPkg.length() == 0) { + return getSimpleComponentName(); } - return pkg + "." + simpleClassName; + return this.targetPkg + "." + getSimpleComponentName(); } - public ServiceType getServiceType() { - return serviceType; + public ComponentType getComponentType() { + return componentType; } public List getMethods() { @@ -65,36 +77,97 @@ public List getMethods() { public static Service fromTypeElement( TypeElement element, Messager messager, Elements elements, Types types) { - if (!element.getTypeParameters().isEmpty()) { + validateType(element, messager); + + dev.restate.sdk.annotation.Service serviceAnnotation = + element.getAnnotation(dev.restate.sdk.annotation.Service.class); + dev.restate.sdk.annotation.VirtualObject virtualObjectAnnotation = + element.getAnnotation(dev.restate.sdk.annotation.VirtualObject.class); + dev.restate.sdk.annotation.Workflow workflowAnnotation = + element.getAnnotation(dev.restate.sdk.annotation.Workflow.class); + boolean isAnnotatedWithService = serviceAnnotation != null; + boolean isAnnotatedWithVirtualObject = virtualObjectAnnotation != null; + boolean isAnnotatedWithWorkflow = workflowAnnotation != null; + + // Should be guaranteed by the caller + assert isAnnotatedWithWorkflow || isAnnotatedWithVirtualObject || isAnnotatedWithService; + + // Check there's no more than one annotation + if (!Boolean.logicalXor( + isAnnotatedWithService, + Boolean.logicalXor(isAnnotatedWithWorkflow, isAnnotatedWithVirtualObject))) { messager.printMessage( Diagnostic.Kind.ERROR, - "The EntityProcessor doesn't support services with generics", + "The type can be annotated only with one annotation between @VirtualObject, @Workflow and @Service", element); } - if (element.getKind().equals(ElementKind.ENUM)) { - messager.printMessage( - Diagnostic.Kind.ERROR, "The EntityProcessor doesn't support enums", element); - } - if (element.getModifiers().contains(Modifier.PRIVATE)) { - messager.printMessage(Diagnostic.Kind.ERROR, "The annotated class is private", element); + ComponentType type = + isAnnotatedWithWorkflow + ? ComponentType.WORKFLOW + : isAnnotatedWithService ? ComponentType.SERVICE : ComponentType.VIRTUAL_OBJECT; + + // Infer names + + CharSequence targetPkg = elements.getPackageOf(element).getQualifiedName(); + CharSequence targetFqcn = element.getQualifiedName(); + + String componentName = + isAnnotatedWithService + ? serviceAnnotation.name() + : isAnnotatedWithVirtualObject + ? virtualObjectAnnotation.name() + : workflowAnnotation.name(); + if (componentName.isEmpty()) { + // Use FQCN + // With this logic we make sure we flatten subclasses names + String simpleComponentName = + targetFqcn.toString().substring(targetPkg.length()).replaceAll(Pattern.quote("."), ""); + componentName = + targetPkg.length() > 0 ? targetPkg + "." + simpleComponentName : simpleComponentName; } - ServiceType type = element.getAnnotation(dev.restate.sdk.annotation.Service.class).value(); + // Compute methods List methods = elements.getAllMembers(element).stream() .filter(e -> e instanceof ExecutableElement) .filter( e -> - e.getAnnotation(Shared.class) != null - || e.getAnnotation(Workflow.class) != null) + e.getAnnotation(Handler.class) != null + || e.getAnnotation(Workflow.class) != null + || e.getAnnotation(Exclusive.class) != null + || e.getAnnotation(Shared.class) != null) .map( e -> Method.fromExecutableElement( - ((ExecutableElement) e), messager, elements, types)) + type, ((ExecutableElement) e), messager, elements, types)) .collect(Collectors.toList()); + validateMethods(type, methods, element, messager); - if (type.equals(ServiceType.WORKFLOW)) { + return new Service(targetPkg, targetFqcn, componentName, type, methods); + } + + private static void validateType(TypeElement element, Messager messager) { + if (!element.getTypeParameters().isEmpty()) { + messager.printMessage( + Diagnostic.Kind.ERROR, + "The EntityProcessor doesn't support services with generics", + element); + } + if (element.getKind().equals(ElementKind.ENUM)) { + messager.printMessage( + Diagnostic.Kind.ERROR, "The EntityProcessor doesn't support enums", element); + } + + if (element.getModifiers().contains(Modifier.PRIVATE)) { + messager.printMessage(Diagnostic.Kind.ERROR, "The annotated class is private", element); + } + } + + private static void validateMethods( + ComponentType componentType, List methods, TypeElement element, Messager messager) { + // Additional validation for Workflow types + if (componentType.equals(ComponentType.WORKFLOW)) { if (methods.stream().filter(m -> m.getMethodType().equals(MethodType.WORKFLOW)).count() != 1) { messager.printMessage( @@ -103,8 +176,5 @@ public static Service fromTypeElement( element); } } - - return new Service( - elements.getPackageOf(element).getQualifiedName(), element.getSimpleName(), type, methods); } } diff --git a/sdk-api-gen/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/sdk-api-gen/src/main/resources/META-INF/services/javax.annotation.processing.Processor index 477bc44f..93da2a05 100644 --- a/sdk-api-gen/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/sdk-api-gen/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1 +1 @@ -dev.restate.sdk.gen.ServiceProcessor \ No newline at end of file +dev.restate.sdk.gen.ComponentProcessor \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/Client.hbs b/sdk-api-gen/src/main/resources/templates/Client.hbs new file mode 100644 index 00000000..cfb224fc --- /dev/null +++ b/sdk-api-gen/src/main/resources/templates/Client.hbs @@ -0,0 +1,109 @@ +{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} + +import dev.restate.sdk.Awaitable; +import dev.restate.sdk.Context; +import dev.restate.sdk.common.StateKey; +import dev.restate.sdk.common.Serde; +import dev.restate.sdk.dynrpc.CodegenUtils; +import io.grpc.Channel; +import java.util.Optional; +import java.time.Duration; + +public class {{generatedClassSimpleName}} { + + public static final String COMPONENT_NAME = "{{componentName}}"; + + {{#methods}} + private static final io.grpc.MethodDescriptor {{descFieldName}} = CodegenUtils.generateMethodDescriptor(dev.restate.sdk.dynrpc.template.generated.{{#if isObject}}KeyedServiceGrpc{{else}}ServiceGrpc{{/if}}.getTemplateMethod(), COMPONENT_NAME, "{{name}}"); + {{^inputEmpty}}private static final Serde<{{{boxedInputFqcn}}}> {{inputSerdeFieldName}} = {{{inputSerdeDecl}}};{{/inputEmpty}} + {{^outputEmpty}}private static final Serde<{{{boxedOutputFqcn}}}> {{outputSerdeFieldName}} = {{{outputSerdeDecl}}};{{/outputEmpty}} + {{/methods}} + + public static ContextClient fromContext(Context ctx{{#isObject}}, String key{{/isObject}}) { + return new ContextClient(ctx{{#isObject}}, key{{/isObject}}); + } + + public static IngressClient fromIngress(Channel restateChannel{{#isObject}}, String key{{/isObject}}) { + return new IngressClient(restateChannel{{#isObject}}, key{{/isObject}}); + } + + public static class ContextClient { + + private final Context ctx; + {{#isObject}}private final String key;{{/isObject}} + + public ContextClient(Context ctx{{#isObject}}, String key{{/isObject}}) { + this.ctx = ctx; + {{#isObject}}this.key = key;{{/isObject}} + } + + {{#methods}} + public Awaitable<{{{boxedOutputFqcn}}}> {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + Awaitable response = CodegenUtils.RestateClient.{{#if isObject}}invokeKeyed{{else}}invoke{{/if}}(this.ctx, {{descFieldName}}{{#isObject}}, this.key{{/isObject}}, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}); + {{#if outputEmpty}} + return response.map(v -> { return null; }); + {{else}} + return response.map(v -> CodegenUtils.valueToT({{outputSerdeFieldName}}, v)); + {{/if}} + }{{/methods}} + + public Send send() { + return new Send(); + } + + public SendDelayed sendDelayed(Duration delay) { + return new SendDelayed(delay); + } + + public class Send { + {{#methods}} + public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + CodegenUtils.RestateClient.{{#if isObject}}invokeKeyed{{else}}invoke{{/if}}OneWay(ContextClient.this.ctx, {{descFieldName}}{{#isObject}}, ContextClient.this.key{{/isObject}}, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}); + }{{/methods}} + } + + public class SendDelayed { + + private final Duration delay; + + SendDelayed(Duration delay) { + this.delay = delay; + } + + {{#methods}} + public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + CodegenUtils.RestateClient.{{#if isObject}}invokeKeyed{{else}}invoke{{/if}}Delayed(ContextClient.this.ctx, {{descFieldName}}{{#isObject}}, ContextClient.this.key{{/isObject}}, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}, delay); + }{{/methods}} + } + } + + public static class IngressClient { + + private final Channel restateChannel; + {{#isObject}}private final String key;{{/isObject}} + + public IngressClient(Channel restateChannel{{#isObject}}, String key{{/isObject}}) { + this.restateChannel = restateChannel; + {{#isObject}}this.key = key;{{/isObject}} + } + + {{#methods}} + public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + com.google.protobuf.Value response = CodegenUtils.ExternalClient.{{#if isObject}}invokeKeyed{{else}}invoke{{/if}}(this.restateChannel, {{descFieldName}}{{#isObject}}, this.key{{/isObject}}, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}); + {{^outputEmpty}} + return CodegenUtils.valueToT({{outputSerdeFieldName}}, response); + {{/outputEmpty}} + }{{/methods}} + + public Send send() { + return new Send(); + } + + public class Send { + {{#methods}} + public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + CodegenUtils.ExternalClient.{{#if isObject}}invokeKeyed{{else}}invoke{{/if}}OneWay(IngressClient.this.restateChannel, {{descFieldName}}{{#isObject}}, IngressClient.this.key{{/isObject}}, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}); + }{{/methods}} + } + } +} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/ComponentAdapter.hbs b/sdk-api-gen/src/main/resources/templates/ComponentAdapter.hbs new file mode 100644 index 00000000..a6e41a96 --- /dev/null +++ b/sdk-api-gen/src/main/resources/templates/ComponentAdapter.hbs @@ -0,0 +1,30 @@ +{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} + +public class {{generatedClassSimpleName}} implements dev.restate.sdk.common.ComponentAdapter<{{originalClassFqcn}}> { + + public static final String COMPONENT_NAME = "{{componentName}}"; + + @java.lang.Override + public dev.restate.sdk.common.ComponentBundle adapt({{originalClassFqcn}} component) { + return dev.restate.sdk.dynrpc.JavaComponent.{{#if isObject}}virtualObject{{else}}service{{/if}}(COMPONENT_NAME) + {{#methods}} + .with( + dev.restate.sdk.dynrpc.JavaComponent.HandlerSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}), + (ctx, req) -> { + {{#if outputEmpty}} + {{#if inputEmpty}}component.{{name}}(ctx){{else}}component.{{name}}(ctx, req){{/if}}; + return null; + {{else}} + return {{#if inputEmpty}}component.{{name}}(ctx){{else}}component.{{name}}(ctx, req){{/if}}; + {{/if}} + }) + {{/methods}} + .build(); + } + + @java.lang.Override + public boolean supportsObject(Object serviceObject) { + return serviceObject instanceof {{originalClassFqcn}}; + } + +} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/ExternalClient.hbs b/sdk-api-gen/src/main/resources/templates/ExternalClient.hbs deleted file mode 100644 index 7469cd50..00000000 --- a/sdk-api-gen/src/main/resources/templates/ExternalClient.hbs +++ /dev/null @@ -1,71 +0,0 @@ -{{#if packageName}}package {{packageName}};{{/if}} - -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.workflow.impl.WorkflowCodegenUtil; -import io.grpc.Channel; -import java.util.Optional; - -public class {{className}}ExternalClient { - - private static final String WORKFLOW_NAME = "{{fqcn}}"; - private static final io.grpc.MethodDescriptor WF_MANAGER_GET_STATE_METHOD_DESC = WorkflowCodegenUtil.generateMethodDescriptorForWorkflowManager(dev.restate.sdk.workflow.template.generated.WorkflowManagerGrpc.getGetStateMethod(), WORKFLOW_NAME); - private static final io.grpc.MethodDescriptor WF_MANAGER_GET_OUTPUT_METHOD_DESC = WorkflowCodegenUtil.generateMethodDescriptorForWorkflowManager(dev.restate.sdk.workflow.template.generated.WorkflowManagerGrpc.getGetOutputMethod(), WORKFLOW_NAME); - private static final io.grpc.MethodDescriptor WF_SUBMIT_METHOD_DESC = WorkflowCodegenUtil.generateMethodDescriptorForWorkflowSubmit(WORKFLOW_NAME); - - {{#methods}} - private static final io.grpc.MethodDescriptor {{descFieldName}} = WorkflowCodegenUtil.generateMethodDescriptorForWorkflow(dev.restate.sdk.workflow.template.generated.WorkflowGrpc.getInvokeTemplateMethod(), WORKFLOW_NAME, "{{name}}"); - {{^inputEmpty}}private static final Serde<{{{inputFqcn}}}> {{inputSerdeFieldName}} = {{{inputSerdeDecl}}};{{/inputEmpty}} - {{^outputEmpty}}private static final Serde<{{{outputFqcn}}}> {{outputSerdeFieldName}} = {{{outputSerdeDecl}}};{{/outputEmpty}} - {{/methods}} - - private final Channel restateChannel; - private final String workflowKey; - - public {{className}}ExternalClient(Channel restateChannel, String workflowKey) { - this.restateChannel = restateChannel; - this.workflowKey = workflowKey; - } - - {{#methods}}{{#if isWorkflow}} - public dev.restate.sdk.workflow.generated.WorkflowExecutionState submit({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return WorkflowCodegenUtil.ExternalClient.submit(restateChannel, WF_SUBMIT_METHOD_DESC, workflowKey, {{#if inputEmpty}}null{{else}}WorkflowCodegenUtil.tToValue({{inputSerdeFieldName}}, req){{/if}}); - } - - public boolean isCompleted() { - return WorkflowCodegenUtil.ExternalClient.isCompleted(restateChannel, WF_MANAGER_GET_OUTPUT_METHOD_DESC, workflowKey); - } - - {{^outputEmpty}} - public Optional<{{{outputFqcn}}}> getOutput() { - return WorkflowCodegenUtil.ExternalClient.getOutput(restateChannel, WF_MANAGER_GET_OUTPUT_METHOD_DESC, workflowKey, {{outputSerdeFieldName}}); - }{{/outputEmpty}} - {{/if}}{{/methods}} - - public Optional getState(StateKey key) { - return WorkflowCodegenUtil.ExternalClient.getState(restateChannel, WF_MANAGER_GET_STATE_METHOD_DESC, workflowKey, key); - } - - {{#methods}}{{#if isShared}} - public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - com.google.protobuf.Value response = WorkflowCodegenUtil.ExternalClient.invokeShared(restateChannel, {{descFieldName}}, workflowKey, {{#if inputEmpty}}null{{else}}WorkflowCodegenUtil.tToValue({{inputSerdeFieldName}}, req){{/if}}); - {{^outputEmpty}} - return WorkflowCodegenUtil.valueToT({{outputSerdeFieldName}}, response); - {{/outputEmpty}} - } - {{/if}}{{/methods}} - - public {{className}}OneWayExternalClient oneWay() { - return new {{className}}OneWayExternalClient(); - } - - public class {{className}}OneWayExternalClient { - - {{#methods}}{{#if isShared}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - WorkflowCodegenUtil.ExternalClient.invokeSharedOneWay(restateChannel, {{descFieldName}}, workflowKey, {{#if inputEmpty}}null{{else}}WorkflowCodegenUtil.tToValue({{inputSerdeFieldName}}, req){{/if}}); - } - {{/if}}{{/methods}} - - } -} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/RestateClient.hbs b/sdk-api-gen/src/main/resources/templates/RestateClient.hbs deleted file mode 100644 index 5a1d3a8b..00000000 --- a/sdk-api-gen/src/main/resources/templates/RestateClient.hbs +++ /dev/null @@ -1,95 +0,0 @@ -{{#if packageName}}package {{packageName}};{{/if}} - -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.Awaitable; -import dev.restate.sdk.Context; -import dev.restate.sdk.workflow.impl.WorkflowCodegenUtil; -import java.util.Optional; -import java.time.Duration; - -public class {{className}}RestateClient { - - private static final String WORKFLOW_NAME = "{{fqcn}}"; - private static final io.grpc.MethodDescriptor WF_MANAGER_GET_STATE_METHOD_DESC = WorkflowCodegenUtil.generateMethodDescriptorForWorkflowManager(dev.restate.sdk.workflow.template.generated.WorkflowManagerGrpc.getGetStateMethod(), WORKFLOW_NAME); - private static final io.grpc.MethodDescriptor WF_MANAGER_GET_OUTPUT_METHOD_DESC = WorkflowCodegenUtil.generateMethodDescriptorForWorkflowManager(dev.restate.sdk.workflow.template.generated.WorkflowManagerGrpc.getGetOutputMethod(), WORKFLOW_NAME); - private static final io.grpc.MethodDescriptor WF_SUBMIT_METHOD_DESC = WorkflowCodegenUtil.generateMethodDescriptorForWorkflowSubmit(WORKFLOW_NAME); - - {{#methods}} - private static final io.grpc.MethodDescriptor {{descFieldName}} = WorkflowCodegenUtil.generateMethodDescriptorForWorkflow(dev.restate.sdk.workflow.template.generated.WorkflowGrpc.getInvokeTemplateMethod(), WORKFLOW_NAME, "{{name}}"); - {{^inputEmpty}}private static final Serde<{{{inputFqcn}}}> {{inputSerdeFieldName}} = {{{inputSerdeDecl}}};{{/inputEmpty}} - {{^outputEmpty}}private static final Serde<{{{outputFqcn}}}> {{outputSerdeFieldName}} = {{{outputSerdeDecl}}};{{/outputEmpty}} - {{/methods}} - - private final Context ctx; - private final String workflowKey; - - public {{className}}RestateClient(Context ctx, String workflowKey) { - this.ctx = ctx; - this.workflowKey = workflowKey; - } - - {{#methods}}{{#if isWorkflow}} - public Awaitable submit({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return WorkflowCodegenUtil.RestateClient.submit(ctx, WF_SUBMIT_METHOD_DESC, workflowKey, {{#if inputEmpty}}null{{else}}WorkflowCodegenUtil.tToValue({{inputSerdeFieldName}}, req){{/if}}); - } - - public Awaitable isCompleted() { - return WorkflowCodegenUtil.RestateClient.isCompleted(ctx, WF_MANAGER_GET_OUTPUT_METHOD_DESC, workflowKey); - } - - {{^outputEmpty}} - public Awaitable> getOutput() { - return WorkflowCodegenUtil.RestateClient.getOutput(ctx, WF_MANAGER_GET_OUTPUT_METHOD_DESC, workflowKey, {{outputSerdeFieldName}}); - }{{/outputEmpty}} - {{/if}}{{/methods}} - - public Awaitable> getState(StateKey key) { - return WorkflowCodegenUtil.RestateClient.getState(ctx, WF_MANAGER_GET_STATE_METHOD_DESC, workflowKey, key); - } - - {{#methods}}{{#if isShared}} - public {{#if outputEmpty}}Awaitable{{else}}Awaitable<{{{outputFqcn}}}>{{/if}} {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - Awaitable response = WorkflowCodegenUtil.RestateClient.invokeShared(ctx, {{descFieldName}}, workflowKey, {{#if inputEmpty}}null{{else}}WorkflowCodegenUtil.tToValue({{inputSerdeFieldName}}, req){{/if}}); - {{#if outputEmpty}} - return response.map(v -> { return null; }); - {{else}} - return response.map(v -> WorkflowCodegenUtil.valueToT({{outputSerdeFieldName}}, v)); - {{/if}} - } - {{/if}}{{/methods}} - - public {{className}}OneWayExternalClient oneWay() { - return new {{className}}OneWayExternalClient(); - } - - public {{className}}DelayedExternalClient delayed(Duration delay) { - return new {{className}}DelayedExternalClient(delay); - } - - public class {{className}}OneWayExternalClient { - - {{#methods}}{{#if isShared}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - WorkflowCodegenUtil.RestateClient.invokeSharedOneWay(ctx, {{descFieldName}}, workflowKey, {{#if inputEmpty}}null{{else}}WorkflowCodegenUtil.tToValue({{inputSerdeFieldName}}, req){{/if}}); - } - {{/if}}{{/methods}} - - } - - public class {{className}}DelayedExternalClient { - - private final Duration delay; - - {{className}}DelayedExternalClient(Duration delay) { - this.delay = delay; - } - - {{#methods}}{{#if isShared}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - WorkflowCodegenUtil.RestateClient.invokeSharedDelayed(ctx, {{descFieldName}}, workflowKey, {{#if inputEmpty}}null{{else}}WorkflowCodegenUtil.tToValue({{inputSerdeFieldName}}, req){{/if}}, delay); - } - {{/if}}{{/methods}} - - } -} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/ServiceAdapter.hbs b/sdk-api-gen/src/main/resources/templates/ServiceAdapter.hbs deleted file mode 100644 index abe1f105..00000000 --- a/sdk-api-gen/src/main/resources/templates/ServiceAdapter.hbs +++ /dev/null @@ -1,37 +0,0 @@ -{{#if packageName}}package {{packageName}};{{/if}} - -public class {{className}}ServiceAdapter implements dev.restate.sdk.common.ServiceAdapter<{{fqcn}}> { - - public static final String SERVICE_NAME = "{{fqcn}}"; - - @java.lang.Override - public dev.restate.sdk.workflow.impl.WorkflowServicesBundle adapt({{fqcn}} service) { - return dev.restate.sdk.workflow.impl.WorkflowServicesBundle.named( - SERVICE_NAME, - {{#methods}}{{#if isWorkflow}} - dev.restate.sdk.workflow.impl.WorkflowServicesBundle.MethodSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}), - (ctx, req) -> { - {{#if outputEmpty}} - {{#if inputEmpty}}service.{{name}}(ctx){{else}}service.{{name}}(ctx, req){{/if}}; - return null; - {{else}} - return {{#if inputEmpty}}service.{{name}}(ctx){{else}}service.{{name}}(ctx, req){{/if}}; - {{/if}} - } - {{/if}}{{/methods}}) - {{#methods}}{{#if isShared}} - .{{builderMethod}}( - dev.restate.sdk.workflow.impl.WorkflowServicesBundle.MethodSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}), - (ctx, req) -> { - {{#if outputEmpty}} - {{#if inputEmpty}}service.{{name}}(ctx){{else}}service.{{name}}(ctx, req){{/if}}; - return null; - {{else}} - return {{#if inputEmpty}}service.{{name}}(ctx){{else}}service.{{name}}(ctx, req){{/if}}; - {{/if}} - }) - {{/if}}{{/methods}} - .build(); - } - -} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/workflow/Client.hbs b/sdk-api-gen/src/main/resources/templates/workflow/Client.hbs new file mode 100644 index 00000000..e48cf1bd --- /dev/null +++ b/sdk-api-gen/src/main/resources/templates/workflow/Client.hbs @@ -0,0 +1,159 @@ +{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} + +import dev.restate.sdk.Awaitable; +import dev.restate.sdk.Context; +import dev.restate.sdk.common.StateKey; +import dev.restate.sdk.common.Serde; +import dev.restate.sdk.workflow.impl.WorkflowCodegenUtil; +import dev.restate.sdk.dynrpc.CodegenUtils; +import io.grpc.Channel; +import java.util.Optional; +import java.time.Duration; + +public class {{generatedClassSimpleName}} { + + public static final String WORKFLOW_NAME = "{{componentName}}"; + private static final io.grpc.MethodDescriptor WF_MANAGER_GET_STATE_METHOD_DESC = WorkflowCodegenUtil.generateMethodDescriptorForWorkflowManager(dev.restate.sdk.workflow.template.generated.WorkflowManagerGrpc.getGetStateMethod(), WORKFLOW_NAME); + private static final io.grpc.MethodDescriptor WF_MANAGER_GET_OUTPUT_METHOD_DESC = WorkflowCodegenUtil.generateMethodDescriptorForWorkflowManager(dev.restate.sdk.workflow.template.generated.WorkflowManagerGrpc.getGetOutputMethod(), WORKFLOW_NAME); + private static final io.grpc.MethodDescriptor WF_SUBMIT_METHOD_DESC = WorkflowCodegenUtil.generateMethodDescriptorForWorkflowSubmit(WORKFLOW_NAME); + + {{#methods}} + private static final io.grpc.MethodDescriptor {{descFieldName}} = WorkflowCodegenUtil.generateMethodDescriptorForWorkflow(dev.restate.sdk.workflow.template.generated.WorkflowGrpc.getInvokeTemplateMethod(), WORKFLOW_NAME, "{{name}}"); + {{^inputEmpty}}private static final Serde<{{{boxedInputFqcn}}}> {{inputSerdeFieldName}} = {{{inputSerdeDecl}}};{{/inputEmpty}} + {{^outputEmpty}}private static final Serde<{{{boxedInputFqcn}}}> {{outputSerdeFieldName}} = {{{outputSerdeDecl}}};{{/outputEmpty}} + {{/methods}} + + public static ContextClient fromContext(Context ctx, String key) { + return new ContextClient(ctx, key); + } + + public static IngressClient fromIngress(Channel restateChannel, String key) { + return new IngressClient(restateChannel, key); + } + + public static class ContextClient { + + private final Context ctx; + private final String workflowKey; + + public ContextClient(Context ctx, String workflowKey) { + this.ctx = ctx; + this.workflowKey = workflowKey; + } + + {{#methods}}{{#if isWorkflow}} + public Awaitable submit({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + return WorkflowCodegenUtil.RestateClient.submit(ctx, WF_SUBMIT_METHOD_DESC, workflowKey, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}); + } + + public Awaitable isCompleted() { + return WorkflowCodegenUtil.RestateClient.isCompleted(ctx, WF_MANAGER_GET_OUTPUT_METHOD_DESC, workflowKey); + } + + {{^outputEmpty}} + public Awaitable> getOutput() { + return WorkflowCodegenUtil.RestateClient.getOutput(ctx, WF_MANAGER_GET_OUTPUT_METHOD_DESC, workflowKey, {{outputSerdeFieldName}}); + }{{/outputEmpty}} + {{/if}}{{/methods}} + + public Awaitable> getState(StateKey key) { + return WorkflowCodegenUtil.RestateClient.getState(ctx, WF_MANAGER_GET_STATE_METHOD_DESC, workflowKey, key); + } + + {{#methods}}{{#if isShared}} + public Awaitable<{{{boxedOutputFqcn}}}> {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + Awaitable response = WorkflowCodegenUtil.RestateClient.invokeShared(ctx, {{descFieldName}}, workflowKey, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}); + {{#if outputEmpty}} + return response.map(v -> { return null; }); + {{else}} + return response.map(v -> CodegenUtils.valueToT({{outputSerdeFieldName}}, v)); + {{/if}} + } + {{/if}}{{/methods}} + + public Send send() { + return new Send(); + } + + public SendDelayed sendDelayed(Duration delay) { + return new SendDelayed(delay); + } + + public class Send { + + {{#methods}}{{#if isShared}} + public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + WorkflowCodegenUtil.RestateClient.invokeSharedOneWay(ContextClient.this.ctx, {{descFieldName}}, ContextClient.this.workflowKey, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}); + }{{/if}}{{/methods}} + + } + + public class SendDelayed { + + private final Duration delay; + + SendDelayed(Duration delay) { + this.delay = delay; + } + + {{#methods}}{{#if isShared}} + public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + WorkflowCodegenUtil.RestateClient.invokeSharedDelayed(ContextClient.this.ctx, {{descFieldName}}, ContextClient.this.workflowKey, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}, delay); + }{{/if}}{{/methods}} + + } + } + + public static class IngressClient { + + private final Channel restateChannel; + private final String workflowKey; + + public IngressClient(Channel restateChannel, String workflowKey) { + this.restateChannel = restateChannel; + this.workflowKey = workflowKey; + } + + {{#methods}}{{#if isWorkflow}} + public dev.restate.sdk.workflow.generated.WorkflowExecutionState submit({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + return WorkflowCodegenUtil.ExternalClient.submit(restateChannel, WF_SUBMIT_METHOD_DESC, workflowKey, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}); + } + + public boolean isCompleted() { + return WorkflowCodegenUtil.ExternalClient.isCompleted(restateChannel, WF_MANAGER_GET_OUTPUT_METHOD_DESC, workflowKey); + } + + {{^outputEmpty}} + public Optional<{{{outputFqcn}}}> getOutput() { + return WorkflowCodegenUtil.ExternalClient.getOutput(restateChannel, WF_MANAGER_GET_OUTPUT_METHOD_DESC, workflowKey, {{outputSerdeFieldName}}); + }{{/outputEmpty}} + {{/if}}{{/methods}} + + public Optional getState(StateKey key) { + return WorkflowCodegenUtil.ExternalClient.getState(restateChannel, WF_MANAGER_GET_STATE_METHOD_DESC, workflowKey, key); + } + + {{#methods}}{{#if isShared}} + public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + com.google.protobuf.Value response = WorkflowCodegenUtil.ExternalClient.invokeShared(IngressClient.this.restateChannel, {{descFieldName}}, IngressClient.this.workflowKey, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}); + {{^outputEmpty}} + return CodegenUtils.valueToT({{outputSerdeFieldName}}, response); + {{/outputEmpty}} + } + {{/if}}{{/methods}} + + public Send send() { + return new Send(); + } + + public class Send { + + {{#methods}}{{#if isShared}} + public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + WorkflowCodegenUtil.ExternalClient.invokeSharedOneWay(IngressClient.this.restateChannel, {{descFieldName}}, IngressClient.this.workflowKey, {{#if inputEmpty}}null{{else}}CodegenUtils.tToValue({{inputSerdeFieldName}}, req){{/if}}); + } + {{/if}}{{/methods}} + + } + } +} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/workflow/ComponentAdapter.hbs b/sdk-api-gen/src/main/resources/templates/workflow/ComponentAdapter.hbs new file mode 100644 index 00000000..baf94ab8 --- /dev/null +++ b/sdk-api-gen/src/main/resources/templates/workflow/ComponentAdapter.hbs @@ -0,0 +1,42 @@ +{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} + +public class {{generatedClassSimpleName}} implements dev.restate.sdk.common.ComponentAdapter<{{originalClassFqcn}}> { + + public static final String SERVICE_NAME = "{{componentName}}"; + + @java.lang.Override + public dev.restate.sdk.workflow.impl.WorkflowComponentBundle adapt({{originalClassFqcn}} component) { + return dev.restate.sdk.workflow.impl.WorkflowComponentBundle.named( + SERVICE_NAME, + {{#methods}}{{#if isWorkflow}} + dev.restate.sdk.dynrpc.JavaComponent.HandlerSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}), + (ctx, req) -> { + {{#if outputEmpty}} + {{#if inputEmpty}}component.{{name}}(ctx){{else}}component.{{name}}(ctx, req){{/if}}; + return null; + {{else}} + return {{#if inputEmpty}}component.{{name}}(ctx){{else}}component.{{name}}(ctx, req){{/if}}; + {{/if}} + } + {{/if}}{{/methods}}) + {{#methods}}{{#if isShared}} + .with{{capitalizeFirst (lower methodType)}}( + dev.restate.sdk.dynrpc.JavaComponent.HandlerSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}), + (ctx, req) -> { + {{#if outputEmpty}} + {{#if inputEmpty}}component.{{name}}(ctx){{else}}component.{{name}}(ctx, req){{/if}}; + return null; + {{else}} + return {{#if inputEmpty}}component.{{name}}(ctx){{else}}component.{{name}}(ctx, req){{/if}}; + {{/if}} + }) + {{/if}}{{/methods}} + .build(); + } + + @java.lang.Override + public boolean supportsObject(Object serviceObject) { + return serviceObject instanceof {{originalClassFqcn}}; + } + +} \ No newline at end of file diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java b/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java new file mode 100644 index 00000000..6e0943bd --- /dev/null +++ b/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java @@ -0,0 +1,80 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk; + +import static dev.restate.sdk.JsonProtoUtils.*; +import static dev.restate.sdk.core.ProtoUtils.*; +import static dev.restate.sdk.core.TestDefinitions.testInvocation; + +import dev.restate.sdk.annotation.Exclusive; +import dev.restate.sdk.annotation.Handler; +import dev.restate.sdk.annotation.Service; +import dev.restate.sdk.annotation.VirtualObject; +import dev.restate.sdk.core.TestDefinitions; +import dev.restate.sdk.core.TestDefinitions.TestSuite; +import java.util.stream.Stream; + +public class CodegenTest implements TestSuite { + + @Service + static class StatelessGreeter { + @Handler + String greet(Context context, String request) { + return request; + } + } + + @VirtualObject + static class ObjectGreeter { + @Exclusive + String greet(ObjectContext context, String request) { + return request; + } + } + + @VirtualObject + public interface GreeterInterface { + @Exclusive + String greet(ObjectContext context, String request); + } + + private static class ObjectGreeterImplementedFromInterface implements GreeterInterface { + + @Override + public String greet(ObjectContext context, String request) { + return request; + } + } + + @Override + public Stream definitions() { + return Stream.of( + testInvocation(StatelessGreeter::new, "greet") + .withInput( + startMessage(1), + inputMessage("Francesco"), + completionMessage(3, greetingResponse("Till"))) + .onlyUnbuffered() + .expectingOutput(outputMessage("Francesco"), END_MESSAGE), + testInvocation(ObjectGreeter::new, "greet") + .withInput( + startMessage(1), + keyedInputMessage("slinkydeveloper", "Francesco"), + completionMessage(3, greetingResponse("Till"))) + .onlyUnbuffered() + .expectingOutput(outputMessage("Francesco"), END_MESSAGE), + testInvocation(ObjectGreeterImplementedFromInterface::new, "greet") + .withInput( + startMessage(1), + keyedInputMessage("slinkydeveloper", "Francesco"), + completionMessage(3, greetingResponse("Till"))) + .onlyUnbuffered() + .expectingOutput(outputMessage("Francesco"), END_MESSAGE)); + } +} diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/JavaCodegenTests.java b/sdk-api-gen/src/test/java/dev/restate/sdk/JavaCodegenTests.java new file mode 100644 index 00000000..039c56d2 --- /dev/null +++ b/sdk-api-gen/src/test/java/dev/restate/sdk/JavaCodegenTests.java @@ -0,0 +1,29 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk; + +import dev.restate.sdk.core.MockMultiThreaded; +import dev.restate.sdk.core.MockSingleThread; +import dev.restate.sdk.core.TestDefinitions.TestExecutor; +import dev.restate.sdk.core.TestDefinitions.TestSuite; +import dev.restate.sdk.core.TestRunner; +import java.util.stream.Stream; + +public class JavaCodegenTests extends TestRunner { + + @Override + protected Stream executors() { + return Stream.of(MockSingleThread.INSTANCE, MockMultiThreaded.INSTANCE); + } + + @Override + public Stream definitions() { + return Stream.of(new CodegenTest()); + } +} diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/JsonProtoUtils.java b/sdk-api-gen/src/test/java/dev/restate/sdk/JsonProtoUtils.java new file mode 100644 index 00000000..98e2b217 --- /dev/null +++ b/sdk-api-gen/src/test/java/dev/restate/sdk/JsonProtoUtils.java @@ -0,0 +1,52 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk; + +import dev.restate.generated.service.protocol.Protocol; +import dev.restate.sdk.common.CoreSerdes; +import dev.restate.sdk.dynrpc.CodegenUtils; +import dev.restate.sdk.dynrpc.generated.KeyedRpcRequest; +import dev.restate.sdk.dynrpc.generated.RpcRequest; +import dev.restate.sdk.dynrpc.generated.RpcResponse; + +public class JsonProtoUtils { + + private JsonProtoUtils() {} + + public static Protocol.PollInputStreamEntryMessage inputMessage(String value) { + return Protocol.PollInputStreamEntryMessage.newBuilder() + .setValue( + RpcRequest.newBuilder() + .setRequest(CodegenUtils.tToValue(CoreSerdes.JSON_STRING, value)) + .build() + .toByteString()) + .build(); + } + + public static Protocol.PollInputStreamEntryMessage keyedInputMessage(String key, String value) { + return Protocol.PollInputStreamEntryMessage.newBuilder() + .setValue( + KeyedRpcRequest.newBuilder() + .setKey(key) + .setRequest(CodegenUtils.tToValue(CoreSerdes.JSON_STRING, value)) + .build() + .toByteString()) + .build(); + } + + public static Protocol.OutputStreamEntryMessage outputMessage(String value) { + return Protocol.OutputStreamEntryMessage.newBuilder() + .setValue( + RpcResponse.newBuilder() + .setResponse(CodegenUtils.tToValue(CoreSerdes.JSON_STRING, value)) + .build() + .toByteString()) + .build(); + } +} 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 15670b7b..0ffcbad3 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 @@ -24,7 +24,7 @@ import kotlin.time.Duration import kotlin.time.toJavaDuration import kotlinx.coroutines.* -internal class ContextImpl internal constructor(private val syscalls: Syscalls) : KeyedContext { +internal class ContextImpl internal constructor(private val syscalls: Syscalls) : ObjectContext { override suspend fun get(key: StateKey): T? { val deferred: Deferred = suspendCancellableCoroutine { cont: CancellableContinuation> -> @@ -108,7 +108,7 @@ internal class ContextImpl internal constructor(private val syscalls: Syscalls) override suspend fun oneWayCall(methodDescriptor: MethodDescriptor, parameter: T) { return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.backgroundCall(methodDescriptor, parameter, null, completingUnitContinuation(cont)) + syscalls.send(methodDescriptor, parameter, null, completingUnitContinuation(cont)) } } @@ -118,7 +118,7 @@ internal class ContextImpl internal constructor(private val syscalls: Syscalls) delay: Duration ) { return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.backgroundCall( + syscalls.send( methodDescriptor, parameter, delay.toJavaDuration(), completingUnitContinuation(cont)) } } 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 8ffae25b..d0c2f777 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 @@ -9,7 +9,7 @@ package dev.restate.sdk.kotlin import dev.restate.sdk.common.CoreSerdes -import dev.restate.sdk.common.NonBlockingService +import dev.restate.sdk.common.NonBlockingComponent import dev.restate.sdk.common.Serde import dev.restate.sdk.common.StateKey import dev.restate.sdk.common.syscalls.Syscalls @@ -23,8 +23,8 @@ import kotlin.time.Duration * interact with other Restate services, record side effects, execute timers and synchronize with * external systems. * - * To use it within your Restate service, implement [RestateKtService] and get an instance with - * [RestateKtService.restateContext]. + * To use it within your Restate service, implement [RestateKtComponent] and get an instance with + * [RestateKtComponent.restateContext]. * * All methods of this interface, and related interfaces, throws either [TerminalException] or * cancels the coroutine. [TerminalException] can be caught and acted upon. @@ -207,9 +207,10 @@ sealed interface Context { } /** - * This interface extends [Context] adding access to the service instance key-value state storage. + * This interface extends [Context] adding access to the virtual object instance key-value state + * storage. */ -sealed interface KeyedContext : Context { +sealed interface ObjectContext : Context { /** * Gets the state stored under key, deserializing the raw value using the [StateKey.serde]. @@ -221,7 +222,7 @@ sealed interface KeyedContext : Context { suspend fun get(key: StateKey): T? /** - * Gets all the known state keys for this service instance. + * Gets all the known state keys for this virtual object instance. * * @return the immutable collection of known state keys. */ @@ -242,22 +243,22 @@ sealed interface KeyedContext : Context { */ suspend fun clear(key: StateKey<*>) - /** Clears all the state of this service instance key-value state storage */ + /** Clears all the state of this virtual object instance key-value state storage */ suspend fun clearAll() companion object { /** - * Create a [KeyedContext]. This will look up the thread-local/async-context storage for the + * Create a [ObjectContext]. This will look up the thread-local/async-context storage for the * underlying context implementation, so make sure to call it always from the same context where * the service is executed. */ - fun current(): KeyedContext { + fun current(): ObjectContext { return fromSyscalls(Syscalls.current()) } /** Build a context from the underlying [Syscalls] object. */ - fun fromSyscalls(syscalls: Syscalls): KeyedContext { + fun fromSyscalls(syscalls: Syscalls): ObjectContext { return ContextImpl(syscalls) } } @@ -415,4 +416,4 @@ sealed interface AwakeableHandle { * * When throwing any other type of exception, the failure is considered "non-terminal" and the * runtime will retry it, according to its configuration */ -interface RestateKtService : NonBlockingService +interface RestateKtComponent : NonBlockingComponent diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwaitableTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwaitableTest.kt index 760e4f94..16f551aa 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwaitableTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwaitableTest.kt @@ -19,9 +19,9 @@ import kotlinx.coroutines.Dispatchers class AwaitableTest : DeferredTestSuite() { private class ReverseAwaitOrder : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val a1 = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Francesco" }) val a2 = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Till" }) val a2Res = a2.await().getMessage() @@ -36,9 +36,9 @@ class AwaitableTest : DeferredTestSuite() { } private class AwaitTwiceTheSameAwaitable : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val a = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Francesco" }) return greetingResponse { message = a.await().getMessage() + "-" + a.await().getMessage() } } @@ -49,9 +49,9 @@ class AwaitableTest : DeferredTestSuite() { } private class AwaitAll : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val a1 = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Francesco" }) val a2 = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Till" }) @@ -69,9 +69,9 @@ class AwaitableTest : DeferredTestSuite() { } private class AwaitAny : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val a1 = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Francesco" }) val a2 = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Till" }) return Awaitable.any(a1, a2).await() as GreetingResponse @@ -79,9 +79,9 @@ class AwaitableTest : DeferredTestSuite() { } private class AwaitSelect : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val a1 = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Francesco" }) val a2 = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Till" }) return select { @@ -96,9 +96,9 @@ class AwaitableTest : DeferredTestSuite() { } private class CombineAnyWithAll : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val a1 = ctx.awakeable(CoreSerdes.JSON_STRING) val a2 = ctx.awakeable(CoreSerdes.JSON_STRING) val a3 = ctx.awakeable(CoreSerdes.JSON_STRING) @@ -119,9 +119,9 @@ class AwaitableTest : DeferredTestSuite() { } private class AwaitAnyIndex : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val a1 = ctx.awakeable(CoreSerdes.JSON_STRING) val a2 = ctx.awakeable(CoreSerdes.JSON_STRING) val a3 = ctx.awakeable(CoreSerdes.JSON_STRING) @@ -138,9 +138,9 @@ class AwaitableTest : DeferredTestSuite() { } private class AwaitOnAlreadyResolvedAwaitables : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val a1 = ctx.awakeable(CoreSerdes.JSON_STRING) val a2 = ctx.awakeable(CoreSerdes.JSON_STRING) val a12 = Awaitable.all(a1, a2) diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwakeableIdTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwakeableIdTest.kt index 16659768..4f37b733 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwakeableIdTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwakeableIdTest.kt @@ -19,10 +19,10 @@ import kotlinx.coroutines.Dispatchers class AwakeableIdTest : AwakeableIdTestSuite() { private class ReturnAwakeableId : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val id: String = KeyedContext.current().awakeable(CoreSerdes.JSON_STRING).id + val id: String = ObjectContext.current().awakeable(CoreSerdes.JSON_STRING).id return greetingResponse { message = id } } } diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/EagerStateTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/EagerStateTest.kt index 7952f2dc..50d4c5f5 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/EagerStateTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/EagerStateTest.kt @@ -18,9 +18,9 @@ import org.assertj.core.api.AssertionsForClassTypes.assertThat class EagerStateTest : EagerStateTestSuite() { private class GetEmpty : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val stateIsEmpty = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)) == null return greetingResponse { message = stateIsEmpty.toString() } } @@ -31,10 +31,10 @@ class EagerStateTest : EagerStateTestSuite() { } private class Get : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { return greetingResponse { - message = KeyedContext.current().get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! + message = ObjectContext.current().get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! } } } @@ -44,9 +44,9 @@ class EagerStateTest : EagerStateTestSuite() { } private class GetAppendAndGet : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! ctx.set(StateKey.of("STATE", CoreSerdes.JSON_STRING), oldState + request.getName()) val newState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! @@ -59,9 +59,9 @@ class EagerStateTest : EagerStateTestSuite() { } private class GetClearAndGet : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! ctx.clear(StateKey.of("STATE", CoreSerdes.JSON_STRING)) assertThat(ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))).isNull() @@ -74,8 +74,8 @@ class EagerStateTest : EagerStateTestSuite() { } private class GetClearAllAndGet : GreeterRestateKt.GreeterRestateKtImplBase() { - override suspend fun greet(context: KeyedContext, request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + override suspend fun greet(context: ObjectContext, request: GreetingRequest): GreetingResponse { + val ctx = ObjectContext.current() val oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! ctx.clearAll() @@ -92,7 +92,7 @@ class EagerStateTest : EagerStateTestSuite() { } private class ListKeys : GreeterRestateKt.GreeterRestateKtImplBase() { - override suspend fun greet(context: KeyedContext, request: GreetingRequest): GreetingResponse { + override suspend fun greet(context: ObjectContext, request: GreetingRequest): GreetingResponse { return greetingResponse { message = context.stateKeys().joinToString(separator = ",") } } } diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/InvocationIdTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/InvocationIdTest.kt index faf7046f..06cc6982 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/InvocationIdTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/InvocationIdTest.kt @@ -19,7 +19,7 @@ import kotlinx.coroutines.Dispatchers class InvocationIdTest : InvocationIdTestSuite() { private class ReturnInvocationId : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { return greetingResponse { message = InvocationId.current().toString() } } diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/OnlyInputAndOutputTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/OnlyInputAndOutputTest.kt index 5b29887e..dfc76540 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/OnlyInputAndOutputTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/OnlyInputAndOutputTest.kt @@ -18,7 +18,7 @@ import kotlinx.coroutines.Dispatchers class OnlyInputAndOutputTest : OnlyInputAndOutputTestSuite() { private class NoSyscallsGreeter : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { return greetingResponse { message = "Hello " + request.getName() } } diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RandomTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RandomTest.kt index 44220a92..c41c2d07 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RandomTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RandomTest.kt @@ -19,10 +19,10 @@ import kotlinx.coroutines.Dispatchers class RandomTest : RandomTestSuite() { private class RandomShouldBeDeterministic : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val number = KeyedContext.current().random().nextInt() + val number = ObjectContext.current().random().nextInt() return greetingResponse { message = number.toString() } } } @@ -32,9 +32,9 @@ class RandomTest : RandomTestSuite() { } private class RandomInsideSideEffect : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() ctx.sideEffect { ctx.random().nextInt() } throw IllegalStateException("This should not unreachable") } diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RestateCodegenTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RestateCodegenTest.kt index 96a5a2af..3d5b9971 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RestateCodegenTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RestateCodegenTest.kt @@ -17,7 +17,7 @@ import kotlin.time.Duration.Companion.seconds class RestateCodegenTest : RestateCodegenTestSuite() { private class GreeterWithRestateClientAndServerCodegen : GreeterRestateKtImplBase() { - override suspend fun greet(context: KeyedContext, request: GreetingRequest): GreetingResponse { + override suspend fun greet(context: ObjectContext, request: GreetingRequest): GreetingResponse { val client = GreeterRestateKt.newClient(context) client.delayed(1.seconds).greet(request) client.oneWay().greet(request) diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SideEffectTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SideEffectTest.kt index ecc1cacf..311b93c0 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SideEffectTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SideEffectTest.kt @@ -18,9 +18,9 @@ import kotlinx.coroutines.Dispatchers class SideEffectTest : SideEffectTestSuite() { private class SideEffect(private val sideEffectOutput: String) : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val result = ctx.sideEffect(CoreSerdes.JSON_STRING) { sideEffectOutput } return greetingResponse { message = "Hello $result" } } @@ -31,9 +31,9 @@ class SideEffectTest : SideEffectTestSuite() { } private class ConsecutiveSideEffect(private val sideEffectOutput: String) : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val firstResult = ctx.sideEffect(CoreSerdes.JSON_STRING) { sideEffectOutput } val secondResult = ctx.sideEffect(CoreSerdes.JSON_STRING) { firstResult.uppercase(Locale.getDefault()) } @@ -48,11 +48,11 @@ class SideEffectTest : SideEffectTestSuite() { private class CheckContextSwitching : GreeterGrpcKt.GreeterCoroutineImplBase( Dispatchers.Unconfined + CoroutineName("CheckContextSwitchingTestCoroutine")), - RestateKtService { + RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { val sideEffectThread = - KeyedContext.current().sideEffect(CoreSerdes.JSON_STRING) { Thread.currentThread().name } + ObjectContext.current().sideEffect(CoreSerdes.JSON_STRING) { Thread.currentThread().name } check(sideEffectThread.contains("CheckContextSwitchingTestCoroutine")) { "Side effect thread is not running within the same coroutine context of the handler method: $sideEffectThread" } @@ -65,9 +65,9 @@ class SideEffectTest : SideEffectTestSuite() { } private class SideEffectGuard : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() ctx.sideEffect { ctx.oneWayCall(GreeterGrpcKt.greetMethod, greetingRequest { name = "something" }) } diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SleepTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SleepTest.kt index bb029ec6..2f1a3ca7 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SleepTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SleepTest.kt @@ -19,9 +19,9 @@ import kotlinx.coroutines.Dispatchers class SleepTest : SleepTestSuite() { private class SleepGreeter : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() ctx.sleep(1000.milliseconds) return greetingResponse { message = "Hello" } } @@ -32,9 +32,9 @@ class SleepTest : SleepTestSuite() { } private class ManySleeps : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val awaitables = mutableListOf>() for (i in 0..9) { awaitables.add(ctx.timer(1000.milliseconds)) diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateMachineFailuresTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateMachineFailuresTest.kt index 5286c3ad..7971fa10 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateMachineFailuresTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateMachineFailuresTest.kt @@ -24,10 +24,10 @@ import kotlinx.coroutines.Dispatchers class StateMachineFailuresTest : StateMachineFailuresTestSuite() { private class GetState(private val nonTerminalExceptionsSeen: AtomicInteger) : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { try { - KeyedContext.current().get(STATE) + ObjectContext.current().get(STATE) } catch (e: Throwable) { // A user should never catch Throwable!!! if (e !is CancellationException && e !is TerminalException) { @@ -55,9 +55,9 @@ class StateMachineFailuresTest : StateMachineFailuresTestSuite() { } private class SideEffectFailure(private val serde: Serde) : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - KeyedContext.current().sideEffect(serde) { 0 } + ObjectContext.current().sideEffect(serde) { 0 } return greetingResponse { message = "Francesco" } } } diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateTest.kt index d2721e1e..d528ffbf 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateTest.kt @@ -26,10 +26,10 @@ import kotlinx.serialization.json.Json class StateTest : StateTestSuite() { private class GetState : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { val state: String = - KeyedContext.current().get(StateKey.of("STATE", CoreSerdes.JSON_STRING)) ?: "Unknown" + ObjectContext.current().get(StateKey.of("STATE", CoreSerdes.JSON_STRING)) ?: "Unknown" return greetingResponse { message = "Hello $state" } } } @@ -39,9 +39,9 @@ class StateTest : StateTestSuite() { } private class GetAndSetState : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - val ctx = KeyedContext.current() + val ctx = ObjectContext.current() val state = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! ctx.set(StateKey.of("STATE", CoreSerdes.JSON_STRING), request.getName()) @@ -68,7 +68,7 @@ class StateTest : StateTestSuite() { val DATA: StateKey = StateKey.of("STATE", KtSerdes.json()) } - override suspend fun greet(context: KeyedContext, request: GreetingRequest): GreetingResponse { + override suspend fun greet(context: ObjectContext, request: GreetingRequest): GreetingResponse { val state = context.get(DATA)!! state.a += 1 context.set(DATA, state) diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/UserFailuresTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/UserFailuresTest.kt index 129f6243..edfb6978 100644 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/UserFailuresTest.kt +++ b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/UserFailuresTest.kt @@ -20,7 +20,7 @@ import kotlinx.coroutines.Dispatchers class UserFailuresTest : UserFailuresTestSuite() { private class ThrowIllegalStateException : - GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { throw IllegalStateException("Whatever") } @@ -32,10 +32,10 @@ class UserFailuresTest : UserFailuresTestSuite() { private class SideEffectThrowIllegalStateException( private val nonTerminalExceptionsSeen: AtomicInteger - ) : GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + ) : GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { try { - KeyedContext.current().sideEffect { throw IllegalStateException("Whatever") } + ObjectContext.current().sideEffect { throw IllegalStateException("Whatever") } } catch (e: Throwable) { if (e !is CancellationException && e !is TerminalException) { nonTerminalExceptionsSeen.addAndGet(1) @@ -56,7 +56,7 @@ class UserFailuresTest : UserFailuresTestSuite() { private class ThrowTerminalException( private val code: TerminalException.Code, private val message: String - ) : GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + ) : GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { throw TerminalException(code, message) } @@ -72,9 +72,9 @@ class UserFailuresTest : UserFailuresTestSuite() { private class SideEffectThrowTerminalException( private val code: TerminalException.Code, private val message: String - ) : GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtService { + ) : GreeterGrpcKt.GreeterCoroutineImplBase(Dispatchers.Unconfined), RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { - KeyedContext.current().sideEffect { throw TerminalException(code, message) } + ObjectContext.current().sideEffect { throw TerminalException(code, message) } throw IllegalStateException("Not expected to reach this point") } } diff --git a/sdk-api/build.gradle.kts b/sdk-api/build.gradle.kts index 34588c22..12164ccb 100644 --- a/sdk-api/build.gradle.kts +++ b/sdk-api/build.gradle.kts @@ -1,4 +1,5 @@ import com.google.protobuf.gradle.id +import com.google.protobuf.gradle.protobuf plugins { `java-library` @@ -10,15 +11,20 @@ description = "Restate SDK APIs" dependencies { api(project(":sdk-common")) - testCompileOnly(coreLibs.javax.annotation.api) + // For the gRPC Ingress client + protobuf(project(":sdk-common")) + + implementation(coreLibs.protobuf.util) + implementation(coreLibs.grpc.stub) + implementation(coreLibs.grpc.protobuf) + compileOnly(coreLibs.javax.annotation.api) testImplementation(project(":sdk-core")) testImplementation(testingLibs.junit.jupiter) testImplementation(testingLibs.assertj) testImplementation(coreLibs.protobuf.java) - testImplementation(coreLibs.grpc.stub) - testImplementation(coreLibs.grpc.protobuf) testImplementation(coreLibs.log4j.core) + testCompileOnly(coreLibs.javax.annotation.api) // Import test suites from sdk-core testImplementation(project(":sdk-core", "testArchive")) @@ -40,6 +46,10 @@ protobuf { } generateProtoTasks { + ofSourceSet("main").forEach { + it.builtins { java } + it.plugins { id("grpc") } + } ofSourceSet("test").forEach { // Make sure we depend on shadowJar from protoc-gen-restate it.dependsOn(":protoc-gen-restate:shadowJar") diff --git a/sdk-api/src/main/java/dev/restate/sdk/Awakeable.java b/sdk-api/src/main/java/dev/restate/sdk/Awakeable.java index ef8f380a..aae739e7 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/Awakeable.java +++ b/sdk-api/src/main/java/dev/restate/sdk/Awakeable.java @@ -24,7 +24,7 @@ * *

For example, you can send a Kafka record including the {@link Awakeable#id()}, and then let * another service consume from Kafka the responses of given external system interaction by using - * {@link KeyedContext#awakeableHandle(String)}. + * {@link ObjectContext#awakeableHandle(String)}. */ @NotThreadSafe public final class Awakeable extends Awaitable.MappedAwaitable { diff --git a/sdk-api/src/main/java/dev/restate/sdk/RestateService.java b/sdk-api/src/main/java/dev/restate/sdk/Component.java similarity index 84% rename from sdk-api/src/main/java/dev/restate/sdk/RestateService.java rename to sdk-api/src/main/java/dev/restate/sdk/Component.java index 546b8358..0b8683c7 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/RestateService.java +++ b/sdk-api/src/main/java/dev/restate/sdk/Component.java @@ -8,11 +8,11 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk; -import dev.restate.sdk.common.BlockingService; +import dev.restate.sdk.common.BlockingComponent; import dev.restate.sdk.common.TerminalException; /** - * Marker interface for Restate services. + * Marker interface for Restate components. * *

* @@ -27,4 +27,4 @@ * runtime will retry it, according to its configuration * */ -public interface RestateService extends BlockingService {} +public interface Component extends BlockingComponent {} 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 b17b43a6..9d3cce08 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/Context.java +++ b/sdk-api/src/main/java/dev/restate/sdk/Context.java @@ -42,6 +42,22 @@ public interface Context { */ Awaitable call(MethodDescriptor methodDescriptor, T parameter); + /** + * Invoke another Restate service method. + * + * @param target the address of the callee + * @param inputSerde Input serde + * @param outputSerde Output serde + * @param parameter the invocation request parameter. + * @return an {@link Awaitable} that wraps the Restate service method result. + */ + Awaitable call(Target target, Serde inputSerde, Serde outputSerde, T parameter); + + /** Like {@link #call(Target, Serde, Serde, Object)} with raw input/output. */ + default Awaitable call(Target target, byte[] parameter) { + return call(target, CoreSerdes.RAW, CoreSerdes.RAW, parameter); + } + /** * Create a {@link Channel} to use with generated blocking stubs to invoke other Restate services. * @@ -57,6 +73,20 @@ default Channel grpcChannel() { return new GrpcChannelAdapter(this); } + /** + * Invoke another Restate service without waiting for the response. + * + * @param target the address of the callee + * @param inputSerde Input serde + * @param parameter the invocation request parameter. + */ + void oneWayCall(Target target, Serde inputSerde, T parameter); + + /** Like {@link #oneWayCall(Target, Serde, Object)} with raw input. */ + default void oneWayCall(Target target, byte[] parameter) { + oneWayCall(target, CoreSerdes.RAW, parameter); + } + /** * Invoke another Restate service without waiting for the response. * @@ -66,6 +96,24 @@ default Channel grpcChannel() { */ void oneWayCall(MethodDescriptor methodDescriptor, T parameter); + /** + * Invoke another Restate service without waiting for the response after the provided {@code + * delay} has elapsed. + * + *

This method returns immediately, as the timer is executed and awaited on Restate. + * + * @param target the address of the callee + * @param inputSerde Input serde + * @param parameter the invocation request parameter. + * @param delay time to wait before executing the call. + */ + void delayedCall(Target target, Serde inputSerde, T parameter, Duration delay); + + /** Like {@link #delayedCall(Target, Serde, Object, Duration)} with raw input. */ + default void delayedCall(Target target, byte[] parameter, Duration delay) { + delayedCall(target, CoreSerdes.RAW, parameter, delay); + } + /** * Invoke another Restate service without waiting for the response after the provided {@code * delay} has elapsed. @@ -181,9 +229,9 @@ default void sideEffect(ThrowingRunnable runnable) throws TerminalException { RestateRandom random(); /** - * Create a {@link KeyedContext}. This will look up the thread-local/async-context storage for the - * underlying context implementation, so make sure to call it always from the same context where - * the service is executed. + * Create a {@link ObjectContext}. This will look up the thread-local/async-context storage for + * the underlying context implementation, so make sure to call it always from the same context + * where the service is executed. */ static Context current() { return fromSyscalls(Syscalls.current()); 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 957bc1c0..0064b2c5 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/ContextImpl.java +++ b/sdk-api/src/main/java/dev/restate/sdk/ContextImpl.java @@ -24,7 +24,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -class ContextImpl implements KeyedContext { +class ContextImpl implements ObjectContext { private final Syscalls syscalls; @@ -85,16 +85,36 @@ public Awaitable call(MethodDescriptor methodDescriptor, T param return Awaitable.single(syscalls, result); } + @Override + public Awaitable call( + Target target, Serde inputSerde, Serde outputSerde, T parameter) { + ByteString input = Util.serializeWrappingException(syscalls, inputSerde, parameter); + Deferred result = Util.blockOnSyscall(cb -> syscalls.call(target, input, cb)); + return Awaitable.single(syscalls, result) + .map(bs -> Util.deserializeWrappingException(syscalls, outputSerde, bs)); + } + + @Override + public void oneWayCall(Target target, Serde inputSerde, T parameter) { + ByteString input = Util.serializeWrappingException(syscalls, inputSerde, parameter); + Util.blockOnSyscall(cb -> syscalls.send(target, input, null, cb)); + } + @Override public void oneWayCall(MethodDescriptor methodDescriptor, T parameter) { - Util.blockOnSyscall(cb -> syscalls.backgroundCall(methodDescriptor, parameter, null, cb)); + Util.blockOnSyscall(cb -> syscalls.send(methodDescriptor, parameter, null, cb)); + } + + @Override + public void delayedCall(Target target, Serde inputSerde, T parameter, Duration delay) { + ByteString input = Util.serializeWrappingException(syscalls, inputSerde, parameter); + Util.blockOnSyscall(cb -> syscalls.send(target, input, delay, cb)); } @Override public void delayedCall( MethodDescriptor methodDescriptor, T parameter, Duration delay) { - Util.blockOnSyscall( - cb -> syscalls.backgroundCall(methodDescriptor, parameter, delay, cb)); + Util.blockOnSyscall(cb -> syscalls.send(methodDescriptor, parameter, delay, cb)); } @Override diff --git a/sdk-api/src/main/java/dev/restate/sdk/KeyedContext.java b/sdk-api/src/main/java/dev/restate/sdk/ObjectContext.java similarity index 74% rename from sdk-api/src/main/java/dev/restate/sdk/KeyedContext.java rename to sdk-api/src/main/java/dev/restate/sdk/ObjectContext.java index 48df23d8..01cd7922 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/KeyedContext.java +++ b/sdk-api/src/main/java/dev/restate/sdk/ObjectContext.java @@ -16,13 +16,13 @@ import javax.annotation.concurrent.NotThreadSafe; /** - * This interface extends {@link Context} adding access to the service instance key-value state - * storage + * This interface extends {@link Context} adding access to the virtual object instance key-value + * state storage * * @see Context */ @NotThreadSafe -public interface KeyedContext extends Context { +public interface ObjectContext extends Context { /** * Gets the state stored under key, deserializing the raw value using the {@link Serde} in the @@ -36,7 +36,7 @@ public interface KeyedContext extends Context { Optional get(StateKey key); /** - * Gets all the known state keys for this service instance. + * Gets all the known state keys for this virtual object instance. * * @return the immutable collection of known state keys. */ @@ -49,7 +49,7 @@ public interface KeyedContext extends Context { */ void clear(StateKey key); - /** Clears all the state of this service instance key-value state storage */ + /** Clears all the state of this virtual object instance key-value state storage */ void clearAll(); /** @@ -62,16 +62,16 @@ public interface KeyedContext extends Context { void set(StateKey key, @Nonnull T value); /** - * Create a {@link KeyedContext}. This will look up the thread-local/async-context storage for the - * underlying context implementation, so make sure to call it always from the same context where - * the service is executed. + * Create a {@link ObjectContext}. This will look up the thread-local/async-context storage for + * the underlying context implementation, so make sure to call it always from the same context + * where the service is executed. */ - static KeyedContext current() { + static ObjectContext current() { return fromSyscalls(Syscalls.current()); } /** Build a RestateContext from the underlying {@link Syscalls} object. */ - static KeyedContext fromSyscalls(Syscalls syscalls) { + static ObjectContext fromSyscalls(Syscalls syscalls) { return new ContextImpl(syscalls); } } diff --git a/sdk-api/src/main/java/dev/restate/sdk/RestateRandom.java b/sdk-api/src/main/java/dev/restate/sdk/RestateRandom.java index ebc92d96..4ed24ab1 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/RestateRandom.java +++ b/sdk-api/src/main/java/dev/restate/sdk/RestateRandom.java @@ -21,9 +21,9 @@ * *

This instance is useful to generate identifiers, idempotency keys, and for uniform sampling * from a set of options. If a cryptographically secure value is needed, please generate that - * externally using {@link KeyedContext#sideEffect(Serde, ThrowingSupplier)}. + * externally using {@link ObjectContext#sideEffect(Serde, ThrowingSupplier)}. * - *

You MUST NOT use this object inside a {@link KeyedContext#sideEffect(Serde, + *

You MUST NOT use this object inside a {@link ObjectContext#sideEffect(Serde, * ThrowingSupplier)}. */ public class RestateRandom extends Random { diff --git a/sdk-api/src/main/java/dev/restate/sdk/dynrpc/CodegenUtils.java b/sdk-api/src/main/java/dev/restate/sdk/dynrpc/CodegenUtils.java new file mode 100644 index 00000000..4ab2d022 --- /dev/null +++ b/sdk-api/src/main/java/dev/restate/sdk/dynrpc/CodegenUtils.java @@ -0,0 +1,218 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk.dynrpc; + +import static io.grpc.stub.ClientCalls.blockingUnaryCall; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Value; +import com.google.protobuf.util.JsonFormat; +import dev.restate.generated.IngressGrpc; +import dev.restate.sdk.Awaitable; +import dev.restate.sdk.Context; +import dev.restate.sdk.common.Serde; +import dev.restate.sdk.dynrpc.generated.KeyedRpcRequest; +import dev.restate.sdk.dynrpc.generated.RpcRequest; +import dev.restate.sdk.dynrpc.generated.RpcResponse; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.MethodDescriptor; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import javax.annotation.Nullable; + +// Methods invoked from code-generated classes +// DON'T invoke these methods directly unless you know what you're doing! +public class CodegenUtils { + + private CodegenUtils() {} + + public static T valueToT(Serde serde, Value value) { + String reqAsString; + try { + reqAsString = JsonFormat.printer().print(value); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + return serde.deserialize(reqAsString.getBytes(StandardCharsets.UTF_8)); + } + + public static Value tToValue(Serde serde, T value) { + String resAsString = serde.serializeToByteString(value).toStringUtf8(); + Value.Builder outValueBuilder = Value.newBuilder(); + try { + JsonFormat.parser().merge(resAsString, outValueBuilder); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + return outValueBuilder.build(); + } + + // -- Restate client methods + + public static class RestateClient { + private RestateClient() {} + + public static Awaitable invoke( + Context ctx, + MethodDescriptor methodDesc, + @Nullable Value payload) { + RpcRequest.Builder reqBuilder = RpcRequest.newBuilder(); + if (payload != null) { + reqBuilder.setRequest(payload); + } + + return ctx.call(methodDesc, reqBuilder.build()).map(RpcResponse::getResponse); + } + + public static void invokeOneWay( + Context ctx, + MethodDescriptor methodDesc, + @Nullable Value payload) { + RpcRequest.Builder reqBuilder = RpcRequest.newBuilder(); + if (payload != null) { + reqBuilder.setRequest(payload); + } + + ctx.oneWayCall(methodDesc, reqBuilder.build()); + } + + public static void invokeDelayed( + Context ctx, + MethodDescriptor methodDesc, + @Nullable Value payload, + Duration delay) { + RpcRequest.Builder reqBuilder = RpcRequest.newBuilder(); + if (payload != null) { + reqBuilder.setRequest(payload); + } + + ctx.delayedCall(methodDesc, reqBuilder.build(), delay); + } + + public static Awaitable invokeKeyed( + Context ctx, + MethodDescriptor methodDesc, + String key, + @Nullable Value payload) { + KeyedRpcRequest.Builder reqBuilder = KeyedRpcRequest.newBuilder().setKey(key); + if (payload != null) { + reqBuilder.setRequest(payload); + } + + return ctx.call(methodDesc, reqBuilder.build()).map(RpcResponse::getResponse); + } + + public static void invokeKeyedOneWay( + Context ctx, + MethodDescriptor methodDesc, + String key, + @Nullable Value payload) { + KeyedRpcRequest.Builder reqBuilder = KeyedRpcRequest.newBuilder().setKey(key); + if (payload != null) { + reqBuilder.setRequest(payload); + } + + ctx.oneWayCall(methodDesc, reqBuilder.build()); + } + + public static void invokeKeyedDelayed( + Context ctx, + MethodDescriptor methodDesc, + String key, + @Nullable Value payload, + Duration delay) { + KeyedRpcRequest.Builder reqBuilder = KeyedRpcRequest.newBuilder().setKey(key); + if (payload != null) { + reqBuilder.setRequest(payload); + } + + ctx.delayedCall(methodDesc, reqBuilder.build(), delay); + } + } + + // --- External client methods + + public static class ExternalClient { + private ExternalClient() {} + + public static Value invoke( + Channel channel, + MethodDescriptor methodDesc, + @Nullable Value payload) { + RpcRequest.Builder reqBuilder = RpcRequest.newBuilder(); + if (payload != null) { + reqBuilder.setRequest(payload); + } + + return blockingUnaryCall(channel, methodDesc, CallOptions.DEFAULT, reqBuilder.build()) + .getResponse(); + } + + public static void invokeOneWay( + Channel channel, + MethodDescriptor methodDesc, + @Nullable Value payload) { + RpcRequest.Builder reqBuilder = RpcRequest.newBuilder(); + if (payload != null) { + reqBuilder.setRequest(payload); + } + + var ingressClient = IngressGrpc.newBlockingStub(channel); + ingressClient.invoke( + dev.restate.generated.InvokeRequest.newBuilder() + .setService(methodDesc.getServiceName()) + .setMethod(methodDesc.getBareMethodName()) + .setPb(reqBuilder.build().toByteString()) + .build()); + } + + public static Value invokeKeyed( + Channel channel, + MethodDescriptor methodDesc, + String key, + @Nullable Value payload) { + KeyedRpcRequest.Builder reqBuilder = KeyedRpcRequest.newBuilder().setKey(key); + if (payload != null) { + reqBuilder.setRequest(payload); + } + + return blockingUnaryCall(channel, methodDesc, CallOptions.DEFAULT, reqBuilder.build()) + .getResponse(); + } + + public static void invokeKeyedOneWay( + Channel channel, + MethodDescriptor methodDesc, + String key, + @Nullable Value payload) { + KeyedRpcRequest.Builder reqBuilder = KeyedRpcRequest.newBuilder().setKey(key); + if (payload != null) { + reqBuilder.setRequest(payload); + } + + var ingressClient = IngressGrpc.newBlockingStub(channel); + ingressClient.invoke( + dev.restate.generated.InvokeRequest.newBuilder() + .setService(methodDesc.getServiceName()) + .setMethod(methodDesc.getBareMethodName()) + .setPb(reqBuilder.build().toByteString()) + .build()); + } + } + + // --- Method descriptors manglers + + public static MethodDescriptor generateMethodDescriptor( + MethodDescriptor original, String serviceFqn, String methodName) { + return original.toBuilder() + .setFullMethodName(MethodDescriptor.generateFullMethodName(serviceFqn, methodName)) + .build(); + } +} diff --git a/sdk-api/src/main/java/dev/restate/sdk/dynrpc/DescriptorUtils.java b/sdk-api/src/main/java/dev/restate/sdk/dynrpc/DescriptorUtils.java new file mode 100644 index 00000000..d1197cc6 --- /dev/null +++ b/sdk-api/src/main/java/dev/restate/sdk/dynrpc/DescriptorUtils.java @@ -0,0 +1,125 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk.dynrpc; + +import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.Descriptors; +import dev.restate.sdk.dynrpc.template.generated.KeyedServiceGrpc; +import io.grpc.protobuf.ProtoFileDescriptorSupplier; +import io.grpc.protobuf.ProtoMethodDescriptorSupplier; +import io.grpc.protobuf.ProtoServiceDescriptorSupplier; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; + +class DescriptorUtils { + + private DescriptorUtils() {} + + static class AdapterServiceDescriptorSupplier + implements ProtoFileDescriptorSupplier, ProtoServiceDescriptorSupplier { + private final Descriptors.FileDescriptor desc; + private final String serviceName; + + AdapterServiceDescriptorSupplier(Descriptors.FileDescriptor desc, String serviceName) { + this.desc = desc; + this.serviceName = serviceName; + } + + @Override + public Descriptors.FileDescriptor getFileDescriptor() { + return desc; + } + + @Override + public Descriptors.ServiceDescriptor getServiceDescriptor() { + return desc.findServiceByName(this.serviceName); + } + } + + static class AdapterMethodDescriptorSupplier extends AdapterServiceDescriptorSupplier + implements ProtoMethodDescriptorSupplier { + private final String methodName; + + AdapterMethodDescriptorSupplier( + Descriptors.FileDescriptor desc, String serviceName, String methodName) { + super(desc, serviceName); + this.methodName = methodName; + } + + @Override + public Descriptors.MethodDescriptor getMethodDescriptor() { + return getServiceDescriptor().findMethodByName(methodName); + } + } + + public static Descriptors.FileDescriptor mangle( + String packageName, String simpleName, Set methods, boolean isKeyed) { + // This is the built-in workflow.proto descriptor + var templateDescriptor = + ((ProtoFileDescriptorSupplier) + Objects.requireNonNull( + KeyedServiceGrpc.getServiceDescriptor().getSchemaDescriptor())) + .getFileDescriptor(); + var protoDescriptorBuilder = + DescriptorProtos.FileDescriptorProto.newBuilder(templateDescriptor.toProto()); + + // Set package name and file desc name + if (packageName != null) { + protoDescriptorBuilder.setName( + packageName.replaceAll(Pattern.quote("."), "/") + "/" + simpleName + "/dynrpc.proto"); + protoDescriptorBuilder.setPackage(packageName); + } else { + protoDescriptorBuilder.setName(simpleName + "/dynrpc.proto"); + protoDescriptorBuilder.clearPackage(); + } + + // Mangle service descriptors + DescriptorProtos.ServiceDescriptorProto templateServiceDescriptorProto = + isKeyed ? protoDescriptorBuilder.getService(1) : protoDescriptorBuilder.getService(0); + + mangleServiceDescriptor( + templateServiceDescriptorProto, protoDescriptorBuilder, simpleName, methods); + + Descriptors.FileDescriptor outputFileDescriptor; + try { + outputFileDescriptor = + Descriptors.FileDescriptor.buildFrom( + protoDescriptorBuilder.build(), + templateDescriptor.getDependencies().toArray(new Descriptors.FileDescriptor[0])); + } catch (Descriptors.DescriptorValidationException e) { + throw new RuntimeException(e); + } + + return outputFileDescriptor; + } + + private static void mangleServiceDescriptor( + DescriptorProtos.ServiceDescriptorProto serviceDescriptorProto, + DescriptorProtos.FileDescriptorProto.Builder protoDescriptorBuilder, + String prefix, + Set methods) { + var serviceDescriptorBuilder = serviceDescriptorProto.toBuilder(); + + serviceDescriptorBuilder.setName(prefix); + + // Unroll methods + assert serviceDescriptorBuilder.getMethodCount() == 1; + DescriptorProtos.MethodDescriptorProto invokeTemplateMethodDesc = + serviceDescriptorBuilder.getMethod(0); + serviceDescriptorBuilder.removeMethod(0); + for (String method : methods) { + serviceDescriptorBuilder.addMethod(invokeTemplateMethodDesc.toBuilder().setName(method)); + } + + // Update original descriptor builder + protoDescriptorBuilder.clearService(); + protoDescriptorBuilder.addService(serviceDescriptorBuilder); + } +} diff --git a/sdk-api/src/main/java/dev/restate/sdk/dynrpc/JavaComponent.java b/sdk-api/src/main/java/dev/restate/sdk/dynrpc/JavaComponent.java new file mode 100644 index 00000000..6d052288 --- /dev/null +++ b/sdk-api/src/main/java/dev/restate/sdk/dynrpc/JavaComponent.java @@ -0,0 +1,338 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk.dynrpc; + +import com.google.protobuf.Descriptors; +import dev.restate.sdk.Component; +import dev.restate.sdk.Context; +import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.common.*; +import dev.restate.sdk.common.ComponentType; +import dev.restate.sdk.common.syscalls.ComponentDefinition; +import dev.restate.sdk.common.syscalls.Syscalls; +import dev.restate.sdk.dynrpc.generated.KeyedRpcRequest; +import dev.restate.sdk.dynrpc.generated.RpcRequest; +import dev.restate.sdk.dynrpc.generated.RpcResponse; +import dev.restate.sdk.dynrpc.template.generated.KeyedServiceGrpc; +import dev.restate.sdk.dynrpc.template.generated.ServiceGrpc; +import io.grpc.MethodDescriptor; +import io.grpc.ServerCallHandler; +import io.grpc.ServerServiceDefinition; +import io.grpc.ServiceDescriptor; +import io.grpc.stub.ServerCalls; +import io.grpc.stub.StreamObserver; +import java.util.*; +import java.util.function.BiFunction; +import javax.annotation.Nullable; + +public class JavaComponent implements Component, ComponentBundle { + private final String name; + private final HashMap> handlers; + private final ServerServiceDefinition serverServiceDefinition; + private final ComponentDefinition componentDefinition; + + private JavaComponent(String fqsn, boolean isKeyed, HashMap> handlers) { + this.name = fqsn; + this.handlers = handlers; + + String simpleName = getSimpleName(fqsn); + String packageName = getPackageName(fqsn); + + this.serverServiceDefinition = + buildServerServiceDefinition( + DescriptorUtils.mangle(packageName, simpleName, handlers.keySet(), isKeyed), + simpleName, + fqsn, + handlers.keySet(), + isKeyed); + + this.componentDefinition = + new ComponentDefinition( + fqsn, + ComponentDefinition.ExecutorType.BLOCKING, + isKeyed ? ComponentType.VIRTUAL_OBJECT : ComponentType.SERVICE, + Collections.emptyList() // TODO + ); + } + + private Handler getHandler(String name) { + return handlers.get(name); + } + + public String getName() { + return name; + } + + @Override + public ComponentDefinition definition() { + return this.componentDefinition; + } + + @Override + public ServerServiceDefinition bindService() { + return this.serverServiceDefinition; + } + + public static StatelessServiceBuilder service(String name) { + return new StatelessServiceBuilder(name); + } + + public static ObjectServiceBuilder virtualObject(String name) { + return new ObjectServiceBuilder(name); + } + + @Override + public List components() { + return List.of(this); + } + + public static class ObjectServiceBuilder { + private final String name; + private final HashMap> handlers; + + ObjectServiceBuilder(String name) { + this.name = name; + this.handlers = new HashMap<>(); + } + + public ObjectServiceBuilder with( + HandlerSignature sig, BiFunction runner) { + this.handlers.put(sig.getMethod(), new Handler<>(sig, runner)); + return this; + } + + public JavaComponent build() { + return new JavaComponent(this.name, true, this.handlers); + } + } + + public static class StatelessServiceBuilder { + private final String name; + private final HashMap> methods; + + StatelessServiceBuilder(String name) { + this.name = name; + this.methods = new HashMap<>(); + } + + public StatelessServiceBuilder with( + HandlerSignature sig, BiFunction runner) { + this.methods.put(sig.getMethod(), new Handler<>(sig, runner)); + return this; + } + + public JavaComponent build() { + return new JavaComponent(this.name, false, this.methods); + } + } + + @SuppressWarnings("unchecked") + public static class Handler { + private final HandlerSignature handlerSignature; + + private final BiFunction runner; + + Handler( + HandlerSignature handlerSignature, + BiFunction runner) { + this.handlerSignature = handlerSignature; + this.runner = (BiFunction) runner; + } + + public HandlerSignature getHandlerSignature() { + return handlerSignature; + } + + public RES run(Context ctx, REQ req) { + return runner.apply(ctx, req); + } + } + + public static class HandlerSignature { + + private final String method; + private final Serde requestSerde; + private final Serde responseSerde; + + HandlerSignature(String method, Serde requestSerde, Serde responseSerde) { + this.method = method; + this.requestSerde = requestSerde; + this.responseSerde = responseSerde; + } + + public static HandlerSignature of( + String method, Serde requestSerde, Serde responseSerde) { + return new HandlerSignature<>(method, requestSerde, responseSerde); + } + + public String getMethod() { + return method; + } + + public Serde getRequestSerde() { + return requestSerde; + } + + public Serde getResponseSerde() { + return responseSerde; + } + } + + private static String getSimpleName(String name) { + return name.substring(name.lastIndexOf(".") + 1); + } + + private static @Nullable String getPackageName(String name) { + int i = name.lastIndexOf("."); + if (i < 0) { + return null; + } + return name.substring(0, i); + } + + private ServerServiceDefinition buildServerServiceDefinition( + Descriptors.FileDescriptor outputFileDescriptor, + String simpleName, + String fqsn, + Set methodNames, + boolean isKeyed) { + var adapterDescriptorSupplier = + new DescriptorUtils.AdapterServiceDescriptorSupplier(outputFileDescriptor, simpleName); + ServiceDescriptor.Builder grpcServiceDescriptorBuilder = + ServiceDescriptor.newBuilder(fqsn).setSchemaDescriptor(adapterDescriptorSupplier); + + var methodDescriptors = + List.copyOf( + (isKeyed ? KeyedServiceGrpc.getServiceDescriptor() : ServiceGrpc.getServiceDescriptor()) + .getMethods()); + assert methodDescriptors.size() == 1; + + // Compute methods + MethodDescriptor invokeTemplateDescriptor = methodDescriptors.get(0); + Map> methods = new HashMap<>(); + for (String methodName : methodNames) { + var newMethodDescriptor = + invokeTemplateDescriptor.toBuilder() + .setSchemaDescriptor( + new DescriptorUtils.AdapterMethodDescriptorSupplier( + outputFileDescriptor, simpleName, methodName)) + .setFullMethodName(MethodDescriptor.generateFullMethodName(fqsn, methodName)) + .build(); + methods.put(methodName, newMethodDescriptor); + grpcServiceDescriptorBuilder.addMethod(newMethodDescriptor); + } + + ServiceDescriptor grpcServiceDescriptor = grpcServiceDescriptorBuilder.build(); + + ServerServiceDefinition.Builder serverServiceDefinitionBuilder = + ServerServiceDefinition.builder(grpcServiceDescriptor); + + // Compute shared methods + for (var method : methods.entrySet()) { + if (isKeyed) { + @SuppressWarnings("unchecked") + MethodDescriptor desc = + (MethodDescriptor) methods.get(method.getKey()); + ServerCallHandler handler = + ServerCalls.asyncUnaryCall( + (invokeRequest, streamObserver) -> + this.invokeKeyed( + method.getKey(), + ObjectContext.fromSyscalls(Syscalls.current()), + invokeRequest, + streamObserver)); + + serverServiceDefinitionBuilder.addMethod(desc, handler); + } else { + @SuppressWarnings("unchecked") + MethodDescriptor desc = + (MethodDescriptor) methods.get(method.getKey()); + ServerCallHandler handler = + ServerCalls.asyncUnaryCall( + (invokeRequest, streamObserver) -> + this.invokeUnkeyed( + method.getKey(), + Context.fromSyscalls(Syscalls.current()), + invokeRequest, + streamObserver)); + + serverServiceDefinitionBuilder.addMethod(desc, handler); + } + } + + return serverServiceDefinitionBuilder.build(); + } + + private void invokeKeyed( + String methodName, + ObjectContext objectContext, + KeyedRpcRequest invokeRequest, + StreamObserver streamObserver) { + // Lookup the method + @SuppressWarnings("unchecked") + Handler handler = (Handler) this.getHandler(methodName); + if (handler == null) { + throw new TerminalException( + TerminalException.Code.NOT_FOUND, "Method " + methodName + " not found"); + } + + // Convert input + Object input = + CodegenUtils.valueToT( + handler.getHandlerSignature().getRequestSerde(), invokeRequest.getRequest()); + + // Invoke method + // We let the sdk core to manage the failures + + // TODO add key to context? + Object output = handler.run(objectContext, input); + + replySuccess( + RpcResponse.newBuilder() + .setResponse( + CodegenUtils.tToValue(handler.getHandlerSignature().getResponseSerde(), output)) + .build(), + streamObserver); + } + + private void invokeUnkeyed( + String methodName, + Context context, + RpcRequest invokeRequest, + StreamObserver streamObserver) { + // Lookup the method + @SuppressWarnings("unchecked") + Handler handler = (Handler) this.getHandler(methodName); + if (handler == null) { + throw new TerminalException( + TerminalException.Code.NOT_FOUND, "Method " + methodName + " not found"); + } + + // Convert input + Object input = + CodegenUtils.valueToT( + handler.getHandlerSignature().getRequestSerde(), invokeRequest.getRequest()); + + // Invoke method + // We let the sdk core to manage the failures + Object output = handler.run(context, input); + + replySuccess( + RpcResponse.newBuilder() + .setResponse( + CodegenUtils.tToValue(handler.getHandlerSignature().getResponseSerde(), output)) + .build(), + streamObserver); + } + + private void replySuccess(T value, StreamObserver streamObserver) { + streamObserver.onNext(value); + streamObserver.onCompleted(); + } +} diff --git a/sdk-api/src/main/proto/dev/restate/sdk/dynrpc/dynrpc.proto b/sdk-api/src/main/proto/dev/restate/sdk/dynrpc/dynrpc.proto new file mode 100644 index 00000000..4600f6aa --- /dev/null +++ b/sdk-api/src/main/proto/dev/restate/sdk/dynrpc/dynrpc.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +syntax = "proto3"; + +package dev.restate.sdk.dynrpc; + +import "google/protobuf/struct.proto"; +import "dev/restate/ext.proto"; + +option java_multiple_files = true; +option java_package = "dev.restate.sdk.dynrpc.generated"; +option java_outer_classname = "DynRpcProto"; + +message RpcRequest { + google.protobuf.Value request = 1; +} + +message KeyedRpcRequest { + string key = 1 [(dev.restate.ext.field) = KEY]; + google.protobuf.Value request = 2; +} + +message RpcResponse { + google.protobuf.Value response = 1; +} \ No newline at end of file diff --git a/sdk-api/src/main/proto/template_dynrpc.proto b/sdk-api/src/main/proto/template_dynrpc.proto new file mode 100644 index 00000000..e6c16b12 --- /dev/null +++ b/sdk-api/src/main/proto/template_dynrpc.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "google/protobuf/struct.proto"; +import "dev/restate/ext.proto"; +import "dev/restate/sdk/dynrpc/dynrpc.proto"; + +option java_multiple_files = true; +option java_package = "dev.restate.sdk.dynrpc.template.generated"; +option java_outer_classname = "TemplateDynRpcProto"; + +service Service { + option (dev.restate.ext.service_type) = UNKEYED; + + rpc Template(dev.restate.sdk.dynrpc.RpcRequest) returns (dev.restate.sdk.dynrpc.RpcResponse); +} + +service KeyedService { + option (dev.restate.ext.service_type) = KEYED; + + rpc Template(dev.restate.sdk.dynrpc.KeyedRpcRequest) returns (dev.restate.sdk.dynrpc.RpcResponse); +} + diff --git a/sdk-api/src/test/java/dev/restate/sdk/AwaitableTest.java b/sdk-api/src/test/java/dev/restate/sdk/AwaitableTest.java index 91f6142d..d4cf1ac5 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/AwaitableTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/AwaitableTest.java @@ -24,11 +24,10 @@ public class AwaitableTest extends DeferredTestSuite { - private static class ReverseAwaitOrder extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class ReverseAwaitOrder extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); Awaitable a1 = ctx.call(GreeterGrpc.getGreetMethod(), greetingRequest("Francesco")); @@ -51,10 +50,10 @@ protected BindableService reverseAwaitOrder() { } private static class AwaitTwiceTheSameAwaitable extends GreeterGrpc.GreeterImplBase - implements RestateService { + implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); Awaitable a = ctx.call(GreeterGrpc.getGreetMethod(), greetingRequest("Francesco")); @@ -70,10 +69,10 @@ protected BindableService awaitTwiceTheSameAwaitable() { return new AwaitTwiceTheSameAwaitable(); } - private static class AwaitAll extends GreeterGrpc.GreeterImplBase implements RestateService { + private static class AwaitAll extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); Awaitable a1 = ctx.call(GreeterGrpc.getGreetMethod(), greetingRequest("Francesco")); @@ -93,10 +92,10 @@ protected BindableService awaitAll() { return new AwaitAll(); } - private static class AwaitAny extends GreeterGrpc.GreeterImplBase implements RestateService { + private static class AwaitAny extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); Awaitable a1 = ctx.call(GreeterGrpc.getGreetMethod(), greetingRequest("Francesco")); @@ -115,11 +114,10 @@ protected BindableService awaitAny() { return new AwaitAny(); } - private static class CombineAnyWithAll extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class CombineAnyWithAll extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); Awaitable a1 = ctx.awakeable(CoreSerdes.JSON_STRING); Awaitable a2 = ctx.awakeable(CoreSerdes.JSON_STRING); @@ -141,10 +139,10 @@ protected BindableService combineAnyWithAll() { return new CombineAnyWithAll(); } - private static class AwaitAnyIndex extends GreeterGrpc.GreeterImplBase implements RestateService { + private static class AwaitAnyIndex extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); Awaitable a1 = ctx.awakeable(CoreSerdes.JSON_STRING); Awaitable a2 = ctx.awakeable(CoreSerdes.JSON_STRING); @@ -164,10 +162,10 @@ protected BindableService awaitAnyIndex() { } private static class AwaitOnAlreadyResolvedAwaitables extends GreeterGrpc.GreeterImplBase - implements RestateService { + implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); Awaitable a1 = ctx.awakeable(CoreSerdes.JSON_STRING); Awaitable a2 = ctx.awakeable(CoreSerdes.JSON_STRING); @@ -189,11 +187,10 @@ protected BindableService awaitOnAlreadyResolvedAwaitables() { return new AwaitOnAlreadyResolvedAwaitables(); } - private static class AwaitWithTimeout extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class AwaitWithTimeout extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); Awaitable call = ctx.call(GreeterGrpc.getGreetMethod(), greetingRequest("Francesco")); diff --git a/sdk-api/src/test/java/dev/restate/sdk/AwakeableIdTest.java b/sdk-api/src/test/java/dev/restate/sdk/AwakeableIdTest.java index 4cf9895d..31e686b1 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/AwakeableIdTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/AwakeableIdTest.java @@ -20,12 +20,11 @@ public class AwakeableIdTest extends AwakeableIdTestSuite { - private static class ReturnAwakeableId extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class ReturnAwakeableId extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - String id = KeyedContext.current().awakeable(CoreSerdes.JSON_STRING).id(); + String id = ObjectContext.current().awakeable(CoreSerdes.JSON_STRING).id(); responseObserver.onNext(greetingResponse(id)); responseObserver.onCompleted(); } diff --git a/sdk-api/src/test/java/dev/restate/sdk/EagerStateTest.java b/sdk-api/src/test/java/dev/restate/sdk/EagerStateTest.java index 5ac1cc50..24e50c1a 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/EagerStateTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/EagerStateTest.java @@ -23,10 +23,10 @@ public class EagerStateTest extends EagerStateTestSuite { - private static class GetEmpty extends GreeterGrpc.GreeterImplBase implements RestateService { + private static class GetEmpty extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); boolean stateIsEmpty = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).isEmpty(); @@ -41,10 +41,10 @@ protected BindableService getEmpty() { return new GetEmpty(); } - private static class Get extends GreeterGrpc.GreeterImplBase implements RestateService { + private static class Get extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); String state = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); @@ -58,11 +58,10 @@ protected BindableService get() { return new Get(); } - private static class GetAppendAndGet extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class GetAppendAndGet extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); String oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); ctx.set(StateKey.of("STATE", CoreSerdes.JSON_STRING), oldState + request.getName()); @@ -79,11 +78,10 @@ protected BindableService getAppendAndGet() { return new GetAppendAndGet(); } - private static class GetClearAndGet extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class GetClearAndGet extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); String oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); @@ -100,11 +98,10 @@ protected BindableService getClearAndGet() { return new GetClearAndGet(); } - private static class GetClearAllAndGet extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class GetClearAllAndGet extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); String oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); @@ -124,7 +121,7 @@ protected BindableService getClearAllAndGet() { private static class ListKeys extends GreeterRestate.GreeterRestateImplBase { @Override - public GreetingResponse greet(KeyedContext context, GreetingRequest request) + public GreetingResponse greet(ObjectContext context, GreetingRequest request) throws TerminalException { return GreetingResponse.newBuilder() .setMessage(String.join(",", context.stateKeys())) diff --git a/sdk-api/src/test/java/dev/restate/sdk/GrpcChannelAdapterTest.java b/sdk-api/src/test/java/dev/restate/sdk/GrpcChannelAdapterTest.java index ee10d486..a96630e9 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/GrpcChannelAdapterTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/GrpcChannelAdapterTest.java @@ -24,10 +24,10 @@ public class GrpcChannelAdapterTest implements TestSuite { private static class InvokeUsingGeneratedClient extends GreeterGrpc.GreeterImplBase - implements RestateService { + implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(ctx.grpcChannel()); String response = client.greet(GreetingRequest.newBuilder().setName("Francesco").build()).getMessage(); @@ -38,10 +38,10 @@ public void greet(GreetingRequest request, StreamObserver resp } private static class InvokeUsingGeneratedFutureClient extends GreeterGrpc.GreeterImplBase - implements RestateService { + implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); GreeterGrpc.GreeterFutureStub client = GreeterGrpc.newFutureStub(ctx.grpcChannel()); String response; try { diff --git a/sdk-api/src/test/java/dev/restate/sdk/InvocationIdTest.java b/sdk-api/src/test/java/dev/restate/sdk/InvocationIdTest.java index 61e52134..5ab5837c 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/InvocationIdTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/InvocationIdTest.java @@ -20,8 +20,7 @@ public class InvocationIdTest extends InvocationIdTestSuite { - private static class ReturnInvocationId extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class ReturnInvocationId extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { diff --git a/sdk-api/src/test/java/dev/restate/sdk/OnlyInputAndOutputTest.java b/sdk-api/src/test/java/dev/restate/sdk/OnlyInputAndOutputTest.java index 6d71d0fc..bfda02ce 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/OnlyInputAndOutputTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/OnlyInputAndOutputTest.java @@ -17,8 +17,7 @@ public class OnlyInputAndOutputTest extends OnlyInputAndOutputTestSuite { - private static class NoSyscallsGreeter extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class NoSyscallsGreeter extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { responseObserver.onNext( diff --git a/sdk-api/src/test/java/dev/restate/sdk/RandomTest.java b/sdk-api/src/test/java/dev/restate/sdk/RandomTest.java index 9b7aa302..10666475 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/RandomTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/RandomTest.java @@ -20,7 +20,7 @@ public class RandomTest extends RandomTestSuite { private static class RandomShouldBeDeterministic extends GreeterRestate.GreeterRestateImplBase { @Override - public GreetingResponse greet(KeyedContext context, GreetingRequest request) + public GreetingResponse greet(ObjectContext context, GreetingRequest request) throws TerminalException { return GreetingResponse.newBuilder() .setMessage(Integer.toString(context.random().nextInt())) @@ -35,7 +35,7 @@ protected BindableService randomShouldBeDeterministic() { private static class RandomInsideSideEffect extends GreeterRestate.GreeterRestateImplBase { @Override - public GreetingResponse greet(KeyedContext context, GreetingRequest request) + public GreetingResponse greet(ObjectContext context, GreetingRequest request) throws TerminalException { context.sideEffect(() -> context.random().nextInt()); throw new IllegalStateException("This should not unreachable"); diff --git a/sdk-api/src/test/java/dev/restate/sdk/RestateCodegenTest.java b/sdk-api/src/test/java/dev/restate/sdk/RestateCodegenTest.java index 568f8482..bf7e0e15 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/RestateCodegenTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/RestateCodegenTest.java @@ -19,7 +19,7 @@ private static class GreeterWithRestateClientAndServerCodegen extends GreeterRestate.GreeterRestateImplBase { @Override - public GreetingResponse greet(KeyedContext context, GreetingRequest request) { + public GreetingResponse greet(ObjectContext context, GreetingRequest request) { GreeterRestate.GreeterRestateClient client = GreeterRestate.newClient(context); client.delayed(Duration.ofSeconds(1)).greet(request); client.oneWay().greet(request); diff --git a/sdk-api/src/test/java/dev/restate/sdk/SideEffectTest.java b/sdk-api/src/test/java/dev/restate/sdk/SideEffectTest.java index 25fd3b1a..2e928565 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/SideEffectTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/SideEffectTest.java @@ -21,7 +21,7 @@ public class SideEffectTest extends SideEffectTestSuite { - private static class SideEffect extends GreeterGrpc.GreeterImplBase implements RestateService { + private static class SideEffect extends GreeterGrpc.GreeterImplBase implements Component { private final String sideEffectOutput; @@ -31,7 +31,7 @@ private static class SideEffect extends GreeterGrpc.GreeterImplBase implements R @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); String result = ctx.sideEffect(CoreSerdes.JSON_STRING, () -> this.sideEffectOutput); @@ -46,7 +46,7 @@ protected BindableService sideEffect(String sideEffectOutput) { } private static class ConsecutiveSideEffect extends GreeterGrpc.GreeterImplBase - implements RestateService { + implements Component { private final String sideEffectOutput; @@ -56,7 +56,7 @@ private static class ConsecutiveSideEffect extends GreeterGrpc.GreeterImplBase @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); String firstResult = ctx.sideEffect(CoreSerdes.JSON_STRING, () -> this.sideEffectOutput); String secondResult = ctx.sideEffect(CoreSerdes.JSON_STRING, firstResult::toUpperCase); @@ -73,14 +73,14 @@ protected BindableService consecutiveSideEffect(String sideEffectOutput) { } private static class CheckContextSwitching extends GreeterGrpc.GreeterImplBase - implements RestateService { + implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { String currentThread = Thread.currentThread().getName(); String sideEffectThread = - KeyedContext.current() + ObjectContext.current() .sideEffect(CoreSerdes.JSON_STRING, () -> Thread.currentThread().getName()); if (!Objects.equals(currentThread, sideEffectThread)) { @@ -101,12 +101,11 @@ protected BindableService checkContextSwitching() { return new CheckContextSwitching(); } - private static class SideEffectGuard extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class SideEffectGuard extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); ctx.sideEffect( () -> ctx.oneWayCall(GreeterGrpc.getGreetMethod(), greetingRequest("something"))); diff --git a/sdk-api/src/test/java/dev/restate/sdk/SleepTest.java b/sdk-api/src/test/java/dev/restate/sdk/SleepTest.java index 8bb0deaf..aac5ccab 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/SleepTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/SleepTest.java @@ -20,11 +20,11 @@ public class SleepTest extends SleepTestSuite { - private static class SleepGreeter extends GreeterGrpc.GreeterImplBase implements RestateService { + private static class SleepGreeter extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); ctx.sleep(Duration.ofSeconds(1)); @@ -38,11 +38,11 @@ protected BindableService sleepGreeter() { return new SleepGreeter(); } - private static class ManySleeps extends GreeterGrpc.GreeterImplBase implements RestateService { + private static class ManySleeps extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); List> collectedAwaitables = new ArrayList<>(); for (int i = 0; i < 10; i++) { diff --git a/sdk-api/src/test/java/dev/restate/sdk/StateMachineFailuresTest.java b/sdk-api/src/test/java/dev/restate/sdk/StateMachineFailuresTest.java index cee08068..2fef11f7 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/StateMachineFailuresTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/StateMachineFailuresTest.java @@ -25,7 +25,7 @@ public class StateMachineFailuresTest extends StateMachineFailuresTestSuite { - private static class GetState extends GreeterGrpc.GreeterImplBase implements RestateService { + private static class GetState extends GreeterGrpc.GreeterImplBase implements Component { private static final StateKey STATE = StateKey.of( @@ -43,7 +43,7 @@ private GetState(AtomicInteger nonTerminalExceptionsSeen) { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { try { - KeyedContext.current().get(STATE); + ObjectContext.current().get(STATE); } catch (Throwable e) { // A user should never catch Throwable!!! if (AbortedExecutionException.INSTANCE.equals(e)) { @@ -65,8 +65,7 @@ protected BindableService getState(AtomicInteger nonTerminalExceptionsSeen) { return new GetState(nonTerminalExceptionsSeen); } - private static class SideEffectFailure extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class SideEffectFailure extends GreeterGrpc.GreeterImplBase implements Component { private final Serde serde; private SideEffectFailure(Serde serde) { @@ -75,7 +74,7 @@ private SideEffectFailure(Serde serde) { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext.current().sideEffect(serde, () -> 0); + ObjectContext.current().sideEffect(serde, () -> 0); responseObserver.onNext(greetingResponse("Francesco")); responseObserver.onCompleted(); diff --git a/sdk-api/src/test/java/dev/restate/sdk/StateTest.java b/sdk-api/src/test/java/dev/restate/sdk/StateTest.java index 1ac511fe..fbb25c36 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/StateTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/StateTest.java @@ -22,11 +22,11 @@ public class StateTest extends StateTestSuite { - private static class GetState extends GreeterGrpc.GreeterImplBase implements RestateService { + private static class GetState extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { String state = - KeyedContext.current() + ObjectContext.current() .get(StateKey.of("STATE", CoreSerdes.JSON_STRING)) .orElse("Unknown"); @@ -40,11 +40,10 @@ protected BindableService getState() { return new GetState(); } - private static class GetAndSetState extends GreeterGrpc.GreeterImplBase - implements RestateService { + private static class GetAndSetState extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); String state = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); @@ -60,10 +59,10 @@ protected BindableService getAndSetState() { return new GetAndSetState(); } - private static class SetNullState extends GreeterGrpc.GreeterImplBase implements RestateService { + private static class SetNullState extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext.current() + ObjectContext.current() .set( StateKey.of( "STATE", diff --git a/sdk-api/src/test/java/dev/restate/sdk/UserFailuresTest.java b/sdk-api/src/test/java/dev/restate/sdk/UserFailuresTest.java index eb90757f..f12676b5 100644 --- a/sdk-api/src/test/java/dev/restate/sdk/UserFailuresTest.java +++ b/sdk-api/src/test/java/dev/restate/sdk/UserFailuresTest.java @@ -27,7 +27,7 @@ public class UserFailuresTest extends UserFailuresTestSuite { private static class ThrowIllegalStateException extends GreeterGrpc.GreeterImplBase - implements RestateService { + implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { throw new IllegalStateException("Whatever"); @@ -40,7 +40,7 @@ protected BindableService throwIllegalStateException() { } private static class SideEffectThrowIllegalStateException extends GreeterGrpc.GreeterImplBase - implements RestateService { + implements Component { private final AtomicInteger nonTerminalExceptionsSeen; @@ -51,7 +51,7 @@ private SideEffectThrowIllegalStateException(AtomicInteger nonTerminalExceptions @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { try { - KeyedContext.current() + ObjectContext.current() .sideEffect( () -> { throw new IllegalStateException("Whatever"); @@ -76,7 +76,7 @@ protected BindableService sideEffectThrowIllegalStateException( } private static class ThrowTerminalException extends GreeterGrpc.GreeterImplBase - implements RestateService { + implements Component { private final TerminalException.Code code; private final String message; @@ -98,7 +98,7 @@ protected BindableService throwTerminalException(TerminalException.Code code, St } private static class SideEffectThrowTerminalException extends GreeterGrpc.GreeterImplBase - implements RestateService { + implements Component { private final TerminalException.Code code; private final String message; @@ -110,7 +110,7 @@ private SideEffectThrowTerminalException(TerminalException.Code code, String mes @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { - KeyedContext.current() + ObjectContext.current() .sideEffect( () -> { throw new TerminalException(code, message); @@ -127,7 +127,7 @@ protected BindableService sideEffectThrowTerminalException( // -- Response observer is something specific to the sdk-java interface private static class ResponseObserverOnErrorTerminalException extends GreeterGrpc.GreeterImplBase - implements RestateService { + implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { responseObserver.onError(new TerminalException(TerminalException.Code.INTERNAL, MY_ERROR)); @@ -135,7 +135,7 @@ public void greet(GreetingRequest request, StreamObserver resp } private static class ResponseObserverOnErrorIllegalStateException - extends GreeterGrpc.GreeterImplBase implements RestateService { + extends GreeterGrpc.GreeterImplBase implements Component { @Override public void greet(GreetingRequest request, StreamObserver responseObserver) { responseObserver.onError(new IllegalStateException("Whatever")); diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/Service.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Exclusive.java similarity index 87% rename from sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/Service.java rename to sdk-common/src/main/java/dev/restate/sdk/annotation/Exclusive.java index e6e4e22a..6fcecfd9 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/Service.java +++ b/sdk-common/src/main/java/dev/restate/sdk/annotation/Exclusive.java @@ -13,9 +13,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.TYPE) +@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) -public @interface Service { - - ServiceType value(); -} +public @interface Exclusive {} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/Workflow.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Handler.java similarity index 95% rename from sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/Workflow.java rename to sdk-common/src/main/java/dev/restate/sdk/annotation/Handler.java index 0a06749c..feb2b8b7 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/Workflow.java +++ b/sdk-common/src/main/java/dev/restate/sdk/annotation/Handler.java @@ -15,4 +15,4 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) -public @interface Workflow {} +public @interface Handler {} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Service.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Service.java new file mode 100644 index 00000000..8563d584 --- /dev/null +++ b/sdk-common/src/main/java/dev/restate/sdk/annotation/Service.java @@ -0,0 +1,23 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface Service { + /** + * Name of the Service for Restate. If not provided, it will be the FQCN of the annotated element. + */ + String name() default ""; +} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/Shared.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Shared.java similarity index 100% rename from sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/Shared.java rename to sdk-common/src/main/java/dev/restate/sdk/annotation/Shared.java diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/VirtualObject.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/VirtualObject.java new file mode 100644 index 00000000..0af3e843 --- /dev/null +++ b/sdk-common/src/main/java/dev/restate/sdk/annotation/VirtualObject.java @@ -0,0 +1,24 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface VirtualObject { + /** + * Name of the VirtualObject for Restate. If not provided, it will be the FQCN of the annotated + * element. + */ + String name() default ""; +} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Workflow.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Workflow.java new file mode 100644 index 00000000..0538292c --- /dev/null +++ b/sdk-common/src/main/java/dev/restate/sdk/annotation/Workflow.java @@ -0,0 +1,24 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.SOURCE) +public @interface Workflow { + /** + * Name of the Workflow for Restate. If not provided, it will be the FQCN of the annotated + * element. + */ + String name() default ""; +} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/BlockingService.java b/sdk-common/src/main/java/dev/restate/sdk/common/BlockingComponent.java similarity index 55% rename from sdk-common/src/main/java/dev/restate/sdk/common/BlockingService.java rename to sdk-common/src/main/java/dev/restate/sdk/common/BlockingComponent.java index feea5359..7e5b1ed6 100644 --- a/sdk-common/src/main/java/dev/restate/sdk/common/BlockingService.java +++ b/sdk-common/src/main/java/dev/restate/sdk/common/BlockingComponent.java @@ -9,8 +9,8 @@ package dev.restate.sdk.common; /** - * Marker interface for blocking services. This is used by some service endpoint implementations - * (like http-vertx) to select on which executor/context the service code should be executed. Refer - * to the *EndpointBuilder javadocs for more details. + * Marker interface for blocking components. This is used by some endpoint implementations (like + * http-vertx) to select on which executor/context the component code should be executed. Refer to + * the *EndpointBuilder javadocs for more details. */ -public interface BlockingService extends Service {} +public interface BlockingComponent extends Component {} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/Component.java b/sdk-common/src/main/java/dev/restate/sdk/common/Component.java new file mode 100644 index 00000000..58503b8e --- /dev/null +++ b/sdk-common/src/main/java/dev/restate/sdk/common/Component.java @@ -0,0 +1,19 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk.common; + +import dev.restate.sdk.common.syscalls.ComponentDefinition; +import io.grpc.BindableService; + +/** Marker interface for a Restate component. */ +public interface Component extends BindableService { + default ComponentDefinition definition() { + return null; + } +} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/Service.java b/sdk-common/src/main/java/dev/restate/sdk/common/ComponentAdapter.java similarity index 72% rename from sdk-common/src/main/java/dev/restate/sdk/common/Service.java rename to sdk-common/src/main/java/dev/restate/sdk/common/ComponentAdapter.java index df22be60..f52dbcb8 100644 --- a/sdk-common/src/main/java/dev/restate/sdk/common/Service.java +++ b/sdk-common/src/main/java/dev/restate/sdk/common/ComponentAdapter.java @@ -8,7 +8,9 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.common; -import io.grpc.BindableService; +public interface ComponentAdapter { -/** Marker interface for a Restate service. */ -public interface Service extends BindableService {} + ComponentBundle adapt(T componentObject); + + boolean supportsObject(Object componentObject); +} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/ServicesBundle.java b/sdk-common/src/main/java/dev/restate/sdk/common/ComponentBundle.java similarity index 76% rename from sdk-common/src/main/java/dev/restate/sdk/common/ServicesBundle.java rename to sdk-common/src/main/java/dev/restate/sdk/common/ComponentBundle.java index b5b55324..065b192b 100644 --- a/sdk-common/src/main/java/dev/restate/sdk/common/ServicesBundle.java +++ b/sdk-common/src/main/java/dev/restate/sdk/common/ComponentBundle.java @@ -10,8 +10,8 @@ import java.util.List; -/** Bundle of different Restate services. */ -public interface ServicesBundle { +/** Bundle of different Restate components. */ +public interface ComponentBundle { - List services(); + List components(); } diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/ServiceAdapter.java b/sdk-common/src/main/java/dev/restate/sdk/common/ComponentType.java similarity index 79% rename from sdk-common/src/main/java/dev/restate/sdk/common/ServiceAdapter.java rename to sdk-common/src/main/java/dev/restate/sdk/common/ComponentType.java index 4abc5537..60e18f2c 100644 --- a/sdk-common/src/main/java/dev/restate/sdk/common/ServiceAdapter.java +++ b/sdk-common/src/main/java/dev/restate/sdk/common/ComponentType.java @@ -8,8 +8,8 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.common; -@FunctionalInterface -public interface ServiceAdapter { - - ServicesBundle adapt(T entity); +public enum ComponentType { + SERVICE, + VIRTUAL_OBJECT, + WORKFLOW } diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/NonBlockingService.java b/sdk-common/src/main/java/dev/restate/sdk/common/NonBlockingComponent.java similarity index 53% rename from sdk-common/src/main/java/dev/restate/sdk/common/NonBlockingService.java rename to sdk-common/src/main/java/dev/restate/sdk/common/NonBlockingComponent.java index 9aeaf89f..b594b417 100644 --- a/sdk-common/src/main/java/dev/restate/sdk/common/NonBlockingService.java +++ b/sdk-common/src/main/java/dev/restate/sdk/common/NonBlockingComponent.java @@ -9,8 +9,8 @@ package dev.restate.sdk.common; /** - * Marker interface for non-blocking services. This is used by some service endpoint implementations - * (like http-vertx) to select on which executor/context the service code should be executed. Refer - * to the *EndpointBuilder javadocs for more details. + * Marker interface for non-blocking components. This is used by some component endpoint + * implementations (like http-vertx) to select on which executor/context the component code should + * be executed. Refer to the *EndpointBuilder javadocs for more details. */ -public interface NonBlockingService extends Service {} +public interface NonBlockingComponent extends Component {} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/Serde.java b/sdk-common/src/main/java/dev/restate/sdk/common/Serde.java index 1e969458..fdaa0db7 100644 --- a/sdk-common/src/main/java/dev/restate/sdk/common/Serde.java +++ b/sdk-common/src/main/java/dev/restate/sdk/common/Serde.java @@ -30,6 +30,13 @@ default T deserialize(ByteString byteString) { return deserialize(byteString.toByteArray()); } + /** + * @return the schema of this object. + */ + default @Nullable Object schema() { + return null; + } + /** * Create a {@link Serde} from {@code serializer}/{@code deserializer} lambdas. Before invoking * the serializer, we check that {@code value} is non-null. diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/Target.java b/sdk-common/src/main/java/dev/restate/sdk/common/Target.java new file mode 100644 index 00000000..b134087b --- /dev/null +++ b/sdk-common/src/main/java/dev/restate/sdk/common/Target.java @@ -0,0 +1,71 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk.common; + +import java.util.Objects; + +public final class Target { + + private final String component; + private final String handler; + private final String key; + + private Target(String component, String handler, String key) { + this.component = component; + this.handler = handler; + this.key = key; + } + + public static Target virtualObject(String name, String handler, String key) { + return new Target(name, handler, key); + } + + public static Target service(String name, String handler) { + return new Target(name, handler, null); + } + + public static Target workflow(String name, String handler, String key) { + return new Target(name, handler, key); + } + + public String getComponent() { + return component; + } + + public String getHandler() { + return handler; + } + + public String getKey() { + return key; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + Target target = (Target) object; + return Objects.equals(component, target.component) + && Objects.equals(handler, target.handler) + && Objects.equals(key, target.key); + } + + @Override + public int hashCode() { + return Objects.hash(component, handler, key); + } + + @Override + public String toString() { + if (key == null) { + return component + "/" + handler; + } + return component + "/" + key + "/" + handler; + } +} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ComponentDefinition.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ComponentDefinition.java new file mode 100644 index 00000000..8a72d7c9 --- /dev/null +++ b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ComponentDefinition.java @@ -0,0 +1,116 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk.common.syscalls; + +import dev.restate.sdk.common.ComponentType; +import java.util.List; +import java.util.Objects; + +public final class ComponentDefinition { + + public enum ExecutorType { + BLOCKING, + NON_BLOCKING + } + + public static final class HandlerDefinition { + private final String name; + private final Object inputSchema; + private final Object outputSchema; + private final InvocationHandler handler; + + public HandlerDefinition( + String name, Object inputSchema, Object outputSchema, InvocationHandler handler) { + this.name = name; + this.inputSchema = inputSchema; + this.outputSchema = outputSchema; + this.handler = handler; + } + + public String getName() { + return name; + } + + public Object getInputSchema() { + return inputSchema; + } + + public Object getOutputSchema() { + return outputSchema; + } + + public InvocationHandler getHandler() { + return handler; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + HandlerDefinition that = (HandlerDefinition) object; + return Objects.equals(name, that.name) + && Objects.equals(inputSchema, that.inputSchema) + && Objects.equals(outputSchema, that.outputSchema) + && Objects.equals(handler, that.handler); + } + + @Override + public int hashCode() { + return Objects.hash(name, inputSchema, outputSchema, handler); + } + } + + private final String fullyQualifiedServiceName; + private final ExecutorType executorType; + private final ComponentType componentType; + private final List methods; + + public ComponentDefinition( + String fullyQualifiedServiceName, + ExecutorType executorType, + ComponentType componentType, + List methods) { + this.fullyQualifiedServiceName = fullyQualifiedServiceName; + this.executorType = executorType; + this.componentType = componentType; + this.methods = methods; + } + + public String getFullyQualifiedServiceName() { + return fullyQualifiedServiceName; + } + + public ExecutorType getExecutorType() { + return executorType; + } + + public ComponentType getServiceType() { + return componentType; + } + + public List getMethods() { + return methods; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + ComponentDefinition that = (ComponentDefinition) object; + return Objects.equals(fullyQualifiedServiceName, that.fullyQualifiedServiceName) + && executorType == that.executorType + && componentType == that.componentType + && Objects.equals(methods, that.methods); + } + + @Override + public int hashCode() { + return Objects.hash(fullyQualifiedServiceName, executorType, componentType, methods); + } +} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/ServiceType.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/InvocationHandler.java similarity index 61% rename from sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/ServiceType.java rename to sdk-common/src/main/java/dev/restate/sdk/common/syscalls/InvocationHandler.java index 95d7a510..4c8e1235 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/annotation/ServiceType.java +++ b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/InvocationHandler.java @@ -6,8 +6,11 @@ // 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 -package dev.restate.sdk.annotation; +package dev.restate.sdk.common.syscalls; -public enum ServiceType { - WORKFLOW +import com.google.protobuf.ByteString; + +public interface InvocationHandler { + + void handle(Syscalls syscalls, ByteString input, SyscallCallback callback); } diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Syscalls.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Syscalls.java index 0d8490ed..b5e8d41d 100644 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Syscalls.java +++ b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Syscalls.java @@ -10,6 +10,7 @@ import com.google.protobuf.ByteString; import dev.restate.sdk.common.InvocationId; +import dev.restate.sdk.common.Target; import dev.restate.sdk.common.TerminalException; import io.grpc.Context; import io.grpc.MethodDescriptor; @@ -74,10 +75,18 @@ static Syscalls current() { void sleep(Duration duration, SyscallCallback> callback); + void call(Target target, ByteString parameter, SyscallCallback> callback); + void call( MethodDescriptor methodDescriptor, T parameter, SyscallCallback> callback); - void backgroundCall( + void send( + Target target, + ByteString parameter, + @Nullable Duration delay, + SyscallCallback requestCallback); + + void send( MethodDescriptor methodDescriptor, T parameter, @Nullable Duration delay, diff --git a/sdk-core/build.gradle.kts b/sdk-core/build.gradle.kts index 7b87e852..7aee31b3 100644 --- a/sdk-core/build.gradle.kts +++ b/sdk-core/build.gradle.kts @@ -3,6 +3,7 @@ import com.google.protobuf.gradle.id plugins { `java-library` `library-publishing-conventions` + id("org.jsonschema2pojo") version "1.2.1" } description = "Restate SDK Core" @@ -17,6 +18,9 @@ dependencies { implementation(coreLibs.grpc.protobuf) implementation(coreLibs.log4j.api) + // TODO Ideally we don't want this, but we need it for the descriptor... + implementation(jacksonLibs.jackson.databind) + // We don't want a hard-dependency on it compileOnly(coreLibs.log4j.core) @@ -33,6 +37,26 @@ dependencies { testImplementation(coreLibs.log4j.core) } +val generatedJ2SPDir = layout.buildDirectory.dir("generated/j2sp") + +sourceSets { main { java.srcDir(generatedJ2SPDir) } } + +jsonSchema2Pojo { + setSource(files("$projectDir/src/main/resources/json_schema")) + targetPackage = "dev.restate.sdk.core.manifest" + targetDirectory = generatedJ2SPDir.get().asFile + + useLongIntegers = false + includeSetters = true + includeGetters = true + generateBuilders = true +} + +tasks { + withType { dependsOn(generateJsonSchema2Pojo, generateProto) } + withType { dependsOn(generateJsonSchema2Pojo, generateProto) } +} + protobuf { plugins { id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:${coreLibs.versions.grpc.get()}" } diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java b/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java new file mode 100644 index 00000000..27e0447a --- /dev/null +++ b/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java @@ -0,0 +1,65 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// 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 +package dev.restate.sdk.core; + +import dev.restate.sdk.common.ComponentType; +import dev.restate.sdk.common.syscalls.ComponentDefinition; +import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import dev.restate.sdk.core.manifest.Method; +import dev.restate.sdk.core.manifest.Service; +import java.util.*; +import java.util.stream.Collectors; + +final class DeploymentManifest { + + private final DeploymentManifestSchema manifest; + + public DeploymentManifest( + DeploymentManifestSchema.ProtocolMode protocolMode, + Map services) { + this.manifest = + new DeploymentManifestSchema() + .withMinProtocolVersion(1) + .withMaxProtocolVersion(1) + .withProtocolMode(protocolMode) + .withServices( + services.values().stream() + .map( + svc -> + new Service() + .withFullyQualifiedServiceName(svc.getFullyQualifiedServiceName()) + .withServiceType(convertServiceType(svc.getServiceType())) + .withMethods( + svc.getMethods().stream() + .map( + method -> + new Method() + .withName(method.getName()) + .withInputSchema(method.getInputSchema()) + .withOutputSchema(method.getOutputSchema())) + .collect(Collectors.toList()))) + .collect(Collectors.toList())); + } + + public DeploymentManifestSchema manifest() { + return this.manifest; + } + + private static Service.ServiceType convertServiceType(ComponentType componentType) { + switch (componentType) { + case WORKFLOW: + return Service.ServiceType.WORKFLOW; + case VIRTUAL_OBJECT: + return Service.ServiceType.KEYED; + case SERVICE: + return Service.ServiceType.UNKEYED; + } + throw new IllegalStateException(); + } +} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/ExecutorSwitchingSyscalls.java b/sdk-core/src/main/java/dev/restate/sdk/core/ExecutorSwitchingSyscalls.java index 086dcb42..1f577b7d 100644 --- a/sdk-core/src/main/java/dev/restate/sdk/core/ExecutorSwitchingSyscalls.java +++ b/sdk-core/src/main/java/dev/restate/sdk/core/ExecutorSwitchingSyscalls.java @@ -10,6 +10,7 @@ import com.google.protobuf.ByteString; import dev.restate.sdk.common.InvocationId; +import dev.restate.sdk.common.Target; import dev.restate.sdk.common.TerminalException; import dev.restate.sdk.common.syscalls.Deferred; import dev.restate.sdk.common.syscalls.EnterSideEffectSyscallCallback; @@ -20,6 +21,7 @@ import java.util.Collection; import java.util.Map; import java.util.concurrent.Executor; +import javax.annotation.Nullable; class ExecutorSwitchingSyscalls implements SyscallsInternal { @@ -83,13 +85,28 @@ public void call( } @Override - public void backgroundCall( + public void call( + Target target, ByteString parameter, SyscallCallback> callback) { + syscallsExecutor.execute(() -> syscalls.call(target, parameter, callback)); + } + + @Override + public void send( + Target target, + ByteString parameter, + @Nullable Duration delay, + SyscallCallback requestCallback) { + syscallsExecutor.execute(() -> syscalls.send(target, parameter, delay, requestCallback)); + } + + @Override + public void send( MethodDescriptor methodDescriptor, T parameter, Duration delay, SyscallCallback requestCallback) { syscallsExecutor.execute( - () -> syscalls.backgroundCall(methodDescriptor, parameter, delay, requestCallback)); + () -> syscalls.send(methodDescriptor, parameter, delay, requestCallback)); } @Override diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java b/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java index cb33dab1..22e60890 100644 --- a/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java +++ b/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java @@ -9,17 +9,14 @@ package dev.restate.sdk.core; import dev.restate.generated.service.discovery.Discovery; -import dev.restate.sdk.common.ServiceAdapter; +import dev.restate.sdk.common.ComponentAdapter; import io.grpc.*; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.Executor; import java.util.function.Function; import java.util.stream.Collectors; @@ -219,38 +216,45 @@ public void setInvocationStatus(String invocationStatus) { void setInvocationStatus(String invocationStatus); } - /** Resolve the code generated {@link ServiceAdapter} */ - public static ServiceAdapter discoverAdapter(Object entity) { - Class userClazz = entity.getClass(); - - // Find Service code-generated class - // TODO This could be done with an SPI - // TODO This should support interfaces - Class serviceAdapterClazz; - try { - serviceAdapterClazz = Class.forName(userClazz.getCanonicalName() + "ServiceAdapter"); - } catch (ClassNotFoundException e) { - throw new RuntimeException( - "Code generated class not found. " - + "Make sure the annotation processor is correctly configured.", - e); + private static class ComponentAdapterSingleton { + private static final ComponentAdapterDiscovery INSTANCE = new ComponentAdapterDiscovery(); + } + + @SuppressWarnings("rawtypes") + private static class ComponentAdapterDiscovery { + + private final List adapters; + + private ComponentAdapterDiscovery() { + this.adapters = + ServiceLoader.load(ComponentAdapter.class).stream() + .map(ServiceLoader.Provider::get) + .collect(Collectors.toList()); } - // Instantiate it - ServiceAdapter serviceAdapter; - try { - //noinspection unchecked - serviceAdapter = (ServiceAdapter) serviceAdapterClazz.getConstructor().newInstance(); - } catch (InstantiationException - | IllegalAccessException - | InvocationTargetException - | NoSuchMethodException e) { - throw new RuntimeException( - "Cannot invoke code generated class constructor. " - + "Make sure the annotation processor is correctly configured.", - e); + private @Nullable ComponentAdapter discoverAdapter(Object service) { + return this.adapters.stream() + .filter(sa -> sa.supportsObject(service)) + .findFirst() + .orElse(null); } + } - return serviceAdapter; + /** Resolve the code generated {@link ComponentAdapter} */ + @SuppressWarnings("unchecked") + public static ComponentAdapter discoverAdapter(Object component) { + return Objects.requireNonNull( + ComponentAdapterSingleton.INSTANCE.discoverAdapter(component), + () -> + "ComponentAdapter class not found for service " + + component.getClass().getCanonicalName() + + ". " + + "Make sure the annotation processor is correctly configured to generate the ComponentAdapter, " + + "and it generates the META-INF/services/" + + ComponentAdapter.class.getCanonicalName() + + " file containing the generated class. " + + "If you're using fat jars, make sure the jar plugin correctly squashes all the META-INF/services files. " + + "Found ComponentAdapter: " + + ComponentAdapterSingleton.INSTANCE.adapters); } } diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/SyscallsImpl.java b/sdk-core/src/main/java/dev/restate/sdk/core/SyscallsImpl.java index ee4a2383..9fe771b2 100644 --- a/sdk-core/src/main/java/dev/restate/sdk/core/SyscallsImpl.java +++ b/sdk-core/src/main/java/dev/restate/sdk/core/SyscallsImpl.java @@ -14,6 +14,7 @@ import dev.restate.generated.service.protocol.Protocol; import dev.restate.generated.service.protocol.Protocol.PollInputStreamEntryMessage; import dev.restate.sdk.common.InvocationId; +import dev.restate.sdk.common.Target; import dev.restate.sdk.common.TerminalException; import dev.restate.sdk.common.syscalls.*; import dev.restate.sdk.core.DeferredResults.SingleDeferredInternal; @@ -174,6 +175,56 @@ public void sleep(Duration duration, SyscallCallback> callback) { callback); } + @Override + public void call( + Target target, ByteString parameter, SyscallCallback> callback) { + wrapAndPropagateExceptions( + () -> { + LOG.trace("call {}", target); + + Protocol.InvokeEntryMessage.Builder builder = + Protocol.InvokeEntryMessage.newBuilder() + .setServiceName(target.getComponent()) + .setMethodName(target.getHandler()) + .setParameter(parameter); + if (target.getKey() != null) { + // TODO add key! + } + + this.stateMachine.processCompletableJournalEntry( + builder.build(), new InvokeEntry<>(Result::success), callback); + }, + callback); + } + + @Override + public void send( + Target target, + ByteString parameter, + @Nullable Duration delay, + SyscallCallback callback) { + wrapAndPropagateExceptions( + () -> { + LOG.trace("backgroundCall {}", target); + + Protocol.BackgroundInvokeEntryMessage.Builder builder = + Protocol.BackgroundInvokeEntryMessage.newBuilder() + .setServiceName(target.getComponent()) + .setMethodName(target.getHandler()) + .setParameter(parameter); + if (target.getKey() != null) { + // TODO add key! + } + if (delay != null) { + builder.setInvokeTime(Instant.now().toEpochMilli() + delay.toMillis()); + } + + this.stateMachine.processJournalEntry( + builder.build(), BackgroundInvokeEntry.INSTANCE, callback); + }, + callback); + } + @Override public void call( MethodDescriptor methodDescriptor, T parameter, SyscallCallback> callback) { @@ -197,7 +248,7 @@ public void call( } @Override - public void backgroundCall( + public void send( MethodDescriptor methodDescriptor, T parameter, @Nullable Duration delay, diff --git a/sdk-core/src/main/resources/json_schema/deployment_manifest_schema.json b/sdk-core/src/main/resources/json_schema/deployment_manifest_schema.json new file mode 100644 index 00000000..80ba68d7 --- /dev/null +++ b/sdk-core/src/main/resources/json_schema/deployment_manifest_schema.json @@ -0,0 +1,59 @@ +{ + "$id": "https://restate.dev/deployment.manifest.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "title": "Deployment", + "description": "Restate deployment manifest", + "properties": { + "protocolMode": { + "title": "ProtocolMode", + "enum": ["BIDI_STREAM", "REQUEST_RESPONSE"] + }, + "minProtocolVersion": { + "type": "integer", + "minimum": 0 + }, + "maxProtocolVersion": { + "type": "integer", + "maximum": 0 + }, + "services": { + "type": "array", + "items": { + "type": "object", + "title": "Service", + "properties": { + "fullyQualifiedServiceName": { + "type": "string", + "pattern": "^[a-zA-Z]+[a-zA-Z0-9._-]*$" + }, + "serviceType": { + "title": "ServiceType", + "enum": ["UNKEYED", "KEYED", "WORKFLOW"] + }, + "methods": { + "type": "array", + "items": { + "type": "object", + "title": "Method", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-zA-Z]+[a-zA-Z0-9_-]*$" + }, + "inputSchema": {}, + "outputSchema": {} + }, + "required": [ "name" ], + "additionalProperties": false + } + } + }, + "required": [ "fullyQualifiedServiceName","serviceType", "methods" ], + "additionalProperties": false + } + } + }, + "required": [ "minProtocolVersion", "maxProtocolVersion", "services" ], + "additionalProperties": false +} \ No newline at end of file diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/ServiceDiscoveryHandlerTest.java b/sdk-core/src/test/java/dev/restate/sdk/core/ComponentDiscoveryHandlerTest.java similarity index 97% rename from sdk-core/src/test/java/dev/restate/sdk/core/ServiceDiscoveryHandlerTest.java rename to sdk-core/src/test/java/dev/restate/sdk/core/ComponentDiscoveryHandlerTest.java index f6a7197a..b10764b5 100644 --- a/sdk-core/src/test/java/dev/restate/sdk/core/ServiceDiscoveryHandlerTest.java +++ b/sdk-core/src/test/java/dev/restate/sdk/core/ComponentDiscoveryHandlerTest.java @@ -17,7 +17,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; -class ServiceDiscoveryHandlerTest { +class ComponentDiscoveryHandlerTest { @Test void handleWithMultipleServices() { diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/TestDefinitions.java b/sdk-core/src/test/java/dev/restate/sdk/core/TestDefinitions.java index 8ba12a2f..3acbd91f 100644 --- a/sdk-core/src/test/java/dev/restate/sdk/core/TestDefinitions.java +++ b/sdk-core/src/test/java/dev/restate/sdk/core/TestDefinitions.java @@ -14,6 +14,7 @@ import com.google.protobuf.MessageLite; import com.google.protobuf.MessageLiteOrBuilder; import dev.restate.generated.service.protocol.Protocol; +import dev.restate.sdk.common.BlockingComponent; import io.grpc.BindableService; import io.grpc.MethodDescriptor; import java.util.*; @@ -22,6 +23,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; +import org.junit.platform.commons.util.Preconditions; public final class TestDefinitions { @@ -58,10 +60,6 @@ public interface TestExecutor { void executeTest(TestDefinition definition); } - public static TestInvocationBuilder testInvocation(BindableService svc, String method) { - return new TestInvocationBuilder(svc, method); - } - public static TestInvocationBuilder testInvocation( BindableService svc, MethodDescriptor method) { return testInvocation(svc, method.getBareMethodName()); @@ -69,11 +67,31 @@ public static TestInvocationBuilder testInvocation( public static TestInvocationBuilder testInvocation( Supplier svc, MethodDescriptor method) { + return testInvocation(svc::get, method.getBareMethodName()); + } + + public static TestInvocationBuilder testInvocation(Supplier svcSupplier, String method) { + Object svc; try { - return testInvocation(svc.get(), method.getBareMethodName()); + svc = svcSupplier.get(); } catch (UnsupportedOperationException e) { return new TestInvocationBuilder(Objects.requireNonNull(e.getMessage())); } + + // If we're testing a gRPC service + if (svc instanceof BindableService) { + return new TestInvocationBuilder((BindableService) svc, method); + } + + // For handler API service + List bundle = RestateEndpoint.discoverAdapter(svc).adapt(svc).components(); + Preconditions.condition( + bundle.size() == 1, "This test infra supports only 1 service, was " + bundle.size()); + return new TestInvocationBuilder(bundle.get(0), method); + } + + private static TestInvocationBuilder testInvocation(BindableService svc, String method) { + return new TestInvocationBuilder(svc, method); } public static class TestInvocationBuilder { diff --git a/sdk-http-vertx/build.gradle.kts b/sdk-http-vertx/build.gradle.kts index 7ff57d35..ac65413a 100644 --- a/sdk-http-vertx/build.gradle.kts +++ b/sdk-http-vertx/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(coreLibs.opentelemetry.api) implementation(coreLibs.log4j.api) implementation("io.reactiverse:reactiverse-contextual-logging:1.1.2") + testImplementation(project(":sdk-api")) testImplementation(project(":sdk-api-kotlin")) testImplementation(project(":sdk-core", "testArchive")) diff --git a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java index a5e0755a..faa6628f 100644 --- a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java +++ b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java @@ -9,9 +9,9 @@ package dev.restate.sdk.http.vertx; import dev.restate.generated.service.discovery.Discovery; -import dev.restate.sdk.common.BlockingService; -import dev.restate.sdk.common.NonBlockingService; -import dev.restate.sdk.common.ServiceAdapter; +import dev.restate.sdk.common.BlockingComponent; +import dev.restate.sdk.common.ComponentAdapter; +import dev.restate.sdk.common.NonBlockingComponent; import dev.restate.sdk.core.RestateEndpoint; import io.grpc.ServerInterceptor; import io.grpc.ServerInterceptors; @@ -77,26 +77,26 @@ public RestateHttpEndpointBuilder withOptions(HttpServerOptions options) { } /** - * Add a {@link BlockingService} to the endpoint. + * Add a {@link BlockingComponent} to the endpoint. * *

NOTE: The service code will run within the Vert.x worker thread pool. For more details, * check the Vert.x * documentation. */ public RestateHttpEndpointBuilder withService( - BlockingService service, ServerInterceptor... interceptors) { + BlockingComponent service, ServerInterceptor... interceptors) { return this.withService(service, defaultExecutor, interceptors); } /** - * Add a {@link BlockingService} to the endpoint, specifying the {@code executor} where to run the - * service code. + * Add a {@link BlockingComponent} to the endpoint, specifying the {@code executor} where to run + * the service code. * *

You can run on virtual threads by using the executor {@code * Executors.newVirtualThreadPerTaskExecutor()}. */ public RestateHttpEndpointBuilder withService( - BlockingService service, Executor executor, ServerInterceptor... interceptors) { + BlockingComponent service, Executor executor, ServerInterceptor... interceptors) { ServerServiceDefinition definition = ServerInterceptors.intercept(service, Arrays.asList(interceptors)); this.restateGrpcServerBuilder.withService(definition); @@ -105,14 +105,14 @@ public RestateHttpEndpointBuilder withService( } /** - * Add a {@link NonBlockingService} to the endpoint. + * Add a {@link NonBlockingComponent} to the endpoint. * *

NOTE: The service code will run within the same Vert.x event loop thread handling the HTTP * stream, hence the code should never block the thread. For more details, check the Vert.x documentation. */ public RestateHttpEndpointBuilder withService( - NonBlockingService service, ServerInterceptor... interceptors) { + NonBlockingComponent service, ServerInterceptor... interceptors) { this.restateGrpcServerBuilder.withService( ServerInterceptors.intercept(service, Arrays.asList(interceptors))); return this; @@ -121,7 +121,7 @@ public RestateHttpEndpointBuilder withService( /** * Add a Restate service to the endpoint. This will automatically discover the adapter based on * the class name. You can provide the adapter manually using {@link #with(Object, - * ServiceAdapter)} + * ComponentAdapter)} */ public RestateHttpEndpointBuilder with(Object service) { return this.with(service, defaultExecutor); @@ -130,7 +130,7 @@ public RestateHttpEndpointBuilder with(Object service) { /** * Add a Restate service to the endpoint, specifying the {@code executor} where to run the service * code. This will automatically discover the adapter based on the class name. You can provide the - * adapter manually using {@link #with(Object, ServiceAdapter, Executor)} + * adapter manually using {@link #with(Object, ComponentAdapter, Executor)} * *

You can run on virtual threads by using the executor {@code * Executors.newVirtualThreadPerTaskExecutor()}. @@ -140,7 +140,7 @@ public RestateHttpEndpointBuilder with(Object service, Executor executor) { } /** Add a Restate service to the endpoint, specifying an adapter. */ - public RestateHttpEndpointBuilder with(T service, ServiceAdapter adapter) { + public RestateHttpEndpointBuilder with(T service, ComponentAdapter adapter) { return this.with(service, adapter, defaultExecutor); } @@ -152,9 +152,9 @@ public RestateHttpEndpointBuilder with(T service, ServiceAdapter adapter) * Executors.newVirtualThreadPerTaskExecutor()}. */ public RestateHttpEndpointBuilder with( - T service, ServiceAdapter adapter, Executor executor) { - List services = adapter.adapt(service).services(); - for (BlockingService svc : services) { + T service, ComponentAdapter adapter, Executor executor) { + List services = adapter.adapt(service).components(); + for (BlockingComponent svc : services) { this.withService(svc, executor); } diff --git a/sdk-http-vertx/src/test/java/dev/restate/sdk/http/vertx/testservices/BlockingGreeterService.java b/sdk-http-vertx/src/test/java/dev/restate/sdk/http/vertx/testservices/BlockingGreeterService.java index 61a73da4..cb0c07e3 100644 --- a/sdk-http-vertx/src/test/java/dev/restate/sdk/http/vertx/testservices/BlockingGreeterService.java +++ b/sdk-http-vertx/src/test/java/dev/restate/sdk/http/vertx/testservices/BlockingGreeterService.java @@ -8,8 +8,8 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.http.vertx.testservices; -import dev.restate.sdk.KeyedContext; -import dev.restate.sdk.RestateService; +import dev.restate.sdk.Component; +import dev.restate.sdk.ObjectContext; import dev.restate.sdk.common.CoreSerdes; import dev.restate.sdk.common.StateKey; import dev.restate.sdk.core.testservices.GreeterGrpc; @@ -20,7 +20,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public class BlockingGreeterService extends GreeterGrpc.GreeterImplBase implements RestateService { +public class BlockingGreeterService extends GreeterGrpc.GreeterImplBase implements Component { private static final Logger LOG = LogManager.getLogger(BlockingGreeterService.class); public static final StateKey COUNTER = StateKey.of("counter", CoreSerdes.JSON_LONG); @@ -31,10 +31,10 @@ public void greet(GreetingRequest request, StreamObserver resp LOG.info("Greet invoked!"); - var count = KeyedContext.current().get(COUNTER).orElse(0L) + 1; - KeyedContext.current().set(COUNTER, count); + var count = ObjectContext.current().get(COUNTER).orElse(0L) + 1; + ObjectContext.current().set(COUNTER, count); - KeyedContext.current().sleep(Duration.ofSeconds(1)); + ObjectContext.current().sleep(Duration.ofSeconds(1)); responseObserver.onNext( GreetingResponse.newBuilder() diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt index 301fb534..0bbc7b38 100644 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt +++ b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt @@ -9,8 +9,8 @@ package dev.restate.sdk.http.vertx import com.google.protobuf.MessageLite -import dev.restate.sdk.common.BlockingService -import dev.restate.sdk.common.NonBlockingService +import dev.restate.sdk.common.BlockingComponent +import dev.restate.sdk.common.NonBlockingComponent import dev.restate.sdk.core.TestDefinitions.TestDefinition import dev.restate.sdk.core.TestDefinitions.TestExecutor import io.vertx.core.Vertx @@ -18,7 +18,7 @@ import io.vertx.core.buffer.Buffer import io.vertx.core.http.HttpHeaders import io.vertx.core.http.HttpMethod import io.vertx.core.http.HttpServerOptions -import io.vertx.kotlin.coroutines.await +import io.vertx.kotlin.coroutines.coAwait import io.vertx.kotlin.coroutines.dispatcher import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow @@ -38,18 +38,18 @@ class HttpVertxTestExecutor(private val vertx: Vertx) : TestExecutor { val builder = RestateHttpEndpointBuilder.builder(vertx).withOptions(HttpServerOptions().setPort(0)) when (definition.service) { - is BlockingService -> { - builder.withService(definition.service as BlockingService) + is BlockingComponent -> { + builder.withService(definition.service as BlockingComponent) } - is NonBlockingService -> { - builder.withService(definition.service as NonBlockingService) + is NonBlockingComponent -> { + builder.withService(definition.service as NonBlockingComponent) } else -> { throw IllegalStateException("Unexpected service class " + definition.service) } } val server = builder.build() - server.listen().await() + server.listen().coAwait() val client = vertx.createHttpClient(RestateHttpEndpointTest.HTTP_CLIENT_OPTIONS) @@ -60,25 +60,25 @@ class HttpVertxTestExecutor(private val vertx: Vertx) : TestExecutor { server.actualPort(), "localhost", "/invoke/${definition.service.bindService().serviceDescriptor.name}/${definition.method}") - .await() + .coAwait() // Prepare request header and send them request.setChunked(true).putHeader(HttpHeaders.CONTENT_TYPE, "application/restate") - request.sendHead().await() + request.sendHead().coAwait() launch { for (msg in definition.input) { val buffer = Buffer.buffer(MessageEncoder.encodeLength(msg.message())) buffer.appendLong(msg.header().encode()) buffer.appendBytes(msg.message().toByteArray()) - request.write(buffer).await() + request.write(buffer).coAwait() yield() } - request.end().await() + request.end().coAwait() } - val response = request.response().await() + val response = request.response().coAwait() // Start the coroutine to send input messages @@ -100,7 +100,7 @@ class HttpVertxTestExecutor(private val vertx: Vertx) : TestExecutor { definition.outputAssert.accept(messages) // Close the server - server.close().await() + server.close().coAwait() } } } diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTests.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTests.kt index 062bcb49..b86e0738 100644 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTests.kt +++ b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTests.kt @@ -10,16 +10,15 @@ package dev.restate.sdk.http.vertx import com.google.protobuf.ByteString import dev.restate.generated.sdk.java.Java.SideEffectEntryMessage +import dev.restate.sdk.Component import dev.restate.sdk.JavaBlockingTests -import dev.restate.sdk.RestateService import dev.restate.sdk.core.ProtoUtils.* import dev.restate.sdk.core.TestDefinitions.* import dev.restate.sdk.core.testservices.GreeterGrpc import dev.restate.sdk.core.testservices.GreetingRequest import dev.restate.sdk.core.testservices.GreetingResponse -import dev.restate.sdk.kotlin.KeyedContext import dev.restate.sdk.kotlin.KotlinCoroutinesTests -import dev.restate.sdk.kotlin.RestateKtService +import dev.restate.sdk.kotlin.RestateKtComponent import io.grpc.stub.StreamObserver import io.vertx.core.Vertx import java.util.stream.Stream @@ -46,27 +45,29 @@ class HttpVertxTests : dev.restate.sdk.core.TestRunner() { } class VertxExecutorsTest : TestSuite { - private class CheckNonBlockingServiceTrampolineEventLoopContext : + private class CheckNonBlockingComponentTrampolineEventLoopContext : dev.restate.sdk.core.testservices.GreeterGrpcKt.GreeterCoroutineImplBase( Dispatchers.Unconfined), - RestateKtService { + RestateKtComponent { override suspend fun greet(request: GreetingRequest): GreetingResponse { check(Vertx.currentContext().isEventLoopContext) - KeyedContext.current().sideEffect { check(Vertx.currentContext().isEventLoopContext) } + dev.restate.sdk.kotlin.ObjectContext.current().sideEffect { + check(Vertx.currentContext().isEventLoopContext) + } check(Vertx.currentContext().isEventLoopContext) return GreetingResponse.getDefaultInstance() } } - private class CheckBlockingServiceTrampolineExecutor : - GreeterGrpc.GreeterImplBase(), RestateService { + private class CheckBlockingComponentTrampolineExecutor : + GreeterGrpc.GreeterImplBase(), Component { override fun greet( request: GreetingRequest, responseObserver: StreamObserver ) { val id = Thread.currentThread().id check(Vertx.currentContext() == null) - dev.restate.sdk.KeyedContext.current().sideEffect { + dev.restate.sdk.ObjectContext.current().sideEffect { check(Thread.currentThread().id == id) check(Vertx.currentContext() == null) } @@ -80,7 +81,8 @@ class HttpVertxTests : dev.restate.sdk.core.TestRunner() { override fun definitions(): Stream { return Stream.of( testInvocation( - CheckNonBlockingServiceTrampolineEventLoopContext(), GreeterGrpc.getGreetMethod()) + CheckNonBlockingComponentTrampolineEventLoopContext(), + GreeterGrpc.getGreetMethod()) .withInput( startMessage(1), inputMessage(GreetingRequest.getDefaultInstance()), @@ -90,7 +92,7 @@ class HttpVertxTests : dev.restate.sdk.core.TestRunner() { SideEffectEntryMessage.newBuilder().setValue(ByteString.EMPTY), outputMessage(GreetingResponse.getDefaultInstance()), END_MESSAGE), - testInvocation(CheckBlockingServiceTrampolineExecutor(), GreeterGrpc.getGreetMethod()) + testInvocation(CheckBlockingComponentTrampolineExecutor(), GreeterGrpc.getGreetMethod()) .withInput( startMessage(1), inputMessage(GreetingRequest.getDefaultInstance()), diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt index 5671b32c..7f54ce35 100644 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt +++ b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt @@ -18,14 +18,14 @@ import dev.restate.sdk.common.CoreSerdes import dev.restate.sdk.core.ProtoUtils.* import dev.restate.sdk.core.testservices.* import dev.restate.sdk.http.vertx.testservices.BlockingGreeterService -import dev.restate.sdk.http.vertx.testservices.GreeterKtService +import dev.restate.sdk.http.vertx.testservices.GreeterKtComponent import io.netty.handler.codec.http.HttpResponseStatus import io.vertx.core.Vertx import io.vertx.core.buffer.Buffer import io.vertx.core.http.* import io.vertx.junit5.Timeout import io.vertx.junit5.VertxExtension -import io.vertx.kotlin.coroutines.await +import io.vertx.kotlin.coroutines.coAwait import io.vertx.kotlin.coroutines.dispatcher import io.vertx.kotlin.coroutines.receiveChannelHandler import java.util.concurrent.* @@ -50,7 +50,7 @@ internal class RestateHttpEndpointTest { @Timeout(value = 1, timeUnit = TimeUnit.SECONDS) @Test fun endpointWithNonBlockingService(vertx: Vertx): Unit = - greetTest(vertx) { it.withService(GreeterKtService(coroutineContext = vertx.dispatcher())) } + greetTest(vertx) { it.withService(GreeterKtComponent(coroutineContext = vertx.dispatcher())) } @Timeout(value = 1, timeUnit = TimeUnit.SECONDS) @Test @@ -70,7 +70,7 @@ internal class RestateHttpEndpointTest { .withOptions(HttpServerOptions().setPort(0)) .build() .listen() - .await() + .coAwait() .actualPort() val client = vertx.createHttpClient(HTTP_CLIENT_OPTIONS) @@ -84,7 +84,7 @@ internal class RestateHttpEndpointTest { "/invoke/" + dev.restate.sdk.core.testservices.GreeterGrpc.getGreetMethod() .fullMethodName) - .await() + .coAwait() // Prepare request header request.setChunked(true).putHeader(HttpHeaders.CONTENT_TYPE, "application/restate") @@ -93,7 +93,7 @@ internal class RestateHttpEndpointTest { request.write(encode(startMessage(1).build())) request.write(encode(inputMessage(greetingRequest { name = "Francesco" }))) - val response = request.response().await() + val response = request.response().coAwait() // Start the input decoder val inputChannel = vertx.receiveChannelHandler() @@ -153,7 +153,7 @@ internal class RestateHttpEndpointTest { OutputStreamEntryMessage::getValue) // Wait for closing request and response - request.end().await() + request.end().coAwait() } @Test @@ -165,7 +165,7 @@ internal class RestateHttpEndpointTest { .withOptions(HttpServerOptions().setPort(0)) .build() .listen() - .await() + .coAwait() .actualPort() val client = vertx.createHttpClient(HTTP_CLIENT_OPTIONS) @@ -179,18 +179,18 @@ internal class RestateHttpEndpointTest { "/invoke/" + dev.restate.sdk.core.testservices.GreeterGrpc.getGreetMethod().serviceName + "/unknownMethod") - .await() + .coAwait() // Prepare request header request.setChunked(true).putHeader(HttpHeaders.CONTENT_TYPE, "application/restate") request.write(encode(startMessage(0).build())) - val response = request.response().await() + val response = request.response().coAwait() // Response status should be 404 assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.NOT_FOUND.code()) - response.end().await() + response.end().coAwait() } @Test @@ -202,28 +202,28 @@ internal class RestateHttpEndpointTest { .withOptions(HttpServerOptions().setPort(0)) .build() .listen() - .await() + .coAwait() .actualPort() val client = vertx.createHttpClient(HTTP_CLIENT_OPTIONS) // Send request val request = - client.request(HttpMethod.POST, endpointPort, "localhost", "/discover").await() + client.request(HttpMethod.POST, endpointPort, "localhost", "/discover").coAwait() request .putHeader(HttpHeaders.CONTENT_TYPE, "application/proto") .end(Buffer.buffer(ServiceDiscoveryRequest.getDefaultInstance().toByteArray())) - .await() + .coAwait() // Assert response - val response = request.response().await() + val response = request.response().coAwait() // Response status and content type header assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code()) assertThat(response.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo("application/proto") // Parse response - val responseBody = response.body().await() + val responseBody = response.body().coAwait() val serviceDiscoveryResponse = ServiceDiscoveryResponse.parseFrom(responseBody.bytes) assertThat(serviceDiscoveryResponse.servicesList) .containsOnly(dev.restate.sdk.core.testservices.GreeterGrpc.SERVICE_NAME) diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/testservices/GreeterKtService.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/testservices/GreeterKtComponent.kt similarity index 69% rename from sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/testservices/GreeterKtService.kt rename to sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/testservices/GreeterKtComponent.kt index fee9a8ae..a7c6d9c1 100644 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/testservices/GreeterKtService.kt +++ b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/testservices/GreeterKtComponent.kt @@ -12,24 +12,24 @@ import dev.restate.sdk.core.testservices.GreeterGrpcKt import dev.restate.sdk.core.testservices.GreetingRequest import dev.restate.sdk.core.testservices.GreetingResponse import dev.restate.sdk.core.testservices.greetingResponse -import dev.restate.sdk.kotlin.KeyedContext -import dev.restate.sdk.kotlin.RestateKtService +import dev.restate.sdk.kotlin.ObjectContext +import dev.restate.sdk.kotlin.RestateKtComponent import kotlin.coroutines.CoroutineContext import kotlin.time.Duration.Companion.seconds import org.apache.logging.log4j.LogManager -class GreeterKtService(coroutineContext: CoroutineContext) : - GreeterGrpcKt.GreeterCoroutineImplBase(coroutineContext), RestateKtService { +class GreeterKtComponent(coroutineContext: CoroutineContext) : + GreeterGrpcKt.GreeterCoroutineImplBase(coroutineContext), RestateKtComponent { - private val LOG = LogManager.getLogger(GreeterKtService::class.java) + private val LOG = LogManager.getLogger(GreeterKtComponent::class.java) override suspend fun greet(request: GreetingRequest): GreetingResponse { LOG.info("Greet invoked!") - val count = (KeyedContext.current().get(BlockingGreeterService.COUNTER) ?: 0) + 1 - KeyedContext.current().set(BlockingGreeterService.COUNTER, count) + val count = (ObjectContext.current().get(BlockingGreeterService.COUNTER) ?: 0) + 1 + ObjectContext.current().set(BlockingGreeterService.COUNTER, count) - KeyedContext.current().sleep(1.seconds) + ObjectContext.current().sleep(1.seconds) return greetingResponse { message = "Hello ${request.name}. Count: $count" } } diff --git a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java index 57520553..31bdcb83 100644 --- a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java +++ b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java @@ -9,9 +9,9 @@ package dev.restate.sdk.lambda; import dev.restate.generated.service.discovery.Discovery; -import dev.restate.sdk.common.BlockingService; -import dev.restate.sdk.common.Service; -import dev.restate.sdk.common.ServiceAdapter; +import dev.restate.sdk.common.BlockingComponent; +import dev.restate.sdk.common.Component; +import dev.restate.sdk.common.ComponentAdapter; import dev.restate.sdk.core.RestateEndpoint; import io.grpc.ServerInterceptor; import io.grpc.ServerInterceptors; @@ -28,14 +28,14 @@ public final class RestateLambdaEndpointBuilder { private OpenTelemetry openTelemetry = OpenTelemetry.noop(); /** - * Add a {@link Service} to the endpoint. + * Add a {@link Component} to the endpoint. * *

The service code will be executed on the same thread where the lambda is invoked. */ public RestateLambdaEndpointBuilder withService( - Service service, ServerInterceptor... interceptors) { + Component component, ServerInterceptor... interceptors) { ServerServiceDefinition definition = - ServerInterceptors.intercept(service, Arrays.asList(interceptors)); + ServerInterceptors.intercept(component, Arrays.asList(interceptors)); this.restateGrpcServerBuilder.withService(definition); return this; } @@ -48,9 +48,9 @@ public RestateLambdaEndpointBuilder with(Object service) { return this.with(service, RestateEndpoint.discoverAdapter(service)); } - public RestateLambdaEndpointBuilder with(T service, ServiceAdapter adapter) { - List services = adapter.adapt(service).services(); - for (Service svc : services) { + public RestateLambdaEndpointBuilder with(T service, ComponentAdapter adapter) { + List services = adapter.adapt(service).components(); + for (Component svc : services) { this.restateGrpcServerBuilder.withService(svc); } diff --git a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/JavaCounterService.java b/sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/JavaCounterService.java index 6ad49672..e07ab27c 100644 --- a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/JavaCounterService.java +++ b/sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/JavaCounterService.java @@ -8,15 +8,14 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.lambda.testservices; -import dev.restate.sdk.KeyedContext; -import dev.restate.sdk.RestateService; +import dev.restate.sdk.Component; +import dev.restate.sdk.ObjectContext; import dev.restate.sdk.common.Serde; import dev.restate.sdk.common.StateKey; import io.grpc.stub.StreamObserver; import java.nio.charset.StandardCharsets; -public class JavaCounterService extends JavaCounterGrpc.JavaCounterImplBase - implements RestateService { +public class JavaCounterService extends JavaCounterGrpc.JavaCounterImplBase implements Component { public static final StateKey COUNTER = StateKey.of( @@ -27,7 +26,7 @@ public class JavaCounterService extends JavaCounterGrpc.JavaCounterImplBase @Override public void get(CounterRequest request, StreamObserver responseObserver) { - KeyedContext.current().get(COUNTER); + ObjectContext.current().get(COUNTER); throw new IllegalStateException("We shouldn't reach this point"); } diff --git a/sdk-lambda/src/test/kotlin/dev/restate/sdk/lambda/testservices/KotlinCounterService.kt b/sdk-lambda/src/test/kotlin/dev/restate/sdk/lambda/testservices/KotlinCounterService.kt index 3508752f..bbbd5dc6 100644 --- a/sdk-lambda/src/test/kotlin/dev/restate/sdk/lambda/testservices/KotlinCounterService.kt +++ b/sdk-lambda/src/test/kotlin/dev/restate/sdk/lambda/testservices/KotlinCounterService.kt @@ -8,16 +8,28 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.lambda.testservices -import dev.restate.sdk.kotlin.KeyedContext -import dev.restate.sdk.kotlin.RestateKtService +import dev.restate.sdk.common.Serde +import dev.restate.sdk.common.StateKey +import dev.restate.sdk.kotlin.ObjectContext +import dev.restate.sdk.kotlin.RestateKtComponent +import java.nio.charset.StandardCharsets import kotlinx.coroutines.Dispatchers class KotlinCounterService : KotlinCounterGrpcKt.KotlinCounterCoroutineImplBase(coroutineContext = Dispatchers.Unconfined), - RestateKtService { + RestateKtComponent { + + companion object { + private val COUNTER: StateKey = + StateKey.of( + "counter", + Serde.using( + { l: Long -> l.toString().toByteArray(StandardCharsets.UTF_8) }, + { v: ByteArray? -> String(v!!, StandardCharsets.UTF_8).toLong() })) + } override suspend fun get(request: CounterRequest): GetResponse { - (KeyedContext.current().get(JavaCounterService.COUNTER) ?: 0) + 1 + (ObjectContext.current().get(COUNTER) ?: 0) + 1 throw IllegalStateException("We shouldn't reach this point") } diff --git a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunnerBuilder.java b/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunnerBuilder.java index acebb30f..157a4f25 100644 --- a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunnerBuilder.java +++ b/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunnerBuilder.java @@ -8,9 +8,9 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.testing; -import dev.restate.sdk.common.BlockingService; -import dev.restate.sdk.common.NonBlockingService; -import dev.restate.sdk.common.ServiceAdapter; +import dev.restate.sdk.common.BlockingComponent; +import dev.restate.sdk.common.ComponentAdapter; +import dev.restate.sdk.common.NonBlockingComponent; import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder; import io.grpc.ServerInterceptor; import java.util.HashMap; @@ -49,31 +49,31 @@ public RestateRunnerBuilder withConfigFile(String configFile) { } /** - * Register a service. See {@link RestateHttpEndpointBuilder#withService(BlockingService, + * Register a service. See {@link RestateHttpEndpointBuilder#withService(BlockingComponent, * ServerInterceptor...)}. */ public RestateRunnerBuilder withService( - BlockingService service, ServerInterceptor... interceptors) { + BlockingComponent service, ServerInterceptor... interceptors) { this.endpointBuilder.withService(service, interceptors); return this; } /** - * Register a service. See {@link RestateHttpEndpointBuilder#withService(BlockingService, + * Register a service. See {@link RestateHttpEndpointBuilder#withService(BlockingComponent, * Executor, ServerInterceptor...)}. */ public RestateRunnerBuilder withService( - BlockingService service, Executor executor, ServerInterceptor... interceptors) { + BlockingComponent service, Executor executor, ServerInterceptor... interceptors) { this.endpointBuilder.withService(service, executor, interceptors); return this; } /** - * Register a service. See {@link RestateHttpEndpointBuilder#withService(NonBlockingService, + * Register a service. See {@link RestateHttpEndpointBuilder#withService(NonBlockingComponent, * ServerInterceptor...)}. */ public RestateRunnerBuilder withService( - NonBlockingService service, ServerInterceptor... interceptors) { + NonBlockingComponent service, ServerInterceptor... interceptors) { this.endpointBuilder.withService(service, interceptors); return this; } @@ -81,7 +81,7 @@ public RestateRunnerBuilder withService( /** * Add a Restate service to the endpoint. This will automatically discover the adapter based on * the class name. You can provide the adapter manually using {@link #with(Object, - * ServiceAdapter)} + * ComponentAdapter)} */ public RestateRunnerBuilder with(Object service) { this.endpointBuilder.with(service); @@ -91,7 +91,7 @@ public RestateRunnerBuilder with(Object service) { /** * Add a Restate service to the endpoint, specifying the {@code executor} where to run the service * code. This will automatically discover the adapter based on the class name. You can provide the - * adapter manually using {@link #with(Object, ServiceAdapter, Executor)} + * adapter manually using {@link #with(Object, ComponentAdapter, Executor)} * *

You can run on virtual threads by using the executor {@code * Executors.newVirtualThreadPerTaskExecutor()}. @@ -102,7 +102,7 @@ public RestateRunnerBuilder with(Object service, Executor executor) { } /** Add a Restate service to the endpoint, specifying an adapter. */ - public RestateRunnerBuilder with(T service, ServiceAdapter adapter) { + public RestateRunnerBuilder with(T service, ComponentAdapter adapter) { this.endpointBuilder.with(service, adapter); return this; } @@ -114,7 +114,7 @@ public RestateRunnerBuilder with(T service, ServiceAdapter adapter) { *

You can run on virtual threads by using the executor {@code * Executors.newVirtualThreadPerTaskExecutor()}. */ - public RestateRunnerBuilder with(T service, ServiceAdapter adapter, Executor executor) { + public RestateRunnerBuilder with(T service, ComponentAdapter adapter, Executor executor) { this.endpointBuilder.with(service, adapter, executor); return this; } diff --git a/sdk-testing/src/test/java/dev/restate/sdk/testing/Counter.java b/sdk-testing/src/test/java/dev/restate/sdk/testing/Counter.java index 3f186ea9..78c91d32 100644 --- a/sdk-testing/src/test/java/dev/restate/sdk/testing/Counter.java +++ b/sdk-testing/src/test/java/dev/restate/sdk/testing/Counter.java @@ -9,8 +9,8 @@ package dev.restate.sdk.testing; import com.google.protobuf.Empty; -import dev.restate.sdk.KeyedContext; -import dev.restate.sdk.RestateService; +import dev.restate.sdk.Component; +import dev.restate.sdk.ObjectContext; import dev.restate.sdk.common.CoreSerdes; import dev.restate.sdk.common.StateKey; import dev.restate.sdk.examples.generated.*; @@ -18,7 +18,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public class Counter extends CounterGrpc.CounterImplBase implements RestateService { +public class Counter extends CounterGrpc.CounterImplBase implements Component { private static final Logger LOG = LogManager.getLogger(Counter.class); @@ -26,7 +26,7 @@ public class Counter extends CounterGrpc.CounterImplBase implements RestateServi @Override public void reset(CounterRequest request, StreamObserver responseObserver) { - KeyedContext.current().clear(TOTAL); + ObjectContext.current().clear(TOTAL); responseObserver.onNext(Empty.getDefaultInstance()); responseObserver.onCompleted(); @@ -34,7 +34,7 @@ public void reset(CounterRequest request, StreamObserver responseObserver @Override public void add(CounterAddRequest request, StreamObserver responseObserver) { - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); long currentValue = ctx.get(TOTAL).orElse(0L); long newValue = currentValue + request.getValue(); @@ -46,7 +46,7 @@ public void add(CounterAddRequest request, StreamObserver responseObserve @Override public void get(CounterRequest request, StreamObserver responseObserver) { - long currentValue = KeyedContext.current().get(TOTAL).orElse(0L); + long currentValue = ObjectContext.current().get(TOTAL).orElse(0L); responseObserver.onNext(GetResponse.newBuilder().setValue(currentValue).build()); responseObserver.onCompleted(); @@ -57,7 +57,7 @@ public void getAndAdd( CounterAddRequest request, StreamObserver responseObserver) { LOG.info("Invoked get and add with " + request.getValue()); - KeyedContext ctx = KeyedContext.current(); + ObjectContext ctx = ObjectContext.current(); long currentValue = ctx.get(TOTAL).orElse(0L); long newValue = currentValue + request.getValue(); diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DescriptorUtils.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DescriptorUtils.java index 96c1edf9..ee0d675f 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DescriptorUtils.java +++ b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DescriptorUtils.java @@ -53,9 +53,4 @@ public Descriptors.MethodDescriptor getMethodDescriptor() { return getServiceDescriptor().findMethodByName(methodName); } } - - static String toMethodName(String methodName) { - return Character.toString(Character.toUpperCase(methodName.codePointAt(0))) - + methodName.substring(1); - } } diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DurablePromiseHandleImpl.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DurablePromiseHandleImpl.java index 9d880f9a..0758515f 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DurablePromiseHandleImpl.java +++ b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DurablePromiseHandleImpl.java @@ -9,7 +9,7 @@ package dev.restate.sdk.workflow.impl; import com.google.protobuf.Empty; -import dev.restate.sdk.KeyedContext; +import dev.restate.sdk.ObjectContext; import dev.restate.sdk.workflow.DurablePromiseHandle; import dev.restate.sdk.workflow.DurablePromiseKey; import dev.restate.sdk.workflow.generated.CompleteDurablePromiseRequest; @@ -22,12 +22,12 @@ public final class DurablePromiseHandleImpl implements DurablePromiseHandle workflowManagerCompleteDurablePromise; - private final KeyedContext ctx; + private final ObjectContext ctx; private final DurablePromiseKey key; DurablePromiseHandleImpl( String workflowKey, - KeyedContext ctx, + ObjectContext ctx, MethodDescriptor workflowManagerCompleteDurablePromise, DurablePromiseKey key) { this.workflowKey = workflowKey; diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DurablePromiseImpl.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DurablePromiseImpl.java index 78231c79..de0ea2b6 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DurablePromiseImpl.java +++ b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/DurablePromiseImpl.java @@ -11,7 +11,7 @@ import com.google.protobuf.Empty; import dev.restate.sdk.Awaitable; import dev.restate.sdk.Awakeable; -import dev.restate.sdk.KeyedContext; +import dev.restate.sdk.ObjectContext; import dev.restate.sdk.common.TerminalException; import dev.restate.sdk.workflow.DurablePromise; import dev.restate.sdk.workflow.DurablePromiseKey; @@ -22,7 +22,7 @@ public final class DurablePromiseImpl implements DurablePromise { private final String workflowKey; - private final KeyedContext ctx; + private final ObjectContext ctx; private final DurablePromiseKey key; private final Awaitable awakeable; @@ -31,7 +31,7 @@ public final class DurablePromiseImpl implements DurablePromise { private DurablePromiseImpl( String workflowKey, - KeyedContext ctx, + ObjectContext ctx, DurablePromiseKey key, Awaitable awakeable, MethodDescriptor @@ -89,7 +89,7 @@ public boolean isCompleted() { static DurablePromise prepare( String workflowKey, - KeyedContext ctx, + ObjectContext ctx, DurablePromiseKey key, MethodDescriptor workflowManagerWaitDurablePromiseCompletion, diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowCodegenUtil.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowCodegenUtil.java index e8306701..1bb0e026 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowCodegenUtil.java +++ b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowCodegenUtil.java @@ -11,21 +11,19 @@ import static io.grpc.stub.ClientCalls.blockingUnaryCall; import com.google.protobuf.Empty; -import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Value; -import com.google.protobuf.util.JsonFormat; import dev.restate.generated.IngressGrpc; import dev.restate.sdk.Awaitable; import dev.restate.sdk.Context; import dev.restate.sdk.common.Serde; import dev.restate.sdk.common.StateKey; import dev.restate.sdk.common.TerminalException; +import dev.restate.sdk.dynrpc.CodegenUtils; import dev.restate.sdk.workflow.generated.*; import dev.restate.sdk.workflow.template.generated.WorkflowGrpc; import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.MethodDescriptor; -import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Objects; import java.util.Optional; @@ -36,27 +34,6 @@ public class WorkflowCodegenUtil { private WorkflowCodegenUtil() {} - public static T valueToT(Serde serde, Value value) { - String reqAsString; - try { - reqAsString = JsonFormat.printer().print(value); - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(e); - } - return serde.deserialize(reqAsString.getBytes(StandardCharsets.UTF_8)); - } - - public static Value tToValue(Serde serde, T value) { - String resAsString = serde.serializeToByteString(value).toStringUtf8(); - Value.Builder outValueBuilder = Value.newBuilder(); - try { - JsonFormat.parser().merge(resAsString, outValueBuilder); - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(e); - } - return outValueBuilder.build(); - } - // -- Restate client methods public static class RestateClient { @@ -90,7 +67,7 @@ public static Awaitable> getOutput( TerminalException.Code.fromValue(response.getFailure().getCode()), response.getFailure().getMessage()); } - return Optional.ofNullable(valueToT(serde, response.getValue())); + return Optional.ofNullable(CodegenUtils.valueToT(serde, response.getValue())); }); } @@ -206,7 +183,7 @@ public static Optional getOutput( TerminalException.Code.fromValue(response.getFailure().getCode()), response.getFailure().getMessage()); } - return Optional.ofNullable(valueToT(serde, response.getValue())); + return Optional.ofNullable(CodegenUtils.valueToT(serde, response.getValue())); } public static boolean isCompleted( @@ -292,9 +269,7 @@ public static MethodDescriptor generateMethodDescriptorForW public static MethodDescriptor generateMethodDescriptorForWorkflow( MethodDescriptor original, String workflowFqsn, String methodName) { return original.toBuilder() - .setFullMethodName( - MethodDescriptor.generateFullMethodName( - workflowFqsn, DescriptorUtils.toMethodName(methodName))) + .setFullMethodName(MethodDescriptor.generateFullMethodName(workflowFqsn, methodName)) .build(); } diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowServicesBundle.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowComponentBundle.java similarity index 59% rename from sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowServicesBundle.java rename to sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowComponentBundle.java index 0031d8cd..63a3121c 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowServicesBundle.java +++ b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowComponentBundle.java @@ -8,12 +8,10 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.workflow.impl; -import static dev.restate.sdk.workflow.impl.DescriptorUtils.toMethodName; - import dev.restate.sdk.Context; -import dev.restate.sdk.common.BlockingService; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.ServicesBundle; +import dev.restate.sdk.common.BlockingComponent; +import dev.restate.sdk.common.ComponentBundle; +import dev.restate.sdk.dynrpc.JavaComponent; import dev.restate.sdk.workflow.WorkflowContext; import dev.restate.sdk.workflow.WorkflowSharedContext; import java.util.HashMap; @@ -22,15 +20,15 @@ import java.util.function.BiFunction; import javax.annotation.Nullable; -public class WorkflowServicesBundle implements ServicesBundle { +public class WorkflowComponentBundle implements ComponentBundle { private final String name; - private final MethodSignature sig; + private final JavaComponent.HandlerSignature sig; private final BiFunction runner; private final HashMap> sharedMethods; - public WorkflowServicesBundle( + public WorkflowComponentBundle( String name, - MethodSignature sig, + JavaComponent.HandlerSignature sig, BiFunction runner, HashMap> sharedMethods) { this.name = name; @@ -39,7 +37,7 @@ public WorkflowServicesBundle( this.sharedMethods = sharedMethods; } - public MethodSignature getSig() { + public JavaComponent.HandlerSignature getSig() { return sig; } @@ -72,7 +70,7 @@ public String getSimpleName() { } @Override - public List services() { + public List components() { WorkflowMangledDescriptors workflowMangledDescriptors = WorkflowMangledDescriptors.mangle(this); return List.of( @@ -84,18 +82,22 @@ public List services() { } public static Builder named( - String name, MethodSignature sig, BiFunction runner) { + String name, + JavaComponent.HandlerSignature sig, + BiFunction runner) { return new Builder(name, sig, runner); } public static class Builder { private final String name; - private final MethodSignature sig; + private final JavaComponent.HandlerSignature sig; private final BiFunction runner; private final HashMap> sharedMethods; Builder( - String name, MethodSignature sig, BiFunction runner) { + String name, + JavaComponent.HandlerSignature sig, + BiFunction runner) { this.name = name; this.sig = sig; this.runner = runner; @@ -103,64 +105,36 @@ Builder( } public Builder withShared( - MethodSignature sig, BiFunction runner) { + JavaComponent.HandlerSignature sig, + BiFunction runner) { this.sharedMethods.put(sig.getMethod(), new Method<>(sig, runner)); return this; } - public WorkflowServicesBundle build() { - return new WorkflowServicesBundle(this.name, this.sig, this.runner, this.sharedMethods); + public WorkflowComponentBundle build() { + return new WorkflowComponentBundle(this.name, this.sig, this.runner, this.sharedMethods); } } @SuppressWarnings("unchecked") public static class Method { - private final MethodSignature methodSignature; + private final JavaComponent.HandlerSignature handlerSignature; private final BiFunction runner; Method( - MethodSignature methodSignature, BiFunction runner) { - this.methodSignature = methodSignature; + JavaComponent.HandlerSignature handlerSignature, + BiFunction runner) { + this.handlerSignature = handlerSignature; this.runner = (BiFunction) runner; } - public MethodSignature getMethodSignature() { - return methodSignature; + public JavaComponent.HandlerSignature getMethodSignature() { + return handlerSignature; } public RES run(Context ctx, REQ req) { return runner.apply(ctx, req); } } - - public static class MethodSignature { - - private final String method; - private final Serde requestSerde; - private final Serde responseSerde; - - MethodSignature(String method, Serde requestSerde, Serde responseSerde) { - this.method = toMethodName(method); - this.requestSerde = requestSerde; - this.responseSerde = responseSerde; - } - - public static MethodSignature of( - String method, Serde requestSerde, Serde responseSerde) { - return new MethodSignature<>(method, requestSerde, responseSerde); - } - - public String getMethod() { - return method; - } - - public Serde getRequestSerde() { - return requestSerde; - } - - public Serde getResponseSerde() { - return responseSerde; - } - } } diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowContextImpl.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowContextImpl.java index 8375b5f0..a313ba41 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowContextImpl.java +++ b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowContextImpl.java @@ -12,6 +12,7 @@ import dev.restate.sdk.*; import dev.restate.sdk.common.Serde; import dev.restate.sdk.common.StateKey; +import dev.restate.sdk.common.Target; import dev.restate.sdk.common.TerminalException; import dev.restate.sdk.common.function.ThrowingRunnable; import dev.restate.sdk.common.function.ThrowingSupplier; @@ -25,7 +26,7 @@ class WorkflowContextImpl implements WorkflowContext { - private final KeyedContext ctx; + private final ObjectContext ctx; private final String workflowKey; private final boolean isExclusive; @@ -40,7 +41,7 @@ class WorkflowContextImpl implements WorkflowContext { workflowManagerCompleteSignal; WorkflowContextImpl( - String workflowFqsn, KeyedContext ctx, String workflowKey, boolean isExclusive) { + String workflowFqsn, ObjectContext ctx, String workflowKey, boolean isExclusive) { this.ctx = ctx; this.workflowKey = workflowKey; this.isExclusive = isExclusive; @@ -150,11 +151,27 @@ public Awaitable call(MethodDescriptor methodDescriptor, T param return ctx.call(methodDescriptor, parameter); } + @Override + public Awaitable call( + Target target, Serde inputSerde, Serde outputSerde, T parameter) { + return ctx.call(target, inputSerde, outputSerde, parameter); + } + + @Override + public void oneWayCall(Target target, Serde inputSerde, T parameter) { + ctx.oneWayCall(target, inputSerde, parameter); + } + @Override public void oneWayCall(MethodDescriptor methodDescriptor, T parameter) { ctx.oneWayCall(methodDescriptor, parameter); } + @Override + public void delayedCall(Target target, Serde inputSerde, T parameter, Duration delay) { + ctx.delayedCall(target, inputSerde, parameter, delay); + } + @Override public void delayedCall( MethodDescriptor methodDescriptor, T parameter, Duration delay) { diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowImpl.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowImpl.java index b4c71674..36b6688b 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowImpl.java +++ b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowImpl.java @@ -11,11 +11,12 @@ import com.google.protobuf.Descriptors; import com.google.protobuf.Empty; import com.google.protobuf.Value; -import dev.restate.sdk.KeyedContext; -import dev.restate.sdk.RestateService; +import dev.restate.sdk.Component; +import dev.restate.sdk.ObjectContext; import dev.restate.sdk.common.Serde; import dev.restate.sdk.common.TerminalException; import dev.restate.sdk.common.syscalls.Syscalls; +import dev.restate.sdk.dynrpc.CodegenUtils; import dev.restate.sdk.workflow.WorkflowContext; import dev.restate.sdk.workflow.generated.*; import dev.restate.sdk.workflow.template.generated.WorkflowGrpc; @@ -32,16 +33,16 @@ import java.util.Set; import java.util.function.BiFunction; -class WorkflowImpl implements RestateService { +class WorkflowImpl implements Component { - private final WorkflowServicesBundle workflowServicesBundle; + private final WorkflowComponentBundle workflowServicesBundle; private final ServerServiceDefinition serverServiceDefinition; private final MethodDescriptor workflowManagerTryStart; private final MethodDescriptor workflowManagerSetOutput; private final MethodDescriptor workflowInternalStart; WorkflowImpl( - WorkflowServicesBundle workflowServicesBundle, + WorkflowComponentBundle workflowServicesBundle, WorkflowMangledDescriptors mangledDescriptors) { this.workflowServicesBundle = workflowServicesBundle; @@ -69,26 +70,26 @@ public final ServerServiceDefinition bindService() { } private void submit( - KeyedContext keyedContext, + ObjectContext objectContext, InvokeRequest invokeRequest, StreamObserver streamObserver) { // Try start var response = - keyedContext + objectContext .call( workflowManagerTryStart, StartRequest.newBuilder().setKey(invokeRequest.getKey()).build()) .await(); if (response.getState().equals(WorkflowExecutionState.STARTED)) { // Schedule start - keyedContext.oneWayCall(this.workflowInternalStart, invokeRequest); + objectContext.oneWayCall(this.workflowInternalStart, invokeRequest); } replySuccess(SubmitResponse.newBuilder().setState(response.getState()).build(), streamObserver); } private void internalStart( - KeyedContext keyedContext, + ObjectContext objectContext, InvokeRequest invokeRequest, StreamObserver streamObserver) { // We can start now! @@ -96,13 +97,13 @@ private void internalStart( try { // Convert input Object input = - WorkflowCodegenUtil.valueToT( + CodegenUtils.valueToT( workflowServicesBundle.getSig().getRequestSerde(), invokeRequest.getPayload()); // Invoke method WorkflowContext ctx = new WorkflowContextImpl( - workflowServicesBundle.getName(), keyedContext, invokeRequest.getKey(), true); + workflowServicesBundle.getName(), objectContext, invokeRequest.getKey(), true); @SuppressWarnings("unchecked") Object output = ((BiFunction) workflowServicesBundle.getRunner()) @@ -110,11 +111,11 @@ private void internalStart( //noinspection unchecked valueOutput = - WorkflowCodegenUtil.tToValue( + CodegenUtils.tToValue( (Serde) workflowServicesBundle.getSig().getResponseSerde(), output); } catch (TerminalException e) { // Intercept TerminalException to record it - keyedContext.oneWayCall( + objectContext.oneWayCall( workflowManagerSetOutput, SetOutputRequest.newBuilder() .setKey(invokeRequest.getKey()) @@ -129,7 +130,7 @@ private void internalStart( } // Record output - keyedContext.oneWayCall( + objectContext.oneWayCall( workflowManagerSetOutput, SetOutputRequest.newBuilder() .setKey(invokeRequest.getKey()) @@ -141,13 +142,13 @@ private void internalStart( private void invokeSharedMethod( String methodName, - KeyedContext context, + ObjectContext context, InvokeRequest request, StreamObserver streamObserver) { // Lookup the method @SuppressWarnings("unchecked") - WorkflowServicesBundle.Method method = - (WorkflowServicesBundle.Method) + WorkflowComponentBundle.Method method = + (WorkflowComponentBundle.Method) workflowServicesBundle.getSharedMethod(methodName); if (method == null) { throw new TerminalException( @@ -156,8 +157,7 @@ private void invokeSharedMethod( // Convert input Object input = - WorkflowCodegenUtil.valueToT( - method.getMethodSignature().getRequestSerde(), request.getPayload()); + CodegenUtils.valueToT(method.getMethodSignature().getRequestSerde(), request.getPayload()); // Invoke method WorkflowContext ctx = @@ -166,7 +166,7 @@ private void invokeSharedMethod( Object output = method.run(ctx, input); replySuccess( - WorkflowCodegenUtil.tToValue(method.getMethodSignature().getResponseSerde(), output), + CodegenUtils.tToValue(method.getMethodSignature().getResponseSerde(), output), streamObserver); } @@ -244,13 +244,17 @@ private ServerServiceDefinition buildWorfklowServerServiceDefinition( ServerCalls.asyncUnaryCall( (invokeRequest, streamObserver) -> this.submit( - KeyedContext.fromSyscalls(Syscalls.current()), invokeRequest, streamObserver))); + ObjectContext.fromSyscalls(Syscalls.current()), + invokeRequest, + streamObserver))); serverServiceDefinitionBuilder.addMethod( internalStartMethodDescriptor, ServerCalls.asyncUnaryCall( (invokeRequest, streamObserver) -> this.internalStart( - KeyedContext.fromSyscalls(Syscalls.current()), invokeRequest, streamObserver))); + ObjectContext.fromSyscalls(Syscalls.current()), + invokeRequest, + streamObserver))); // Compute shared methods for (var method : methods.entrySet()) { @@ -262,7 +266,7 @@ private ServerServiceDefinition buildWorfklowServerServiceDefinition( (invokeRequest, streamObserver) -> this.invokeSharedMethod( method.getKey(), - KeyedContext.fromSyscalls(Syscalls.current()), + ObjectContext.fromSyscalls(Syscalls.current()), invokeRequest, streamObserver)); diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowManagerImpl.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowManagerImpl.java index d24dcb57..0cecd4c3 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowManagerImpl.java +++ b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowManagerImpl.java @@ -12,7 +12,7 @@ import com.google.protobuf.Descriptors; import com.google.protobuf.Empty; import com.google.protobuf.UnsafeByteOperations; -import dev.restate.sdk.KeyedContext; +import dev.restate.sdk.ObjectContext; import dev.restate.sdk.common.*; import dev.restate.sdk.serde.jackson.JacksonSerdes; import dev.restate.sdk.workflow.generated.*; @@ -36,7 +36,7 @@ class WorkflowManagerImpl extends WorkflowManagerRestate.WorkflowManagerRestateI StateKey.of("_workflow_execution_state", CoreSerdes.ofProtobuf(StartResponse.parser())); @Override - public GetStateResponse getState(KeyedContext context, StateRequest request) + public GetStateResponse getState(ObjectContext context, StateRequest request) throws TerminalException { return context .get(stateKey(request.getStateKey())) @@ -50,12 +50,12 @@ public GetStateResponse getState(KeyedContext context, StateRequest request) } @Override - public void setState(KeyedContext context, SetStateRequest request) throws TerminalException { + public void setState(ObjectContext context, SetStateRequest request) throws TerminalException { context.set(stateKey(request.getStateKey()), request.getStateValue().toByteArray()); } @Override - public void clearState(KeyedContext context, StateRequest request) throws TerminalException { + public void clearState(ObjectContext context, StateRequest request) throws TerminalException { context.clear(stateKey(request.getStateKey())); } @@ -65,7 +65,7 @@ private StateKey stateKey(String key) { @Override public void waitDurablePromiseCompletion( - KeyedContext context, WaitDurablePromiseCompletionRequest request) throws TerminalException { + ObjectContext context, WaitDurablePromiseCompletionRequest request) throws TerminalException { Optional val = context.get(durablePromiseKey(request.getDurablePromiseKey())); if (val.isPresent()) { @@ -81,7 +81,7 @@ public void waitDurablePromiseCompletion( @Override public MaybeDurablePromiseCompletion getDurablePromiseCompletion( - KeyedContext context, GetDurablePromiseCompletionRequest request) throws TerminalException { + ObjectContext context, GetDurablePromiseCompletionRequest request) throws TerminalException { StateKey durablePromiseKey = durablePromiseKey(request.getDurablePromiseKey()); Optional val = context.get(durablePromiseKey); @@ -97,7 +97,7 @@ public MaybeDurablePromiseCompletion getDurablePromiseCompletion( } @Override - public void completeDurablePromise(KeyedContext context, CompleteDurablePromiseRequest request) + public void completeDurablePromise(ObjectContext context, CompleteDurablePromiseRequest request) throws TerminalException { // User can decide whether they want to allow overwriting the previously resolved value or not StateKey durablePromiseKey = @@ -117,7 +117,7 @@ public void completeDurablePromise(KeyedContext context, CompleteDurablePromiseR } private void completeListener( - KeyedContext context, String listener, DurablePromiseCompletion completion) { + ObjectContext context, String listener, DurablePromiseCompletion completion) { if (completion.hasValue()) { context .awakeableHandle(listener) @@ -128,7 +128,7 @@ private void completeListener( } @Override - public StartResponse tryStart(KeyedContext context, StartRequest request) + public StartResponse tryStart(ObjectContext context, StartRequest request) throws TerminalException { Optional maybeResponse = context.get(WORKFLOW_EXECUTION_STATE_KEY); if (maybeResponse.isPresent()) { @@ -142,7 +142,7 @@ public StartResponse tryStart(KeyedContext context, StartRequest request) } @Override - public GetOutputResponse getOutput(KeyedContext context, OutputRequest request) + public GetOutputResponse getOutput(ObjectContext context, OutputRequest request) throws TerminalException { return context .get(OUTPUT_KEY) @@ -157,7 +157,7 @@ public GetOutputResponse getOutput(KeyedContext context, OutputRequest request) } @Override - public void setOutput(KeyedContext context, SetOutputRequest request) throws TerminalException { + public void setOutput(ObjectContext context, SetOutputRequest request) throws TerminalException { context.set(OUTPUT_KEY, request.getOutput()); context.set( WORKFLOW_EXECUTION_STATE_KEY, @@ -165,7 +165,7 @@ public void setOutput(KeyedContext context, SetOutputRequest request) throws Ter } @Override - public void cleanup(KeyedContext context, WorkflowManagerRequest request) + public void cleanup(ObjectContext context, WorkflowManagerRequest request) throws TerminalException { context.clearAll(); } @@ -178,7 +178,7 @@ private StateKey> durablePromiseListenersKey(String key) { return StateKey.of("_durablePromise_listeners_" + key, DURABLEPROMISE_LISTENER_SERDE); } - static BlockingService create( + static BlockingComponent create( Descriptors.FileDescriptor outputFileDescriptor, String simpleName, String fqsn) { WorkflowManagerImpl workflowManager = new WorkflowManagerImpl(); ServerServiceDefinition originalDefinition = workflowManager.bindService(); diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowMangledDescriptors.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowMangledDescriptors.java index 53da137d..753c266b 100644 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowMangledDescriptors.java +++ b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowMangledDescriptors.java @@ -59,7 +59,7 @@ public String getWorkflowManagerServiceSimpleName() { return workflowManagerServiceSimpleName; } - public static WorkflowMangledDescriptors mangle(WorkflowServicesBundle workflowServicesBundle) { + public static WorkflowMangledDescriptors mangle(WorkflowComponentBundle workflowServicesBundle) { // This is the built-in workflow.proto descriptor var templateDescriptor = ((ProtoFileDescriptorSupplier)