|
| 1 | +# Migrating from 1.x to 2.x |
| 2 | + |
| 3 | +This guide helps teams upgrade from the `1.x` line to `2.x`. |
| 4 | + |
| 5 | +It focuses on the breaking changes introduced since `v1.2.1`, the most recent `1.x` release at the time of writing. If you are already on a newer `1.x` patch, the same migration steps still apply. |
| 6 | + |
| 7 | +## Upgrade Checklist |
| 8 | + |
| 9 | +- Replace `StepConfig.builder().semantics(...)` with the correct `2.x` equivalent for your intended behavior. |
| 10 | +- Update log queries, parsers, and dashboards to use `executionArn`, `operationId`, and `operationName`. |
| 11 | +- Rebaseline replay-sensitive logging and plugin behavior for child contexts, especially in `parallel()`, `map()`, and nested `runInChildContext(...)` workflows. |
| 12 | +- Update any code that expected validation failures to throw `IllegalDurableOperationException`. |
| 13 | +- Verify that custom `SerDes` implementations can deserialize SDK-managed values immediately after serialization, or explicitly opt out of the extra validation pass. |
| 14 | + |
| 15 | +Useful searches before upgrading: |
| 16 | + |
| 17 | +```bash |
| 18 | +rg -n "\.semantics\(" . |
| 19 | +rg -n "durableExecutionArn|contextId|contextName" . |
| 20 | +rg -n "replay|isReplayingChildren|onOperationStart|onOperationEnd" sdk examples |
| 21 | +``` |
| 22 | + |
| 23 | +## 1. Rename `StepConfig.semantics(...)` to `semanticsPerRetry(...)` |
| 24 | + |
| 25 | +The deprecated `semantics(...)` builder method is removed in `2.x`. |
| 26 | + |
| 27 | +This is not always a one-line rename. In `1.x`, `semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY)` behaved like `2.x` `semanticsPerRetry(StepSemantics.AT_MOST_ONCE_PER_RETRY)` plus a `NO_RETRY` policy. |
| 28 | + |
| 29 | +Before: |
| 30 | + |
| 31 | +```java |
| 32 | +var config = StepConfig.builder() |
| 33 | + .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY) |
| 34 | + .build(); |
| 35 | +``` |
| 36 | + |
| 37 | +Naive rename: |
| 38 | + |
| 39 | +```java |
| 40 | +var config = StepConfig.builder() |
| 41 | + .semanticsPerRetry(StepSemantics.AT_MOST_ONCE_PER_RETRY) |
| 42 | + .build(); |
| 43 | +``` |
| 44 | + |
| 45 | +Behavior-preserving migration for old `1.x` `AT_MOST_ONCE_PER_RETRY` usage: |
| 46 | + |
| 47 | +```java |
| 48 | +var config = StepConfig.builder() |
| 49 | + .semanticsPerRetry(StepSemantics.AT_MOST_ONCE_PER_RETRY) |
| 50 | + .retryStrategy(RetryStrategies.Presets.NO_RETRY) |
| 51 | + .build(); |
| 52 | +``` |
| 53 | + |
| 54 | +Migration rules: |
| 55 | + |
| 56 | +- Old `semantics(AT_LEAST_ONCE_PER_RETRY)` maps directly to `semanticsPerRetry(AT_LEAST_ONCE_PER_RETRY)`. |
| 57 | +- Old `semantics(AT_MOST_ONCE_PER_RETRY)` should usually become `semanticsPerRetry(AT_MOST_ONCE_PER_RETRY)` plus `retryStrategy(RetryStrategies.Presets.NO_RETRY)` if you want to preserve the old `1.x` behavior. |
| 58 | +- If you intentionally want the corrected `2.x` per-retry semantics, use `semanticsPerRetry(AT_MOST_ONCE_PER_RETRY)` without forcing `NO_RETRY`. |
| 59 | + |
| 60 | +What to update: |
| 61 | + |
| 62 | +- Step configuration builders |
| 63 | +- Shared helper methods and wrapper APIs |
| 64 | +- Tests that asserted on `config.semantics()` |
| 65 | + |
| 66 | +If you expose your own configuration layer on top of the SDK, rename it now so downstream users do not inherit the removed `semantics` name. |
| 67 | + |
| 68 | +## 2. Update logger MDC field names |
| 69 | + |
| 70 | +The main user-visible breaking change in `2.x` is the logger metadata rename so Java matches the other durable execution SDKs. |
| 71 | + |
| 72 | +Before: |
| 73 | + |
| 74 | +```json |
| 75 | +{ |
| 76 | + "durableExecutionArn": "arn:aws:lambda:...", |
| 77 | + "contextId": "child-context-id", |
| 78 | + "contextName": "inventory-check" |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +After: |
| 83 | + |
| 84 | +```json |
| 85 | +{ |
| 86 | + "executionArn": "arn:aws:lambda:...", |
| 87 | + "operationId": "child-context-id", |
| 88 | + "operationName": "inventory-check" |
| 89 | +} |
| 90 | +``` |
| 91 | + |
| 92 | +What to update: |
| 93 | + |
| 94 | +- CloudWatch Logs Insights queries |
| 95 | +- Metric filters and alarms |
| 96 | +- Log processors and index mappings |
| 97 | +- Dashboards and saved searches |
| 98 | +- Any custom JSON or MDC parsing |
| 99 | + |
| 100 | +Important: this rename only applies to logger MDC fields. The SDK API still uses `durableExecutionArn` in places such as `DurableExecutionInput` and plugin invocation records. Do not mechanically rename every `durableExecutionArn` identifier in your codebase. |
| 101 | + |
| 102 | +### Mixed-version rollout query |
| 103 | + |
| 104 | +If you need one query that works during a rolling upgrade, use `coalesce(...)`: |
| 105 | + |
| 106 | +```sql |
| 107 | +fields coalesce(executionArn, durableExecutionArn) as executionArn, |
| 108 | + coalesce(operationId, contextId) as operationId, |
| 109 | + coalesce(operationName, contextName) as operationName |
| 110 | +| filter executionArn = "arn:aws:lambda:..." |
| 111 | +``` |
| 112 | + |
| 113 | +### Temporary compatibility option |
| 114 | + |
| 115 | +If you need to preserve the old MDC keys for a short rollout window, configure `LoggerConfig` with `oldKeyNames=true`: |
| 116 | + |
| 117 | +```java |
| 118 | +@Override |
| 119 | +protected DurableConfig createConfiguration() { |
| 120 | + return DurableConfig.builder() |
| 121 | + .withLoggerConfig(new LoggerConfig(true, true)) |
| 122 | + .build(); |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +That can reduce migration risk while dashboards and parsers are being updated, but the recommended end state for `2.x` is the new key set. |
| 127 | + |
| 128 | +## 3. Rebaseline replay-sensitive logging and replay APIs |
| 129 | + |
| 130 | +`2.x` uses per-context replay state for logging and plugin callbacks instead of relying on a single global replay view. |
| 131 | + |
| 132 | +What changes in practice: |
| 133 | + |
| 134 | +- Replay suppression is more accurate for child contexts. |
| 135 | +- Concurrent child contexts no longer look like fresh execution when that child is still replaying. |
| 136 | +- Custom plugins see replay metadata that better reflects the current child context. |
| 137 | +- `StepContext` does not expose replay state anymore. |
| 138 | +- Step logs are attempt-based and are never replay-suppressed. |
| 139 | + |
| 140 | +API impact: |
| 141 | + |
| 142 | +- `isReplaying()` now belongs on `DurableContext`, not `BaseContext`. |
| 143 | +- Code that assumed every context type had `isReplaying()` needs to be updated. |
| 144 | +- If you were checking replay state inside step lambdas, move that logic to the surrounding `DurableContext` or redesign it around attempt-based step behavior. |
| 145 | + |
| 146 | +What to review: |
| 147 | + |
| 148 | +- Tests that count log lines across replays |
| 149 | +- Dashboards that alert on replay log volume |
| 150 | +- Custom plugins using replay-sensitive hooks or `isReplayingChildren` |
| 151 | +- Nested workflows that use `parallel()`, `map()`, or `runInChildContext(...)` |
| 152 | +- Any code that called `isReplaying()` on `BaseContext` or `StepContext` |
| 153 | + |
| 154 | +The most common upgrade symptom here is not a compile error. It is changed log volume or changed replay-related assertions in tests. |
| 155 | + |
| 156 | +## 4. Update exception handling for context validation failures |
| 157 | + |
| 158 | +In `2.x`, invalid context usage now throws `IllegalStateException` instead of `IllegalDurableOperationException`. |
| 159 | + |
| 160 | +This affects validation failures such as nested durable operations from unsupported thread types, for example calling a blocking durable operation from within a step execution. |
| 161 | + |
| 162 | +What to update: |
| 163 | + |
| 164 | +- Unit and integration tests that assert exception types |
| 165 | +- Error classification logic |
| 166 | +- Alerting or telemetry that treated `IllegalDurableOperationException` as an SDK defect signal |
| 167 | +- Runbooks that distinguished user misuse from SDK or platform failures |
| 168 | + |
| 169 | +Before: |
| 170 | + |
| 171 | +```java |
| 172 | +assertThrows(IllegalDurableOperationException.class, future::get); |
| 173 | +``` |
| 174 | + |
| 175 | +After: |
| 176 | + |
| 177 | +```java |
| 178 | +assertThrows(IllegalStateException.class, future::get); |
| 179 | +``` |
| 180 | + |
| 181 | +## 5. Validate serialization round trips earlier |
| 182 | + |
| 183 | +`2.x` validates serialized results and exceptions with an immediate deserialize pass before checkpointing by default. |
| 184 | + |
| 185 | +What changes in practice: |
| 186 | + |
| 187 | +- Serialization problems now fail on first execution instead of surfacing later on replay. |
| 188 | +- Custom `SerDes` implementations must be able to deserialize SDK-managed values they serialize. |
| 189 | +- Child-context results are validated consistently, including virtual child-context paths. |
| 190 | + |
| 191 | +This is usually a correctness improvement, but it can surface previously hidden `SerDes` bugs during upgrade. |
| 192 | + |
| 193 | +### New opt-out configuration |
| 194 | + |
| 195 | +If your workload is very performance-sensitive and you need to skip the extra validation deserialize pass, you can opt out: |
| 196 | + |
| 197 | +```java |
| 198 | +@Override |
| 199 | +protected DurableConfig createConfiguration() { |
| 200 | + return DurableConfig.builder() |
| 201 | + .withSerializationRoundTripValidation(false) |
| 202 | + .build(); |
| 203 | +} |
| 204 | +``` |
| 205 | + |
| 206 | +Use that carefully: |
| 207 | + |
| 208 | +- Disabling validation can hide serialization bugs until replay. |
| 209 | +- Custom `SerDes` implementations are still expected to be round-trip safe. |
| 210 | + |
| 211 | +## Recommended Validation After Upgrading |
| 212 | + |
| 213 | +1. Build and run your test suite with the `2.x` dependency. |
| 214 | +2. Exercise one workflow that replays after `wait()`, `waitForCondition()`, or callback resume. |
| 215 | +3. Exercise one workflow with child contexts or concurrency. |
| 216 | +4. Verify that your log queries and dashboards still resolve the correct execution and operation identifiers. |
| 217 | +5. Verify any code that relied on `BaseContext.isReplaying()` or replay suppression inside step lambdas. |
| 218 | +6. If you use custom `SerDes`, run one workflow that checkpoints both a successful result and an exception payload. |
| 219 | +7. If you use plugins, verify replay-sensitive metadata in at least one replayed child-context scenario. |
| 220 | + |
| 221 | +## Summary |
| 222 | + |
| 223 | +Most upgrades are straightforward: |
| 224 | + |
| 225 | +- `semantics(...)` becomes `semanticsPerRetry(...)`, and old `AT_MOST_ONCE_PER_RETRY` users may also need `RetryStrategies.Presets.NO_RETRY` to preserve `1.x` behavior |
| 226 | +- Logger metadata moves to `executionArn`, `operationId`, and `operationName` |
| 227 | +- Replay-sensitive logging becomes per-context, `isReplaying()` moves to `DurableContext`, and step logs are no longer replay-suppressed |
| 228 | +- Validation failures now throw `IllegalStateException` |
| 229 | +- Serialization round-trip problems surface earlier by default, with an opt-out via `withSerializationRoundTripValidation(false)` |
| 230 | + |
| 231 | +If you update those areas first, the `1.x` to `2.x` migration should be low risk. |
0 commit comments