Skip to content

Commit 8bbf9f0

Browse files
authored
Documentation for Data Converter (#263)
1 parent c90facd commit 8bbf9f0

File tree

3 files changed

+350
-0
lines changed

3 files changed

+350
-0
lines changed
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
---
2+
layout: default
3+
title: Data Converter
4+
permalink: /docs/concepts/data-converter
5+
---
6+
7+
# Data Converter
8+
9+
Data Converters in Cadence handle serialization and deserialization of data exchanged between workflows, activities, and the Cadence service. They ensure data is correctly encoded and decoded during communication.
10+
11+
---
12+
13+
## Key Features
14+
15+
### Custom Serialization
16+
Implement custom serialization logic for complex data types that require special handling beyond the default JSON encoding.
17+
18+
### Data Compression
19+
Reduce payload size for efficient data transfer, especially important for large data objects or high-throughput workflows.
20+
21+
### Encryption
22+
Secure sensitive data during transmission and storage by implementing encryption/decryption in your custom data converter.
23+
24+
### Format Support
25+
Support various serialization formats like JSON, Protocol Buffers, MessagePack, or custom binary formats.
26+
27+
---
28+
29+
## Default Data Converter
30+
31+
Cadence provides a default data converter that uses JSON for serialization. It handles most common Go types automatically and is suitable for most use cases.
32+
33+
```go
34+
import (
35+
"go.uber.org/cadence/client"
36+
"go.uber.org/cadence/encoded"
37+
)
38+
39+
// The default data converter is used automatically
40+
// when creating a workflow client
41+
workflowClient := client.NewClient(
42+
cadenceClient,
43+
domain,
44+
&client.Options{},
45+
)
46+
```
47+
48+
---
49+
50+
## Custom Data Converter Implementation
51+
52+
You can implement a custom data converter by implementing the `encoded.DataConverter` interface. Here's an example of a custom data converter that adds compression:
53+
54+
```go
55+
package main
56+
57+
import (
58+
"bytes"
59+
"compress/gzip"
60+
"encoding/json"
61+
"fmt"
62+
"io"
63+
64+
"go.uber.org/cadence/encoded"
65+
)
66+
67+
// CompressionDataConverter wraps the default JSON data converter
68+
// with gzip compression for payload size optimization
69+
type CompressionDataConverter struct {
70+
encoded.DataConverter
71+
}
72+
73+
// NewCompressionDataConverter creates a new compression data converter
74+
func NewCompressionDataConverter() *CompressionDataConverter {
75+
return &CompressionDataConverter{
76+
DataConverter: encoded.GetDefaultDataConverter(),
77+
}
78+
}
79+
80+
// ToData converts a value to compressed encoded data
81+
func (dc *CompressionDataConverter) ToData(values ...interface{}) ([]byte, error) {
82+
// First, serialize using the default JSON converter
83+
data, err := dc.DataConverter.ToData(values...)
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
// Compress the serialized data
89+
var buf bytes.Buffer
90+
gzWriter := gzip.NewWriter(&buf)
91+
92+
if _, err := gzWriter.Write(data); err != nil {
93+
return nil, fmt.Errorf("failed to compress data: %w", err)
94+
}
95+
96+
if err := gzWriter.Close(); err != nil {
97+
return nil, fmt.Errorf("failed to close gzip writer: %w", err)
98+
}
99+
100+
return buf.Bytes(), nil
101+
}
102+
103+
// FromData converts compressed encoded data back to values
104+
func (dc *CompressionDataConverter) FromData(data []byte, values ...interface{}) error {
105+
// Decompress the data
106+
gzReader, err := gzip.NewReader(bytes.NewReader(data))
107+
if err != nil {
108+
return fmt.Errorf("failed to create gzip reader: %w", err)
109+
}
110+
defer gzReader.Close()
111+
112+
decompressedData, err := io.ReadAll(gzReader)
113+
if err != nil {
114+
return fmt.Errorf("failed to decompress data: %w", err)
115+
}
116+
117+
// Deserialize using the default JSON converter
118+
return dc.DataConverter.FromData(decompressedData, values...)
119+
}
120+
```
121+
122+
---
123+
124+
## Encryption Data Converter
125+
126+
For sensitive data, you can implement an encryption data converter:
127+
128+
```go
129+
package main
130+
131+
import (
132+
"crypto/aes"
133+
"crypto/cipher"
134+
"crypto/rand"
135+
"errors"
136+
"io"
137+
138+
"go.uber.org/cadence/encoded"
139+
)
140+
141+
// EncryptionDataConverter adds AES encryption to data serialization
142+
type EncryptionDataConverter struct {
143+
encoded.DataConverter
144+
key []byte
145+
}
146+
147+
// NewEncryptionDataConverter creates a new encryption data converter
148+
func NewEncryptionDataConverter(key []byte) *EncryptionDataConverter {
149+
return &EncryptionDataConverter{
150+
DataConverter: encoded.GetDefaultDataConverter(),
151+
key: key,
152+
}
153+
}
154+
155+
// ToData encrypts the serialized data
156+
func (dc *EncryptionDataConverter) ToData(values ...interface{}) ([]byte, error) {
157+
// Serialize first
158+
data, err := dc.DataConverter.ToData(values...)
159+
if err != nil {
160+
return nil, err
161+
}
162+
163+
// Encrypt the data
164+
block, err := aes.NewCipher(dc.key)
165+
if err != nil {
166+
return nil, err
167+
}
168+
169+
// Generate a random nonce
170+
nonce := make([]byte, 12) // GCM standard nonce size
171+
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
172+
return nil, err
173+
}
174+
175+
aesgcm, err := cipher.NewGCM(block)
176+
if err != nil {
177+
return nil, err
178+
}
179+
180+
ciphertext := aesgcm.Seal(nonce, nonce, data, nil)
181+
return ciphertext, nil
182+
}
183+
184+
// FromData decrypts and deserializes the data
185+
func (dc *EncryptionDataConverter) FromData(data []byte, values ...interface{}) error {
186+
if len(data) < 12 {
187+
return errors.New("ciphertext too short")
188+
}
189+
190+
block, err := aes.NewCipher(dc.key)
191+
if err != nil {
192+
return err
193+
}
194+
195+
aesgcm, err := cipher.NewGCM(block)
196+
if err != nil {
197+
return err
198+
}
199+
200+
// Extract nonce and ciphertext
201+
nonce, ciphertext := data[:12], data[12:]
202+
203+
// Decrypt
204+
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
205+
if err != nil {
206+
return err
207+
}
208+
209+
// Deserialize
210+
return dc.DataConverter.FromData(plaintext, values...)
211+
}
212+
```
213+
214+
---
215+
216+
## Using Custom Data Converters
217+
218+
To use a custom data converter with your Cadence client:
219+
220+
```go
221+
package main
222+
223+
import (
224+
"go.uber.org/cadence/client"
225+
"go.uber.org/cadence/worker"
226+
)
227+
228+
func main() {
229+
// Create custom data converter
230+
customDataConverter := NewCompressionDataConverter()
231+
232+
// Use with workflow client
233+
workflowClient := client.NewClient(
234+
cadenceClient,
235+
domain,
236+
&client.Options{
237+
DataConverter: customDataConverter,
238+
},
239+
)
240+
241+
// Use with worker
242+
worker := worker.New(
243+
cadenceClient,
244+
domain,
245+
taskList,
246+
worker.Options{
247+
DataConverter: customDataConverter,
248+
},
249+
)
250+
}
251+
```
252+
253+
---
254+
255+
## Protocol Buffers Data Converter
256+
257+
For high-performance applications, you might want to use Protocol Buffers:
258+
259+
```go
260+
package main
261+
262+
import (
263+
"fmt"
264+
"reflect"
265+
266+
"google.golang.org/protobuf/proto"
267+
"go.uber.org/cadence/encoded"
268+
)
269+
270+
// ProtoDataConverter handles Protocol Buffers serialization
271+
type ProtoDataConverter struct{}
272+
273+
// NewProtoDataConverter creates a new Protocol Buffers data converter
274+
func NewProtoDataConverter() *ProtoDataConverter {
275+
return &ProtoDataConverter{}
276+
}
277+
278+
// ToData serializes proto messages to bytes
279+
func (dc *ProtoDataConverter) ToData(values ...interface{}) ([]byte, error) {
280+
if len(values) != 1 {
281+
return nil, fmt.Errorf("proto data converter expects exactly one value")
282+
}
283+
284+
message, ok := values[0].(proto.Message)
285+
if !ok {
286+
return nil, fmt.Errorf("value must implement proto.Message interface")
287+
}
288+
289+
return proto.Marshal(message)
290+
}
291+
292+
// FromData deserializes bytes to proto messages
293+
func (dc *ProtoDataConverter) FromData(data []byte, values ...interface{}) error {
294+
if len(values) != 1 {
295+
return fmt.Errorf("proto data converter expects exactly one value")
296+
}
297+
298+
// Get the pointer to the value
299+
rv := reflect.ValueOf(values[0])
300+
if rv.Kind() != reflect.Ptr {
301+
return fmt.Errorf("value must be a pointer")
302+
}
303+
304+
message, ok := values[0].(proto.Message)
305+
if !ok {
306+
return fmt.Errorf("value must implement proto.Message interface")
307+
}
308+
309+
return proto.Unmarshal(data, message)
310+
}
311+
```
312+
313+
---
314+
315+
## Best Practices
316+
317+
### Performance Considerations
318+
- Use compression for large payloads to reduce network overhead
319+
- Consider binary formats like Protocol Buffers for high-throughput scenarios
320+
- Profile your data converter implementation for performance bottlenecks
321+
322+
### Security
323+
- Always encrypt sensitive data before serialization
324+
- Use strong encryption algorithms and proper key management
325+
- Consider rotating encryption keys periodically
326+
327+
### Compatibility
328+
- Ensure backward compatibility when updating data converter implementations
329+
- Test data converter changes thoroughly before deploying to production
330+
- Document any breaking changes in serialization format
331+
332+
### Error Handling
333+
- Implement robust error handling in custom data converters
334+
- Provide meaningful error messages for debugging
335+
- Consider fallback mechanisms for corrupted or incompatible data
336+
337+
---
338+
339+
## References
340+
341+
For complete working examples and advanced implementations, refer to the official Cadence samples:
342+
- [Data Converter Recipe](https://github.com/cadence-workflow/cadence-samples/tree/master/cmd/samples/recipes/dataconverter)
343+
- [Cadence Go Client Documentation](https://pkg.go.dev/go.uber.org/cadence)
344+
345+
---
346+
347+
## Conclusion
348+
349+
Data Converters are a powerful feature in Cadence that allow developers to customize how data is handled during workflow execution. By leveraging custom converters, you can optimize performance, ensure data security, and support various serialization formats. The examples provided demonstrate compression, encryption, and Protocol Buffers implementations that can be adapted to your specific use cases.
File renamed without changes.

sidebars.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const sidebars: SidebarsConfig = {
6161
{ type: 'doc', id: 'concepts/cross-dc-replication' },
6262
{ type: 'doc', id: 'concepts/search-workflows' },
6363
{ type: 'doc', id: 'concepts/http-api' },
64+
{ type: 'doc', id: 'concepts/data-converter' },
6465
{ type: 'doc', id: 'concepts/grafana-helm-setup' },
6566
],
6667
},

0 commit comments

Comments
 (0)