Skip to content

Commit 82fc68e

Browse files
Implement Shared handlers annotation for virtual objects (#288)
1 parent 5bb5164 commit 82fc68e

File tree

23 files changed

+201
-72
lines changed

23 files changed

+201
-72
lines changed

examples/src/main/java/my/restate/sdk/examples/Counter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
package my.restate.sdk.examples;
1010

1111
import dev.restate.sdk.ObjectContext;
12+
import dev.restate.sdk.SharedObjectContext;
1213
import dev.restate.sdk.annotation.Handler;
14+
import dev.restate.sdk.annotation.Shared;
1315
import dev.restate.sdk.annotation.VirtualObject;
1416
import dev.restate.sdk.common.CoreSerdes;
1517
import dev.restate.sdk.common.StateKey;
@@ -36,8 +38,9 @@ public void add(ObjectContext ctx, Long request) {
3638
ctx.set(TOTAL, newValue);
3739
}
3840

41+
@Shared
3942
@Handler
40-
public Long get(ObjectContext ctx) {
43+
public Long get(SharedObjectContext ctx) {
4144
return ctx.get(TOTAL).orElse(0L);
4245
}
4346

examples/src/main/kotlin/my/restate/sdk/examples/CounterKt.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
package my.restate.sdk.examples
1010

1111
import dev.restate.sdk.annotation.Handler
12+
import dev.restate.sdk.annotation.Shared
1213
import dev.restate.sdk.annotation.VirtualObject
1314
import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder
1415
import dev.restate.sdk.kotlin.KtStateKey
1516
import dev.restate.sdk.kotlin.ObjectContext
17+
import dev.restate.sdk.kotlin.SharedObjectContext
1618
import kotlinx.serialization.Serializable
1719
import org.apache.logging.log4j.LogManager
1820
import org.apache.logging.log4j.Logger
@@ -40,7 +42,8 @@ class CounterKt {
4042
}
4143

4244
@Handler
43-
suspend fun get(ctx: ObjectContext): Long {
45+
@Shared
46+
suspend fun get(ctx: SharedObjectContext): Long {
4447
return ctx.get(TOTAL) ?: 0L
4548
}
4649

sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import dev.restate.sdk.Context;
1212
import dev.restate.sdk.ObjectContext;
13+
import dev.restate.sdk.SharedObjectContext;
1314
import dev.restate.sdk.annotation.Exclusive;
1415
import dev.restate.sdk.annotation.Shared;
1516
import dev.restate.sdk.annotation.Workflow;
@@ -233,6 +234,8 @@ private void validateMethodSignature(
233234
case SHARED:
234235
if (serviceType == ServiceType.WORKFLOW) {
235236
validateFirstParameterType(WorkflowSharedContext.class, element);
237+
} else if (serviceType == ServiceType.VIRTUAL_OBJECT) {
238+
validateFirstParameterType(SharedObjectContext.class, element);
236239
} else {
237240
messager.printMessage(
238241
Diagnostic.Kind.ERROR,

sdk-api-gen/src/main/resources/templates/BindableService.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class {{generatedClassSimpleName}} implements dev.restate.sdk.common.Bind
1313
public {{generatedClassSimpleName}}({{originalClassFqcn}} bindableService, dev.restate.sdk.Service.Options options) {
1414
this.service = dev.restate.sdk.Service.{{#if isObject}}virtualObject{{else}}service{{/if}}(SERVICE_NAME)
1515
{{#handlers}}
16-
.with(
16+
.{{#if isShared}}withShared{{else if isExclusive}}withExclusive{{else}}with{{/if}}(
1717
dev.restate.sdk.Service.HandlerSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}),
1818
(ctx, req) -> {
1919
{{#if outputEmpty}}

sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@
1212
import static dev.restate.sdk.core.TestDefinitions.testInvocation;
1313

1414
import com.google.protobuf.ByteString;
15-
import dev.restate.sdk.annotation.Exclusive;
16-
import dev.restate.sdk.annotation.Handler;
15+
import dev.restate.sdk.annotation.*;
1716
import dev.restate.sdk.annotation.Service;
18-
import dev.restate.sdk.annotation.VirtualObject;
1917
import dev.restate.sdk.common.CoreSerdes;
2018
import dev.restate.sdk.common.Target;
2119
import dev.restate.sdk.core.ProtoUtils;
@@ -39,6 +37,12 @@ static class ObjectGreeter {
3937
String greet(ObjectContext context, String request) {
4038
return request;
4139
}
40+
41+
@Handler
42+
@Shared
43+
String sharedGreet(SharedObjectContext context, String request) {
44+
return request;
45+
}
4246
}
4347

4448
@VirtualObject
@@ -113,6 +117,10 @@ public Stream<TestDefinitions.TestDefinition> definitions() {
113117
.withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco"))
114118
.onlyUnbuffered()
115119
.expectingOutput(outputMessage("Francesco"), END_MESSAGE),
120+
testInvocation(ObjectGreeter::new, "sharedGreet")
121+
.withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco"))
122+
.onlyUnbuffered()
123+
.expectingOutput(outputMessage("Francesco"), END_MESSAGE),
116124
testInvocation(ObjectGreeterImplementedFromInterface::new, "greet")
117125
.withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco"))
118126
.onlyUnbuffered()

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import dev.restate.sdk.gen.model.PayloadType
2323
import dev.restate.sdk.gen.model.Service
2424
import dev.restate.sdk.kotlin.Context
2525
import dev.restate.sdk.kotlin.ObjectContext
26+
import dev.restate.sdk.kotlin.SharedObjectContext
2627
import java.util.regex.Pattern
2728
import kotlin.reflect.KClass
2829

@@ -128,7 +129,7 @@ class KElementConverter(private val logger: KSPLogger, private val builtIns: KSB
128129
}
129130

130131
val isAnnotatedWithShared =
131-
function.isAnnotationPresent(dev.restate.sdk.annotation.Service::class)
132+
function.isAnnotationPresent(dev.restate.sdk.annotation.Shared::class)
132133
val isAnnotatedWithExclusive =
133134
function.isAnnotationPresent(dev.restate.sdk.annotation.Exclusive::class)
134135

@@ -190,8 +191,13 @@ class KElementConverter(private val logger: KSPLogger, private val builtIns: KSB
190191
}
191192
when (handlerType) {
192193
HandlerType.SHARED ->
193-
logger.error(
194-
"The annotation @Shared is not supported by the service type $serviceType", function)
194+
if (serviceType == ServiceType.VIRTUAL_OBJECT) {
195+
validateFirstParameterType(SharedObjectContext::class, function)
196+
} else {
197+
logger.error(
198+
"The annotation @Shared is not supported by the service type $serviceType",
199+
function)
200+
}
195201
HandlerType.EXCLUSIVE ->
196202
if (serviceType == ServiceType.VIRTUAL_OBJECT) {
197203
validateFirstParameterType(ObjectContext::class, function)

sdk-api-kotlin-gen/src/main/resources/templates/BindableService.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class {{generatedClassSimpleName}}(
1111

1212
val service: dev.restate.sdk.kotlin.Service = dev.restate.sdk.kotlin.Service.{{#if isObject}}virtualObject{{else}}service{{/if}}(SERVICE_NAME, options) {
1313
{{#handlers}}
14-
handler(dev.restate.sdk.kotlin.Service.HandlerSignature("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}})) { ctx, req ->
14+
{{#if isShared}}sharedHandler{{else if isExclusive}}exclusiveHandler{{else}}handler{{/if}}(dev.restate.sdk.kotlin.Service.HandlerSignature("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}})) { ctx, req ->
1515
{{#if inputEmpty}}bindableService.{{name}}(ctx){{else}}bindableService.{{name}}(ctx, req){{/if}}
1616
}
1717
{{/handlers}}

sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@
99
package dev.restate.sdk.kotlin
1010

1111
import com.google.protobuf.ByteString
12-
import dev.restate.sdk.annotation.Exclusive
13-
import dev.restate.sdk.annotation.Handler
12+
import dev.restate.sdk.annotation.*
1413
import dev.restate.sdk.annotation.Service
15-
import dev.restate.sdk.annotation.VirtualObject
1614
import dev.restate.sdk.common.CoreSerdes
1715
import dev.restate.sdk.common.Target
1816
import dev.restate.sdk.core.ProtoUtils.*
@@ -36,6 +34,12 @@ class CodegenTest : TestDefinitions.TestSuite {
3634
suspend fun greet(context: ObjectContext, request: String): String {
3735
return request
3836
}
37+
38+
@Handler
39+
@Shared
40+
suspend fun sharedGreet(context: SharedObjectContext, request: String): String {
41+
return request
42+
}
3943
}
4044

4145
@VirtualObject
@@ -104,6 +108,10 @@ class CodegenTest : TestDefinitions.TestSuite {
104108
.withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco"))
105109
.onlyUnbuffered()
106110
.expectingOutput(outputMessage("Francesco"), END_MESSAGE),
111+
testInvocation({ ObjectGreeter() }, "sharedGreet")
112+
.withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco"))
113+
.onlyUnbuffered()
114+
.expectingOutput(outputMessage("Francesco"), END_MESSAGE),
107115
testInvocation({ ObjectGreeterImplementedFromInterface() }, "greet")
108116
.withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco"))
109117
.onlyUnbuffered()

sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Service.kt

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@
99
package dev.restate.sdk.kotlin
1010

1111
import com.google.protobuf.ByteString
12-
import dev.restate.sdk.common.BindableService
13-
import dev.restate.sdk.common.Serde
14-
import dev.restate.sdk.common.ServiceType
15-
import dev.restate.sdk.common.TerminalException
12+
import dev.restate.sdk.common.*
1613
import dev.restate.sdk.common.syscalls.*
1714
import kotlin.coroutines.CoroutineContext
1815
import kotlinx.coroutines.CoroutineScope
@@ -65,18 +62,31 @@ private constructor(
6562
class VirtualObjectBuilder internal constructor(private val name: String) {
6663
private val handlers: MutableMap<String, Handler<*, *, ObjectContext>> = mutableMapOf()
6764

68-
fun <REQ, RES> handler(
65+
fun <REQ, RES> sharedHandler(
6966
sig: HandlerSignature<REQ, RES>,
7067
runner: suspend (ObjectContext, REQ) -> RES
7168
): VirtualObjectBuilder {
72-
handlers[sig.name] = Handler(sig, runner)
69+
handlers[sig.name] = Handler(sig, HandlerType.SHARED, runner)
7370
return this
7471
}
7572

76-
inline fun <reified REQ, reified RES> handler(
73+
inline fun <reified REQ, reified RES> sharedHandler(
7774
name: String,
7875
noinline runner: suspend (ObjectContext, REQ) -> RES
79-
) = this.handler(HandlerSignature(name, KtSerdes.json(), KtSerdes.json()), runner)
76+
) = this.sharedHandler(HandlerSignature(name, KtSerdes.json(), KtSerdes.json()), runner)
77+
78+
fun <REQ, RES> exclusiveHandler(
79+
sig: HandlerSignature<REQ, RES>,
80+
runner: suspend (ObjectContext, REQ) -> RES
81+
): VirtualObjectBuilder {
82+
handlers[sig.name] = Handler(sig, HandlerType.EXCLUSIVE, runner)
83+
return this
84+
}
85+
86+
inline fun <reified REQ, reified RES> exclusiveHandler(
87+
name: String,
88+
noinline runner: suspend (ObjectContext, REQ) -> RES
89+
) = this.exclusiveHandler(HandlerSignature(name, KtSerdes.json(), KtSerdes.json()), runner)
8090

8191
fun build(options: Options) = Service(this.name, true, this.handlers, options)
8292
}
@@ -88,7 +98,7 @@ private constructor(
8898
sig: HandlerSignature<REQ, RES>,
8999
runner: suspend (Context, REQ) -> RES
90100
): ServiceBuilder {
91-
handlers[sig.name] = Handler(sig, runner)
101+
handlers[sig.name] = Handler(sig, HandlerType.SHARED, runner)
92102
return this
93103
}
94104

@@ -102,6 +112,7 @@ private constructor(
102112

103113
class Handler<REQ, RES, CTX : Context>(
104114
private val handlerSignature: HandlerSignature<REQ, RES>,
115+
private val handlerType: HandlerType,
105116
private val runner: suspend (CTX, REQ) -> RES,
106117
) : InvocationHandler<Options> {
107118

@@ -112,6 +123,7 @@ private constructor(
112123
fun toHandlerDefinition() =
113124
HandlerDefinition(
114125
handlerSignature.name,
126+
handlerType,
115127
handlerSignature.requestSerde.schema(),
116128
handlerSignature.responseSerde.schema(),
117129
this)

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ suspend inline fun <reified T : Any> Context.awakeable(): Awakeable<T> {
247247
* This interface extends [Context] adding access to the virtual object instance key-value state
248248
* storage.
249249
*/
250-
sealed interface ObjectContext : Context {
250+
sealed interface SharedObjectContext : Context {
251251

252252
/** @return the key of this object */
253253
fun key(): String
@@ -267,6 +267,13 @@ sealed interface ObjectContext : Context {
267267
* @return the immutable collection of known state keys.
268268
*/
269269
suspend fun stateKeys(): Collection<String>
270+
}
271+
272+
/**
273+
* This interface extends [Context] adding access to the virtual object instance key-value state
274+
* storage.
275+
*/
276+
sealed interface ObjectContext : SharedObjectContext {
270277

271278
/**
272279
* Sets the given value under the given key, serializing the value using the [StateKey.serde].

0 commit comments

Comments
 (0)