Skip to content

Commit

Permalink
Add temporal-kotlin providing correct support for Kotlin in Async
Browse files Browse the repository at this point in the history
  • Loading branch information
Spikhalskiy committed Aug 14, 2021
1 parent d38065d commit 22d83c1
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 130 deletions.
2 changes: 1 addition & 1 deletion temporal-kotlin/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Temporal Kotlin support module

This module added to classpath provides some support for Kotlin specific language features:
- Support for Kotlin
- Support for Kotlin method references for Temporal client stubs passed into Async

## Usage

Expand Down
15 changes: 5 additions & 10 deletions temporal-kotlin/build.gradle
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
}

plugins {
// id 'org.jetbrains.kotlin.jvm' version '1.3.72'
// id 'org.jetbrains.kotlin.jvm' version '1.4.21'
id 'org.jetbrains.kotlin.jvm' version '1.5.21'

id 'org.jlleitschuh.gradle.ktlint' version '10.1.0'
}

Expand Down Expand Up @@ -38,10 +31,12 @@ dependencies {

implementation project(':temporal-sdk')

implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.12.4'
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: '2.12.4'

testImplementation project(':temporal-testing')
testImplementation project(':temporal-testing-junit4')

testImplementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.12.4'
testImplementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: '2.12.4'
testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.5'
testImplementation group: 'junit', name: 'junit', version: '4.13.2'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright 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 file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.common.converter

import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.PropertyAccessor
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule

class DefaultKotlinJacksonObjectMapper {
companion object {
fun new(): ObjectMapper {
val mapper = ObjectMapper()
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
mapper.registerModule(JavaTimeModule())
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
mapper.registerModule(KotlinModule())
return mapper
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright 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 file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.client.WorkflowClientOptions
import io.temporal.client.WorkflowOptions
import io.temporal.common.converter.DefaultDataConverter
import io.temporal.common.converter.DefaultKotlinJacksonObjectMapper
import io.temporal.common.converter.JacksonJsonPayloadConverter
import io.temporal.internal.async.FunctionWrappingUtil
import io.temporal.internal.sync.AsyncInternal
import io.temporal.testing.TestWorkflowRule
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test

class KotlinAsyncChildWorkflowTest {

@Rule @JvmField
var testWorkflowRule: TestWorkflowRule = TestWorkflowRule.newBuilder()
.setWorkflowTypes(NaiveParentWorkflowImpl::class.java, ChildWorkflowImpl::class.java)
.setWorkflowClientOptions(
WorkflowClientOptions.newBuilder()
.setDataConverter(DefaultDataConverter(JacksonJsonPayloadConverter(DefaultKotlinJacksonObjectMapper.new())))
.build()
)
.build()

@WorkflowInterface
interface ChildWorkflow {
@WorkflowMethod
fun execute(): Int
}

class ChildWorkflowImpl : ChildWorkflow {
override fun execute(): Int {
return 0
}
}

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

class NaiveParentWorkflowImpl : NaiveParentWorkflow {
override fun execute() {
val childWorkflow = Workflow.newChildWorkflowStub(ChildWorkflow::class.java)
assertTrue(
"This has to be true to make Async.function(childWorkflow::execute) work correctly as expected",
AsyncInternal.isAsync(childWorkflow::execute)
)
assertTrue(
"This has to be true to make Async.function(childWorkflow::execute) work correctly as expected",
AsyncInternal.isAsync(FunctionWrappingUtil.temporalJavaFunctionalWrapper(childWorkflow::execute))
)
Async.function(childWorkflow::execute).get()
}
}

@Test
fun asyncChildWorkflowTest() {
val client = testWorkflowRule.workflowClient
val options = WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.taskQueue).build()
val workflowStub = client.newWorkflowStub(NaiveParentWorkflow::class.java, options)
workflowStub.execute()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright 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 file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.client.WorkflowClientOptions
import io.temporal.client.WorkflowOptions
import io.temporal.common.converter.DefaultDataConverter
import io.temporal.common.converter.DefaultKotlinJacksonObjectMapper
import io.temporal.common.converter.JacksonJsonPayloadConverter
import io.temporal.testing.TestWorkflowRule
import junit.framework.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import java.util.concurrent.atomic.AtomicBoolean

class KotlinAsyncLambdaTest {

companion object {
private val success = AtomicBoolean(false)
}

@Rule @JvmField
var testWorkflowRule: TestWorkflowRule = TestWorkflowRule.newBuilder()
.setWorkflowTypes(NaiveParentWorkflowImpl::class.java)
.setWorkflowClientOptions(
WorkflowClientOptions.newBuilder()
.setDataConverter(DefaultDataConverter(JacksonJsonPayloadConverter(DefaultKotlinJacksonObjectMapper.new())))
.build()
)
.build()

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

class NaiveParentWorkflowImpl : NaiveParentWorkflow {
override fun execute() {
Async.procedure { success.set(true) }.get()
}
}

@Test
fun asyncChildWorkflowTest() {
val client = testWorkflowRule.workflowClient
val options = WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.taskQueue).build()
val workflowStub = client.newWorkflowStub(NaiveParentWorkflow::class.java, options)
workflowStub.execute()
assertTrue(success.get())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ private static boolean isAsyncKotlin(Object func) {
if (methodReferenceDisassemblyService == null) {
throw new IllegalStateException(
"Kotlin method reference is used with async. "
+ "For Temporal to correctly support async invocation kotlin method references, add temporal-kotlin to classpath");
+ "For Temporal to correctly support async invocation kotlin method references, "
+ "add io.temporal:temporal-kotlin to classpath");
}

Object target = methodReferenceDisassemblyService.getMethodReferenceTarget(func);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
* Provides language specific functionality to disassemble method references that could be passed
* from different languages into temporal code and extract target from them, which is required for
* correct working of {@link io.temporal.workflow.Async}
*/
public interface MethodReferenceDisassemblyService {
String KOTLIN = "kotlin";

Expand All @@ -33,5 +38,6 @@ public interface MethodReferenceDisassemblyService {
@Nullable
Object getMethodReferenceTarget(@Nonnull Object methodReference);

/** @return language this service provides an extension or implementation for */
String getLanguageName();
}

0 comments on commit 22d83c1

Please sign in to comment.