|
| 1 | +# Durable Task Export History (Java) |
| 2 | + |
| 3 | +Durable, resumable export of **terminal orchestration history** to Azure Blob Storage for the Durable Task Java |
| 4 | +SDK — for compliance, audit, and offline analysis before instances age out of the task hub. |
| 5 | + |
| 6 | +This module is at parity with the .NET `Microsoft.DurableTask.ExportHistory` (preview) feature: a checkpointed |
| 7 | +entity + orchestrator that pages terminal instances by completion window, fans out per-instance export activities, |
| 8 | +and uploads serialized history (gzipped JSONL by default) to a customer-owned blob container. |
| 9 | + |
| 10 | +> **Status:** preview (`0.1.0`). |
| 11 | +
|
| 12 | +## Install |
| 13 | + |
| 14 | +Add the module dependency alongside the core `client` (and your Durable Task Scheduler extension): |
| 15 | + |
| 16 | +```groovy |
| 17 | +implementation 'com.microsoft:durabletask-exporthistory:0.1.0' |
| 18 | +``` |
| 19 | + |
| 20 | +The export activities upload to Azure Blob Storage via `azure-storage-blob`. If you authenticate with a managed |
| 21 | +identity, also add `com.azure:azure-identity` to your application. |
| 22 | + |
| 23 | +## Usage |
| 24 | + |
| 25 | +```java |
| 26 | +// Storage destination for exported history (Azure Blob) |
| 27 | +ExportHistoryStorageOptions storage = new ExportHistoryStorageOptions() |
| 28 | + .setConnectionString(System.getenv("EXPORT_HISTORY_STORAGE_CONNECTION_STRING")) |
| 29 | + .setContainerName("orchestration-history") |
| 30 | + .setPrefix("exports/"); // optional |
| 31 | + // identity alt: .setAccountUri(uri).setCredential(new DefaultAzureCredentialBuilder().build()) |
| 32 | + |
| 33 | +// Build a client first — the export activities need a client to the same backend. |
| 34 | +DurableTaskGrpcClientBuilder clientBuilder = new DurableTaskGrpcClientBuilder(); |
| 35 | +DurableTaskSchedulerClientExtensions.useDurableTaskScheduler(clientBuilder, dtsConn); |
| 36 | +DurableTaskClient client = clientBuilder.build(); |
| 37 | + |
| 38 | +// Worker: register the export entity + orchestrators + activities (uploads run here). |
| 39 | +DurableTaskGrpcWorkerBuilder workerBuilder = new DurableTaskGrpcWorkerBuilder(); |
| 40 | +DurableTaskSchedulerWorkerExtensions.useDurableTaskScheduler(workerBuilder, dtsConn); |
| 41 | +ExportHistoryWorkerExtensions.useExportHistory(workerBuilder, storage, client); |
| 42 | + |
| 43 | +// Client: obtain an ExportHistoryClient bound to the destination. |
| 44 | +ExportHistoryClient export = ExportHistoryClientExtensions.useExportHistory(client, storage); |
| 45 | + |
| 46 | +// Create a job: archive everything completed in a window. |
| 47 | +ExportHistoryJobClient job = export.createJob(new ExportJobCreationOptions("nightly-archive") |
| 48 | + .setMode(ExportMode.BATCH) |
| 49 | + .setCompletedTimeFrom(Instant.parse("2026-06-01T00:00:00Z")) |
| 50 | + .setCompletedTimeTo(Instant.parse("2026-06-25T00:00:00Z")) |
| 51 | + .setRuntimeStatus(List.of(OrchestrationRuntimeStatus.COMPLETED)) |
| 52 | + .setMaxInstancesPerBatch(200)); // 1–1000, default 100 |
| 53 | + |
| 54 | +// Inspect progress. |
| 55 | +ExportJobDescription d = job.describe(); |
| 56 | +System.out.println(d.getStatus() + " exported=" + d.getExportedInstances()); |
| 57 | +``` |
| 58 | + |
| 59 | +### Modes |
| 60 | + |
| 61 | +- **BATCH** — exports a fixed completion-time window and completes. Requires `completedTimeFrom` and |
| 62 | + `completedTimeTo` (the upper bound must not be in the future). |
| 63 | +- **CONTINUOUS** — tails newly-completed terminal instances on a 1-minute idle loop until the job is deleted. |
| 64 | + |
| 65 | +### Terminal statuses only |
| 66 | + |
| 67 | +Export supports terminal orchestration statuses only: `COMPLETED`, `FAILED`, `TERMINATED`. When no status filter is |
| 68 | +supplied, all three are exported. |
| 69 | + |
| 70 | +## Required settings |
| 71 | + |
| 72 | +- **DTS connection** — the one your app already uses; no new value. |
| 73 | +- **Blob destination** — a container name plus either a storage **connection string** or **identity** |
| 74 | + (`AccountUri` + `TokenCredential`). Prefix and format (JSONL + gzip) are optional with defaults. The storage |
| 75 | + secret is held worker-side, not persisted in task-hub state. |
| 76 | +- **Permissions** — the storage credential needs blob write on the container; the DTS credential needs |
| 77 | + orchestration read. |
| 78 | + |
| 79 | +## Export format |
| 80 | + |
| 81 | +Each blob holds the instance's full history, and the **blob body is byte-for-byte identical to the .NET |
| 82 | +`Microsoft.DurableTask.ExportHistory` output** (pinned by a test against golden output captured from |
| 83 | +`Microsoft.Azure.DurableTask.Core`): |
| 84 | + |
| 85 | +- **JSONL** (default, gzipped) is one JSON object per line; **JSON** is a single array. |
| 86 | +- Each event is `{"eventType": "...", <type-specific fields>, "eventId": N, "isPlayed": false, "timestamp": "..."}`. |
| 87 | +- camelCase field names, null fields omitted, empty maps as `{}`, enum values in PascalCase (e.g. `"Completed"`), |
| 88 | + timestamps as trimmed ISO-8601 ending in `Z`, and the same HTML-safe string escaping (`"` → `\u0022`, |
| 89 | + `& < > ' +` and all non-ASCII → `\uXXXX`). |
| 90 | + |
| 91 | +Blob **names**: a lowercase-hex SHA-256 of `"<completedTimestamp>|<instanceId>"` plus the format extension. |
| 92 | + |
| 93 | +## Backend requirement |
| 94 | + |
| 95 | +The export feature relies on the `ListInstanceIds` and `StreamInstanceHistory` gRPC operations. Managed DTS serves |
| 96 | +both; the emulator / self-hosted sidecar needs **≥ v0.4.22**. Against an older backend, a raw gRPC `UNIMPLEMENTED` |
| 97 | +surfaces (matching .NET). |
| 98 | + |
| 99 | +## Validating the export |
| 100 | + |
| 101 | +Locally, with the DTS emulator and Azurite: |
| 102 | + |
| 103 | +1. Start the backends: |
| 104 | + ``` |
| 105 | + docker run --name durabletask-emulator -p 4001:8080 -d mcr.microsoft.com/dts/dts-emulator:latest |
| 106 | + docker run --name azurite -p 10000:10000 -d mcr.microsoft.com/azure-storage/azurite azurite-blob --blobHost 0.0.0.0 |
| 107 | + ``` |
| 108 | +2. Point the app at them: DTS connection `Endpoint=http://localhost:4001;Authentication=None`, storage = the Azurite |
| 109 | + dev connection string, container `orchestration-history`. |
| 110 | +3. Run an orchestration to a terminal state, then create a `BATCH` export job whose window covers its completion time. |
| 111 | +4. Confirm the job reaches `COMPLETED` and inspect progress: |
| 112 | + ```java |
| 113 | + ExportJobDescription d = job.describe(); |
| 114 | + // d.getStatus() == ExportJobStatus.COMPLETED, d.getExportedInstances() >= 1 |
| 115 | + ``` |
| 116 | +5. Download the blob from the container (gunzip for JSONL) and inspect it. Every line carries an `eventType` |
| 117 | + discriminator, `isPlayed:false`, and a trailing `timestamp`. |
| 118 | + |
| 119 | +## Sample |
| 120 | + |
| 121 | +See [`HistoryExportSample`](../samples/src/main/java/io/durabletask/samples/HistoryExportSample.java): |
| 122 | + |
| 123 | +``` |
| 124 | +./gradlew :samples:runHistoryExportSample |
| 125 | +``` |
0 commit comments