Skip to content

Commit 4030b93

Browse files
committed
refactor(dataconverter): split samples into per-pattern packages
Signed-off-by: “Kevin” <kevlar_ksb@yahoo.com>
1 parent dfc0e81 commit 4030b93

27 files changed

Lines changed: 1057 additions & 844 deletions

README.md

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ These samples demonstrate various capabilities of Java Cadence client and server
2929

3030
* **Custom Workflow Controls** ([`com.uber.cadence.samples.query`](src/main/java/com/uber/cadence/samples/query/)) — workflow queries that return **markdown** for Cadence Web (Markdoc buttons that **signal** workflows or **start** new workflows). **Requires Cadence Web v4.0.14+.** Copy-paste run instructions: [query samples README](src/main/java/com/uber/cadence/samples/query/README.md).
3131

32-
* **DataConverter Samples** ([`com.uber.cadence.samples.dataconverter`](src/main/java/com/uber/cadence/samples/dataconverter/)) — three custom `DataConverter` patterns (gzip compression, AES-256-GCM encryption, and BlobStore / S3 claim-check offload) that transparently transform every workflow input, output, and activity parameter. Copy-paste run instructions: [dataconverter samples README](src/main/java/com/uber/cadence/samples/dataconverter/README.md).
32+
* **DataConverter Samples** — three independent custom `DataConverter` patterns that transparently transform every workflow input, output, and activity parameter. Each lives in its own package and is fully standalone, so you can copy any one of them into your own project:
33+
* **Compression** ([`com.uber.cadence.samples.compression`](src/main/java/com/uber/cadence/samples/compression/)) — gzip-over-JSON; typically 60-80% size reduction for repetitive payloads. [README](src/main/java/com/uber/cadence/samples/compression/README.md).
34+
* **Encryption** ([`com.uber.cadence.samples.encryption`](src/main/java/com/uber/cadence/samples/encryption/)) — AES-256-GCM so payloads in Cadence history are unreadable without the key. [README](src/main/java/com/uber/cadence/samples/encryption/README.md).
35+
* **S3 / claim-check offload** ([`com.uber.cadence.samples.s3offload`](src/main/java/com/uber/cadence/samples/s3offload/)) — payloads above a threshold are stored in an external `BlobStore`; only a small reference travels through history. [README](src/main/java/com/uber/cadence/samples/s3offload/README.md).
3336

3437
## Get the Samples
3538

@@ -143,17 +146,28 @@ In Cadence Web, open the workflow → **Query** tab → run query **`Signal`**,
143146

144147
### DataConverter Samples
145148

146-
Three samples (compression, encryption, S3 offload) demonstrating custom `DataConverter` implementations. One worker hosts all three on three task lists. See [src/main/java/com/uber/cadence/samples/dataconverter/README.md](src/main/java/com/uber/cadence/samples/dataconverter/README.md) for full details, encryption-key configuration, and S3 swap instructions.
149+
Three independent samples demonstrating custom `DataConverter` implementations. Each sample is self-contained in its own package with its own worker, starter, task list, and README. Pick one to run, or run all three in parallel — they share nothing.
147150

148-
Worker (hosts all three samples; prints per-sample stats banners on startup):
151+
#### Compression (gzip-over-JSON)
149152

150-
./gradlew -q execute -PmainClass=com.uber.cadence.samples.dataconverter.DataConverterWorker
153+
See [src/main/java/com/uber/cadence/samples/compression/README.md](src/main/java/com/uber/cadence/samples/compression/README.md).
151154

152-
Starters (pick one per run; each starts a new workflow execution and exits):
155+
./gradlew -q execute -PmainClass=com.uber.cadence.samples.compression.CompressionWorker
156+
./gradlew -q execute -PmainClass=com.uber.cadence.samples.compression.CompressionStarter
153157

154-
./gradlew -q execute -PmainClass=com.uber.cadence.samples.dataconverter.CompressionStarter
155-
./gradlew -q execute -PmainClass=com.uber.cadence.samples.dataconverter.EncryptionStarter
156-
./gradlew -q execute -PmainClass=com.uber.cadence.samples.dataconverter.S3OffloadStarter
158+
#### Encryption (AES-256-GCM)
159+
160+
See [src/main/java/com/uber/cadence/samples/encryption/README.md](src/main/java/com/uber/cadence/samples/encryption/README.md) for the `CADENCE_ENCRYPTION_KEY` env var.
161+
162+
./gradlew -q execute -PmainClass=com.uber.cadence.samples.encryption.EncryptionWorker
163+
./gradlew -q execute -PmainClass=com.uber.cadence.samples.encryption.EncryptionStarter
164+
165+
#### S3 / claim-check offload
166+
167+
See [src/main/java/com/uber/cadence/samples/s3offload/README.md](src/main/java/com/uber/cadence/samples/s3offload/README.md) for the AWS SDK swap-in instructions.
168+
169+
./gradlew -q execute -PmainClass=com.uber.cadence.samples.s3offload.S3OffloadWorker
170+
./gradlew -q execute -PmainClass=com.uber.cadence.samples.s3offload.S3OffloadStarter
157171

