Configure Camunda SPIN in SpringBoot and Kotlin

Camunda SPIN is a framework used by Camunda engine for serialization and de-serialization of payload. It is pre-configured for the usage, but can be extended if needed as described in this example.

In my current project, I had to use Camunda SPIN together with a decent version of SpringBoot and Kotlin and wanted to rely on Jackson JSON serialization.

In order to use Camunda SPIN, the following Maven Modules should be available on the classpath (I use Engine 7.9.0 and SPIN 1.5.1):

<dependency>
<groupId>org.camunda.bpm</groupId>
<artifactId>camunda-engine-plugin-spin</artifactId>
<version>${camunda.version}</version>
</dependency>
<dependency>
<groupId>org.camunda.spin</groupId>
<artifactId>camunda-spin-dataformat-json-jackson</artifactId>
<version>${version.camunda.spin}</version>
</dependency>

For usage of Jackson (including Java8 time and date datatypes) with Kotlin and SpringBoot, the following dependencies are required (Jackson 2.9.5 should be used if using SpringBoot 2.0.1 RELEASE):

<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>

Now, if you run the application, the ObjectMapper, available for Spring (e.g. via Injection) is capable of handling Java8 types and can work with Kotlin data classes. If you try to serialize or deserialize any of those in SPIN, e.g. by putting a such an object as a process variable, you will fail with a deseralization error. This is because default SPIN JacksonDataFormatConfigurator is accessing the Jackson Object Mapper by calling new ObjectMapper().

In order to solve this, a special JacksonDataFormatConfigurator needs to be provided:

package io.holunda.spin

import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import org.camunda.spin.impl.json.jackson.format.JacksonJsonDataFormat
import org.camunda.spin.spi.DataFormatConfigurator


open class JacksonDataFormatConfigurator : DataFormatConfigurator<JacksonJsonDataFormat> {

override fun configure(dataFormat: JacksonJsonDataFormat) {
val objectMapper = dataFormat.objectMapper
objectMapper.registerModule(KotlinModule())
objectMapper.registerModule(JavaTimeModule())
}

override fun getDataFormatClass(): Class<JacksonJsonDataFormat> = JacksonJsonDataFormat::class.java

}

This one is registering both modules available on classpath with SPIN ObjectMapper. Now, we need to register this class by SPIN SPI. Create a file META-INF/services/org.camunda.spin.spi.DataFormatConfigurator with the following content:

io.holunda.spin.JacksonDataFormatConfigurator

Run the process application and you should be able to see the registration of out configurator in the log:

2018–08–21 20:57:55.009 INFO 7980 — — [ost-startStop-1] org.camunda.spin : SPIN-01011 Discovered Spin data format configurator: class io.holunda.spin.JacksonDataFormatConfigurator[dataformat = org.camunda.spin.impl.json.jackson.format.JacksonJsonDataFormat]

Now, the serialization works directly for Kotlin data classes (without special default constructors). So the instance of the following class can be serialized and de-serialized by SPIN directly:

data class Reservation(
val name: String,
@get: JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
val from: LocalDate,
@get: JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
val to: LocalDate,
val hotel: String,
val flightNumber: String,
val id: String = UUID.randomUUID().toString()
)

References

Like what you read? Give Simon Zambrovski a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.