Skip to content

Commit

Permalink
Add helper for Nexus to Kotlin Integration (#2230)
Browse files Browse the repository at this point in the history
Add helper for Nexus to Kotlin
  • Loading branch information
Quinn-With-Two-Ns authored Sep 28, 2024
1 parent 8d7b3cd commit 3a605e0
Show file tree
Hide file tree
Showing 8 changed files with 342 additions and 1 deletion.
1 change: 1 addition & 0 deletions temporal-kotlin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies {

// this module shouldn't carry temporal-sdk with it, especially for situations when users may be using a shaded artifact
compileOnly project(':temporal-sdk')
implementation "io.nexusrpc:nexus-sdk:$nexusVersion"

implementation "org.jetbrains.kotlin:kotlin-reflect"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.workflow

import io.temporal.kotlin.TemporalDsl

/**
* @see NexusOperationOptions
*/
inline fun NexusOperationOptions(
options: @TemporalDsl NexusOperationOptions.Builder.() -> Unit
): NexusOperationOptions {
return NexusOperationOptions.newBuilder().apply(options).build()
}

/**
* Create a new instance of [NexusOperationOptions], optionally overriding some of its properties.
*/
inline fun NexusOperationOptions.copy(
overrides: @TemporalDsl NexusOperationOptions.Builder.() -> Unit
): NexusOperationOptions {
return toBuilder().apply(overrides).build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.workflow

import io.temporal.kotlin.TemporalDsl

/**
* @see NexusServiceOptions
*/
inline fun NexusServiceOptions(
options: @TemporalDsl NexusServiceOptions.Builder.() -> Unit
): NexusServiceOptions {
return NexusServiceOptions.newBuilder().apply(options).build()
}

/**
* Create a new instance of [NexusServiceOptions], optionally overriding some of its properties.
*/
inline fun NexusServiceOptions.copy(
overrides: @TemporalDsl NexusServiceOptions.Builder.() -> Unit
): NexusServiceOptions {
return toBuilder().apply(overrides).build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.nexus

import io.temporal.workflow.NexusOperationOptions
import org.junit.Assert.assertEquals
import org.junit.Test
import java.time.Duration

class NexusOperationOptionsExtTest {

@Test
fun `OperationOptions DSL should be equivalent to builder`() {
val dslOperationOptions = NexusOperationOptions {
setScheduleToCloseTimeout(Duration.ofMinutes(1))
}

val builderOperationOptions = NexusOperationOptions.newBuilder()
.setScheduleToCloseTimeout(Duration.ofMinutes(1))
.build()

assertEquals(builderOperationOptions, dslOperationOptions)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.nexus

import io.temporal.workflow.NexusOperationOptions
import io.temporal.workflow.NexusServiceOptions
import org.junit.Assert.assertEquals
import org.junit.Test
import java.time.Duration

class NexusServiceOptionsExtTest {

@Test
fun `ServiceOptions DSL should be equivalent to builder`() {
val dslServiceOptions = NexusServiceOptions {
setEndpoint("TestEndpoint")
setOperationOptions(
NexusOperationOptions {
setScheduleToCloseTimeout(Duration.ofMinutes(1))
}
)
setOperationMethodOptions(
mapOf(
"test" to NexusOperationOptions {
setScheduleToCloseTimeout(Duration.ofMinutes(2))
}
)
)
}

val builderServiceOptions = NexusServiceOptions.newBuilder()
.setEndpoint("TestEndpoint")
.setOperationOptions(
NexusOperationOptions.newBuilder()
.setScheduleToCloseTimeout(Duration.ofMinutes(1))
.build()
)
.setOperationMethodOptions(
mapOf(
"test" to NexusOperationOptions.newBuilder()
.setScheduleToCloseTimeout(Duration.ofMinutes(2))
.build()
)
)
.build()

assertEquals(builderServiceOptions, dslServiceOptions)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.workflow

import io.nexusrpc.Operation
import io.nexusrpc.Service
import io.nexusrpc.handler.OperationContext
import io.nexusrpc.handler.OperationHandler
import io.nexusrpc.handler.OperationImpl
import io.nexusrpc.handler.OperationStartDetails
import io.nexusrpc.handler.ServiceImpl
import io.nexusrpc.handler.SynchronousOperationFunction
import io.temporal.client.WorkflowClientOptions
import io.temporal.client.WorkflowOptions
import io.temporal.common.converter.DefaultDataConverter
import io.temporal.common.converter.JacksonJsonPayloadConverter
import io.temporal.common.converter.KotlinObjectMapperFactory
import io.temporal.internal.async.FunctionWrappingUtil
import io.temporal.internal.sync.AsyncInternal
import io.temporal.testing.internal.SDKTestWorkflowRule
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import java.time.Duration

class KotlinAsyncNexusTest {

@Rule
@JvmField
var testWorkflowRule: SDKTestWorkflowRule = SDKTestWorkflowRule.newBuilder()
.setWorkflowTypes(WorkflowImpl::class.java)
.setNexusServiceImplementation(TestNexusServiceImpl())
.setWorkflowClientOptions(
WorkflowClientOptions.newBuilder()
.setDataConverter(DefaultDataConverter(JacksonJsonPayloadConverter(KotlinObjectMapperFactory.new())))
.build()
)
.build()

@Service
interface TestNexusService {
@Operation
fun operation(): String?
}

@ServiceImpl(service = TestNexusService::class)
class TestNexusServiceImpl {
@OperationImpl
fun operation(): OperationHandler<Void, String> {
// Implemented inline
return OperationHandler.sync<Void, String>(
SynchronousOperationFunction<Void, String> { ctx: OperationContext, details: OperationStartDetails, _: Void? -> "Hello Kotlin" }
)
}
}

@WorkflowInterface
interface TestWorkflow {
@WorkflowMethod
fun execute()
}

class WorkflowImpl : TestWorkflow {
override fun execute() {
val nexusService = Workflow.newNexusServiceStub(
TestNexusService::class.java,
NexusServiceOptions {
setOperationOptions(
NexusOperationOptions {
setScheduleToCloseTimeout(Duration.ofSeconds(10))
}
)
}
)
assertTrue(
"This has to be true to make Async.function(nexusService::operation) work correctly as expected",
AsyncInternal.isAsync(nexusService::operation)
)
assertTrue(
"This has to be true to make Async.function(nexusService::operation) work correctly as expected",
AsyncInternal.isAsync(FunctionWrappingUtil.temporalJavaFunctionalWrapper(nexusService::operation))
)
Async.function(nexusService::operation).get()
}
}

@Test
fun asyncNexusWorkflowTest() {
val client = testWorkflowRule.workflowClient
val options = WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.taskQueue).build()
val workflowStub = client.newWorkflowStub(TestWorkflow::class.java, options)
workflowStub.execute()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ private NexusOperationOptions(Duration scheduleToCloseTimeout) {
this.scheduleToCloseTimeout = scheduleToCloseTimeout;
}

public NexusOperationOptions.Builder toBuilder() {
return new NexusOperationOptions.Builder(this);
}

private Duration scheduleToCloseTimeout;

public Duration getScheduleToCloseTimeout() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.temporal.common.Experimental;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;

/**
* Options for configuring a NexusService in a Workflow.
Expand Down Expand Up @@ -131,7 +132,7 @@ public NexusServiceOptions.Builder mergeNexusServiceOptions(NexusServiceOptions
private final Map<String, NexusOperationOptions> operationMethodOptions;
private final String endpoint;

NexusServiceOptions(
private NexusServiceOptions(
String endpoint,
NexusOperationOptions operationOptions,
Map<String, NexusOperationOptions> operationMethodOptions) {
Expand All @@ -143,6 +144,10 @@ public NexusServiceOptions.Builder mergeNexusServiceOptions(NexusServiceOptions
: Collections.unmodifiableMap(operationMethodOptions);
}

public NexusServiceOptions.Builder toBuilder() {
return new NexusServiceOptions.Builder(this);
}

public NexusOperationOptions getOperationOptions() {
return operationOptions;
}
Expand All @@ -154,4 +159,32 @@ public String getEndpoint() {
public Map<String, NexusOperationOptions> getOperationMethodOptions() {
return operationMethodOptions;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NexusServiceOptions that = (NexusServiceOptions) o;
return Objects.equals(operationOptions, that.operationOptions)
&& Objects.equals(operationMethodOptions, that.operationMethodOptions)
&& Objects.equals(endpoint, that.endpoint);
}

@Override
public int hashCode() {
return Objects.hash(operationOptions, operationMethodOptions, endpoint);
}

@Override
public String toString() {
return "NexusServiceOptions{"
+ "operationOptions="
+ operationOptions
+ ", operationMethodOptions="
+ operationMethodOptions
+ ", endpoint='"
+ endpoint
+ '\''
+ '}';
}
}

0 comments on commit 3a605e0

Please sign in to comment.