158172
### Trip Booking
159173

src/main/java/com/uber/cadence/samples/dataconverter/CompressedDataConverterWorkflow.java renamed to src/main/java/com/uber/cadence/samples/compression/CompressedDataConverterWorkflow.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* permissions and limitations under the License.
1616
*/
1717

18-
package com.uber.cadence.samples.dataconverter;
18+
package com.uber.cadence.samples.compression;
1919

2020
import com.uber.cadence.activity.ActivityMethod;
2121
import com.uber.cadence.activity.ActivityOptions;
@@ -31,7 +31,7 @@
3131
* Demonstrates gzip-over-JSON compression as a Cadence {@code DataConverter}. The workflow itself
3232
* is unchanged from a plain Cadence workflow — the compression is applied transparently to every
3333
* input, output, and activity parameter by {@link CompressedJsonDataConverter}, which is wired in
34-
* at the worker by {@link DataConverterWorker}.
34+
* at the worker by {@link CompressionWorker}.
3535
*
3636
* <p>The workflow takes no inputs and builds its own large payload internally so it can be started
3737
* from the Cadence CLI without bundling a custom converter into the caller.
@@ -40,6 +40,14 @@ public final class CompressedDataConverterWorkflow {
4040

4141
private CompressedDataConverterWorkflow() {}
4242

43+
/** Task list polled by {@link CompressionWorker}. */
44+
public static final String TASK_LIST = "data-compression";
45+
46+
/**
47+
* Registered workflow type, used for both {@code @WorkflowMethod} and CLI {@code workflow start}.
48+
*/
49+
public static final String WORKFLOW_TYPE = "CompressedDataConverterWorkflow";
50+
4351
// ---------------- POJOs ----------------
4452

4553
/**
@@ -293,9 +301,9 @@ private static String repeat(String s, int n) {
293301
public interface WorkflowIface {
294302

295303
@WorkflowMethod(
296-
name = DataConverterConstants.COMPRESSION_WORKFLOW_TYPE,
304+
name = WORKFLOW_TYPE,
297305
executionStartToCloseTimeoutSeconds = 60,
298-
taskList = DataConverterConstants.TASK_LIST_COMPRESSION
306+
taskList = TASK_LIST
299307
)
300308
LargePayload run();
301309
}

src/main/java/com/uber/cadence/samples/dataconverter/CompressedJsonDataConverter.java renamed to src/main/java/com/uber/cadence/samples/compression/CompressedJsonDataConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* permissions and limitations under the License.
1616
*/
1717

18-
package com.uber.cadence.samples.dataconverter;
18+
package com.uber.cadence.samples.compression;
1919

2020
import com.uber.cadence.converter.DataConverter;
2121
import com.uber.cadence.converter.DataConverterException;

src/main/java/com/uber/cadence/samples/dataconverter/CompressionStarter.java renamed to src/main/java/com/uber/cadence/samples/compression/CompressionStarter.java

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@
1515
* permissions and limitations under the License.
1616
*/
1717

18-
package com.uber.cadence.samples.dataconverter;
18+
package com.uber.cadence.samples.compression;
1919

2020
import com.uber.cadence.client.WorkflowClient;
21+
import com.uber.cadence.client.WorkflowClientOptions;
2122
import com.uber.cadence.client.WorkflowOptions;
23+
import com.uber.cadence.internal.compatibility.Thrift2ProtoAdapter;
24+
import com.uber.cadence.internal.compatibility.proto.serviceclient.IGrpcServiceStubs;
25+
import com.uber.cadence.samples.common.SampleConstants;
2226
import java.time.Duration;
2327
import java.util.UUID;
2428

@@ -43,10 +47,13 @@ private CompressionStarter() {}
4347

4448
public static void main(String[] args) {
4549
try {
46-
WorkflowClient client = DataConverterSupport.newWorkflowClient();
50+
WorkflowClient client =
51+
WorkflowClient.newInstance(
52+
new Thrift2ProtoAdapter(IGrpcServiceStubs.newInstance()),
53+
WorkflowClientOptions.newBuilder().setDomain(SampleConstants.DOMAIN).build());
4754
WorkflowOptions options =
4855
new WorkflowOptions.Builder()
49-
.setTaskList(DataConverterConstants.TASK_LIST_COMPRESSION)
56+
.setTaskList(CompressedDataConverterWorkflow.TASK_LIST)
5057
.setExecutionStartToCloseTimeout(Duration.ofMinutes(1))
5158
.setWorkflowId("compression-" + UUID.randomUUID())
5259
.build();
@@ -56,15 +63,45 @@ public static void main(String[] args) {
5663

5764
WorkflowClient.start(workflow::run);
5865
System.out.println(
59-
"Started CompressedDataConverterWorkflow on task list \""
60-
+ DataConverterConstants.TASK_LIST_COMPRESSION
66+
"Started "
67+
+ CompressedDataConverterWorkflow.WORKFLOW_TYPE
68+
+ " on task list \""
69+
+ CompressedDataConverterWorkflow.TASK_LIST
6170
+ "\".");
6271
System.exit(0);
6372
} catch (RuntimeException e) {
64-
if (DataConverterSupport.printHintIfDomainMissing(e)) {
73+
if (printHintIfDomainMissing(e)) {
6574
System.exit(1);
6675
}
6776
throw e;
6877
}
6978
}
79+
80+
/**
81+
* Prints a copy-paste hint when the Cadence error indicates the sample domain has not been
82+
* registered.
83+
*
84+
* @return true if {@code t} was a missing-domain error and a hint was printed (caller should
85+
* exit).
86+
*/
87+
static boolean printHintIfDomainMissing(Throwable t) {
88+
for (Throwable c = t; c != null; c = c.getCause()) {
89+
String m = c.getMessage();
90+
if (m != null && m.contains("Domain") && m.contains("does not exist")) {
91+
System.err.println();
92+
System.err.println(
93+
"Cadence reported that the domain \"" + SampleConstants.DOMAIN + "\" does not exist.");
94+
System.err.println("Register it once against your cluster, then run this again:");
95+
System.err.println();
96+
System.err.println(
97+
" ./gradlew -q execute -PmainClass=com.uber.cadence.samples.common.RegisterDomain");
98+
System.err.println();
99+
System.err.println("Or with Cadence CLI:");
100+
System.err.println(" cadence --domain " + SampleConstants.DOMAIN + " domain register");
101+
System.err.println();
102+
return true;
103+
}
104+
}
105+
return false;
106+
}
70107
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
7+
* use this file except in compliance with the License. A copy of the License is
8+
* located at
9+
*
10+
* http://aws.amazon.com/apache2.0
11+
*
12+
* or in the "license" file accompanying this file. This file is distributed on
13+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
14+
* express or implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*/
17+
18+
package com.uber.cadence.samples.compression;
19+
20+
import com.uber.cadence.client.WorkflowClient;
21+
import com.uber.cadence.client.WorkflowClientOptions;
22+
import com.uber.cadence.converter.DataConverter;
23+
import com.uber.cadence.converter.JsonDataConverter;
24+
import com.uber.cadence.internal.compatibility.Thrift2ProtoAdapter;
25+
import com.uber.cadence.internal.compatibility.proto.serviceclient.IGrpcServiceStubs;
26+
import com.uber.cadence.samples.common.SampleConstants;
27+
import com.uber.cadence.worker.Worker;
28+
import com.uber.cadence.worker.WorkerFactory;
29+
30+
/**
31+
* Hosts the gzip-compression sample worker. Constructs a {@link WorkflowClient} configured with
32+
* {@link CompressedJsonDataConverter} so every workflow input, output, and activity parameter is
33+
* transparently gzip-compressed in Cadence history. On startup it prints a stats banner showing the
34+
* before/after size of the sample payload so the benefit is visible at a glance.
35+
*/
36+
public final class CompressionWorker {
37+
38+
private CompressionWorker() {}
39+
40+
public static void main(String[] args) {
41+
DataConverter converter = new CompressedJsonDataConverter();
42+
WorkflowClient client =
43+
WorkflowClient.newInstance(
44+
new Thrift2ProtoAdapter(IGrpcServiceStubs.newInstance()),
45+
WorkflowClientOptions.newBuilder()
46+
.setDomain(SampleConstants.DOMAIN)
47+
.setDataConverter(converter)
48+
.build());
49+
50+
WorkerFactory factory = WorkerFactory.newInstance(client);
51+
Worker worker = factory.newWorker(CompressedDataConverterWorkflow.TASK_LIST);
52+
worker.registerWorkflowImplementationTypes(CompressedDataConverterWorkflow.WorkflowImpl.class);
53+
worker.registerActivitiesImplementations(new CompressedDataConverterWorkflow.ActivitiesImpl());
54+
factory.start();
55+
56+
printCompressionStats(converter);
57+
58+
System.out.println(
59+
"CompressionWorker listening on \""
60+
+ CompressedDataConverterWorkflow.TASK_LIST
61+
+ "\" (domain \""
62+
+ SampleConstants.DOMAIN
63+
+ "\").");
64+
65+
Runtime.getRuntime().addShutdownHook(new Thread(factory::shutdown));
66+
}
67+
68+
private static void printCompressionStats(DataConverter converter) {
69+
CompressedDataConverterWorkflow.LargePayload payload =
70+
CompressedDataConverterWorkflow.createLargePayload();
71+
byte[] originalJson = JsonDataConverter.getInstance().toData(payload);
72+
byte[] compressed = converter.toData(payload);
73+
int originalSize = originalJson == null ? 0 : originalJson.length;
74+
int compressedSize = compressed == null ? 0 : compressed.length;
75+
double pct = originalSize == 0 ? 0.0 : (1.0 - (double) compressedSize / originalSize) * 100.0;
76+
77+
System.out.println();
78+
System.out.println("=== Compression Sample Statistics ===");
79+
System.out.printf(
80+
"Original JSON size: %d bytes (%.2f KB)%n", originalSize, originalSize / 1024.0);
81+
System.out.printf(
82+
"Compressed size: %d bytes (%.2f KB)%n", compressedSize, compressedSize / 1024.0);
83+
System.out.printf("Compression ratio: %.2f%% reduction%n", pct);
84+
System.out.printf(
85+
"Space saved: %d bytes (%.2f KB)%n",
86+
originalSize - compressedSize, (originalSize - compressedSize) / 1024.0);
87+
System.out.printf(
88+
"Start workflow: cadence --domain %s workflow start --tl %s --workflow_type %s --et 60%n",
89+
SampleConstants.DOMAIN,
90+
CompressedDataConverterWorkflow.TASK_LIST,
91+
CompressedDataConverterWorkflow.WORKFLOW_TYPE);
92+
System.out.println("=====================================");
93+
System.out.println();
94+
}
95+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Compression DataConverter Sample
2+
3+
A custom Cadence [`DataConverter`](../../../../../../../../README.md) that JSON-encodes workflow data and then gzip-compresses the bytes. For repetitive JSON payloads this typically achieves 60-80% size reduction, lowering storage cost and bandwidth without changing any workflow or activity code. The decode path caps decompressed payloads (default 10 MB) so a malformed input cannot drive unbounded memory growth.
4+
5+
- **Task list:** `data-compression`
6+
- **Workflow type:** `CompressedDataConverterWorkflow`
7+
8+
## Prerequisites
9+
10+
1. Cadence server running (e.g. Docker Compose from the [Cadence repo](https://github.com/uber/cadence)).
11+
2. From the repo root, build: `./gradlew build`.
12+
13+
### Register the domain (required once per cluster)
14+
15+
```bash
16+
./gradlew -q execute -PmainClass=com.uber.cadence.samples.common.RegisterDomain
17+
```
18+
19+
Or with the Cadence CLI:
20+
21+
```bash
22+
cadence --domain samples-domain domain register
23+
```
24+
25+
## Run the worker (terminal 1)
26+
27+
The worker prints a compression statistics banner showing the before/after sizes of the sample payload, then begins polling the `data-compression` task list:
28+
29+
```bash
30+
./gradlew -q execute -PmainClass=com.uber.cadence.samples.compression.CompressionWorker
31+
```
32+
33+
## Start a workflow (terminal 2)
34+
35+
```bash
36+
./gradlew -q execute -PmainClass=com.uber.cadence.samples.compression.CompressionStarter
37+
```
38+
39+
Or from the Cadence CLI:
40+
41+
```bash
42+
cadence --domain samples-domain \
43+
workflow start \
44+
--workflow_type CompressedDataConverterWorkflow \
45+
--tl data-compression \
46+
--et 60
47+
```
48+
49+
## How it works
50+
51+
- `toData`: JSON-encode the arguments with the standard `JsonDataConverter`, then write the bytes through `java.util.zip.GZIPOutputStream`.
52+
- `fromData` / `fromDataArray`: decompress through `GZIPInputStream` with a configurable max output cap, then delegate to the standard `JsonDataConverter`.
53+
54+
## Source layout
55+
56+
| File | Purpose |
57+
|------|---------|
58+
| [`CompressedJsonDataConverter.java`](CompressedJsonDataConverter.java) | The custom `DataConverter` |
59+
| [`CompressedDataConverterWorkflow.java`](CompressedDataConverterWorkflow.java) | Workflow + activity + sample `LargePayload` POJOs and generator |
60+
| [`CompressionWorker.java`](CompressionWorker.java) | Worker main; wires the converter into `WorkflowClientOptions` and prints the stats banner |
61+
| [`CompressionStarter.java`](CompressionStarter.java) | Thin async starter |

0 commit comments

Comments
 (0)