Skip to main content

Payload conversion - Ruby SDK

View Markdown

Payload conversion

Temporal SDKs provide a default Payload Converter that can be customized to convert a custom data type to Payload and back.

Conversion sequence

The order in which your encoding Payload Converters are applied depend on the order given to the Data Converter. You can set multiple encoding Payload Converters to run your conversions. When the Data Converter receives a value for conversion, it passes through each Payload Converter in sequence until the converter that handles the data type does the conversion.

Payload Converters can be customized independently of a Payload Codec. Temporal's Converter architecture looks like this:

Temporal converter architecture

Temporal converter architecture

Supported Data Types

Data converters are used to convert raw Temporal payloads to/from actual Ruby types. A custom data converter can be set via the data_converter keyword argument when creating a client. Data converters are a combination of payload converters, payload codecs, and failure converters. Payload converters convert Ruby values to/from serialized bytes. Payload codecs convert bytes to bytes (e.g. for compression or encryption). Failure converters convert exceptions to/from serialized failures.

Data converters are in the Temporalio::Converters module. The default data converter uses a default payload converter, which supports the following types:

  • nil
  • "bytes" (i.e. String with Encoding::ASCII_8BIT encoding)
  • Google::Protobuf::MessageExts instances
  • JSON module for everything else

This means that normal Ruby objects will use JSON.generate when serializing and JSON.parse when deserializing (with create_additions: true set by default). So a Ruby object will often appear as a hash when deserialized. Also, hashes that are passed in with symbol keys end up with string keys when deserialized. While "JSON Additions" are supported, it is not cross-SDK-language compatible since this is a Ruby-specific construct.

The default payload converter is a collection of "encoding payload converters". On serialize, each encoding converter will be tried in order until one accepts (default falls through to the JSON one). The encoding converter sets an encoding metadata value which is used to know which converter to use on deserialize. Custom encoding converters can be created, or even the entire payload converter can be replaced with a different implementation.

NOTE: For ActiveRecord, or other general/ORM models that are used for a different purpose, it is not recommended to try to reuse them as Temporal models. Eventually model purposes diverge and models for a Temporal workflows/activities should be specific to their use for clarity and compatibility reasons. Also many Ruby ORMs do many lazy things and therefore provide unclear serialization semantics. Instead, consider having models specific for workflows/activities and translate to/from existing models as needed. See the next section on how to do this with ActiveModel objects.

ActiveModel

By default, ActiveModel objects do not natively support the JSON module. A mixin can be created to add this support for ActiveModel, for example:

module ActiveModelJSONSupport
extend ActiveSupport::Concern
include ActiveModel::Serializers::JSON

included do
def as_json(*)
super.merge(::JSON.create_id => self.class.name)
end

def to_json(*args)
as_json.to_json(*args)
end

def self.json_create(object)
object = object.dup
object.delete(::JSON.create_id)
new(**object.symbolize_keys)
end
end
end

Now if include ActiveModelJSONSupport is present on any ActiveModel class, on serialization to_json will be used which will use as_json which calls the super as_json but also includes the fully qualified class name as the JSON create_id key. On deserialization, Ruby JSON then uses this key to know what class to call json_create on.