2

I have a workflow with ZonedDatetime of UTC as an argument. During the unit test with test env, I see the UTC zone is dropped while running the workflow. What would be the cause of this behavior?

[UPDATE] It turns out the timezone is not dropped, but somehow converted to Zone id of "Z" instead of the original "UTC", although they're basically the same. Sample code,

data class WorkflowArgs(
   val currentTime: ZonedDateTime
)

class WorkflowImpl {
    override fun execute(workflowArgs: WorkflowArgs) { 
     activityStub.doSomeActivity(
         workflowArgs.currentTime // Zone ID of "Z"
     )
    }
}

WorkflowClient.start(workflowStub::execute, WorkflowArgs(ZonedDateTime.now(ZoneId.of("UTC")))  // Zone ID of "UTC"
user12488846
  • 21
  • 1
  • 3
  • It's probably due to data converter. Which client SDK you are using? and you show an example code of how you pass in the argument ? – Long Quanzheng Sep 12 '21 at 22:38
  • Thanks @LongQuanzheng I update the question with sample code, What's the data converted you mentioned for? – user12488846 Sep 13 '21 at 03:00
  • That's because ZonedDateTime doesn't work for default JSON serialization. You need to implement it yourself. I added an answer to help. – Long Quanzheng Sep 13 '21 at 21:46

1 Answers1

1

You need to customize the DataConverter.

DataConvert is for converting between your argument(Class) and byte array so that your client can send the argument to Cadence server, and then server send it to workflow worker.

For example, you can do something like below.

NOTE that this is just an example for you to implement DataConverter. To encode/decode ZonedDataTime you need to find out a way to do it correctly. You may need to refer to Spring Data JPA - ZonedDateTime format for json serialization


data class WorkflowArgs(
    val currentTime: ZonedDateTime
)

fun main1() { // this doesn't work
    val dataConverter = JsonDataConverter.getInstance()
    val args = WorkflowArgs( ZonedDateTime.now())
    println(args.currentTime.zone)
    val encoded = dataConverter.toData(args)
    val decoded: WorkflowArgs = dataConverter.fromData(encoded, args.javaClass, args.javaClass)
    println(decoded.currentTime.zone)
    /**
     * Results:
    America/Los_Angeles
    Exception in thread "main" com.uber.cadence.converter.DataConverterException: when parsing:"{"currentTime":{"dateTime":{"date":{"year":2021,"month":9,"day":13},"time":{"hour":9,"minute":14,"second":46,"nano":517000000}},"offset":{"totalSeconds":-25200},"zone":{"id":"America/Los_Angeles"}}}" into following types: [class com.uber.cadence.converter.WorkflowArgs]
    at com.uber.cadence.converter.JsonDataConverter.fromData(JsonDataConverter.java:111)
    at com.uber.cadence.converter.TestZonedDatatimeKt.main(TestZonedDatatime.kt:14)
    at com.uber.cadence.converter.TestZonedDatatimeKt.main(TestZonedDatatime.kt)
    Caused by: java.lang.RuntimeException: Failed to invoke java.time.ZoneId() with no args
    at com.google.gson.internal.ConstructorConstructor$3.construct(ConstructorConstructor.java:113)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:212)
...
    at com.uber.cadence.converter.JsonDataConverter.fromData(JsonDataConverter.java:109)
    ... 2 more
     */
}

fun main() { // you need to implement another DataConverter like this
    val dataConverter = MyDataConvert()
    val args = WorkflowArgs( ZonedDateTime.now())
    println(args.currentTime.zone)
    val encoded = dataConverter.toData(args)
    val decoded: WorkflowArgs = dataConverter.fromData(encoded, args.javaClass, args.javaClass)
    println(decoded.currentTime.zone)
/**
Resutls:
America/Los_Angeles
America/Los_Angeles
**/
}

class MyDataConvert:DataConverter{

    val originalDataConverter = JsonDataConverter.getInstance()

    override fun toData(vararg value: Any?): ByteArray {
        if( (value.size == 1) && (value[0] is WorkflowArgs)){
            val v = value[0] as WorkflowArgs
            val zoneId = v.currentTime.zone.id
            val br = zoneId.toByteArray()
            return br
        }else{
            return originalDataConverter.toData(value)
        }
    }

    override fun <T : Any?> fromData(content: ByteArray?, valueClass: Class<T>?, valueType: Type?): T {
        if(valueClass!!.canonicalName.contains("WorkflowArgs") ){
            val zi = String(content!!)
            val zoneId: ZoneId = ZoneId.of(zi)
            val dt = ZonedDateTime.now(zoneId)
            return WorkflowArgs(dt) as T
        }else{
            return originalDataConverter.fromData(content, valueClass, valueType)
        }
    }

    override fun fromDataArray(content: ByteArray?, vararg valueType: Type?): Array<Any> {
        if( (valueType.size == 1) && (valueType[0]!!.typeName.contains("WorkflowArgs"))){
            val zi = String(content!!)
            val zoneId: ZoneId = ZoneId.of(zi)
            val dt = ZonedDateTime.now(zoneId)
            val arr = arrayOf(WorkflowArgs(dt))
            return arr as Array<Any>
        }
        return originalDataConverter.fromDataArray(content, *valueType)
    }

}


This is another example of implmenting DataConverter: https://github.com/uber/cadence-java-samples/pull/37

Long Quanzheng
  • 2,076
  • 1
  • 10
  • 22