Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/notify_slack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
steps:
- name: Send issue notification to Slack
if: github.event_name == 'issues'
uses: slackapi/slack-github-action@v3.0.1
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL_ISSUE }}
webhook-type: incoming-webhook
Expand All @@ -27,7 +27,7 @@ jobs:

- name: Send pull request notification to Slack
if: github.event_name == 'pull_request_target'
uses: slackapi/slack-github-action@v3.0.1
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL_PR }}
webhook-type: incoming-webhook
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ for details.
- [AWS Lambda durable functions Documentation](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)
- [JavaScript SDK Repository](https://github.com/aws/aws-durable-execution-sdk-js)
- [Python SDK Repository](https://github.com/aws/aws-durable-execution-sdk-python)
- [Java SDK Repository](https://github.com/aws/aws-durable-execution-sdk-java)

## Feedback & Support

Expand Down
63 changes: 58 additions & 5 deletions aws-lambda-durable-functions-power/POWER.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Before using AWS Lambda durable functions, verify:
2. **Runtime environment** is ready:
- For TypeScript/JavaScript: Node.js 22+ (`node --version`)
- For Python: Python 3.11+ (`python --version`. Note that currently only Lambda runtime environments 3.13+ come with the Durable Execution SDK pre-installed. 3.11 is the min supported Python version by the Durable SDK itself, however, you could use OCI to bring your own container image with your own Python runtime + Durable SDK.)
- For Java: Java 17+ (`java --version`)

3. **Deployment capability** exists (one of):
- AWS SAM CLI (`sam --version`) 1.153.1 or higher
Expand All @@ -40,7 +41,8 @@ Before using AWS Lambda durable functions, verify:
## Step 2: Check user and project preferences

Ask which IaC framework to use for new projects.
Ask which programming language to use if unclear, clarify between JavaScript and TypeScript if necessary.
Ask which programming language to use. Supported languages are: **TypeScript**, **JavaScript**, **Python**, and **Java**. If the user says JavaScript or TypeScript, clarify between the two if necessary.
**IMPORTANT**: Only install the SDK and set up the project for the language the user chose. Do NOT install SDKs or create project files for other languages.
Ask to create a git repo for projects if one doesn't exist already.

### Error Scenarios
Expand All @@ -59,6 +61,8 @@ Ask to create a git repo for projects if one doesn't exist already.

### Step 3: Install SDK

Install **only** the SDK for the language the user selected in Step 2.

**For TypeScript/JavaScript:**

```bash
Expand All @@ -73,6 +77,24 @@ pip install aws-durable-execution-sdk-python
pip install aws-durable-execution-sdk-python-testing
```

**For Java (Maven):**

```xml
<dependency>
<groupId>software.amazon.lambda.durable</groupId>
<artifactId>aws-durable-execution-sdk-java</artifactId>
<version>1.0.0</version>
</dependency>

<!-- Testing utilities -->
<dependency>
<groupId>software.amazon.lambda.durable</groupId>
<artifactId>aws-durable-execution-sdk-java-testing</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
```

## When to Load Reference Files

Load the appropriate reference file based on what the user is working on:
Expand Down Expand Up @@ -115,12 +137,25 @@ def handler(event: dict, context: DurableContext) -> dict:
return result
```

**Java:**

```java
public class MyHandler extends DurableHandler<MyInput, MyOutput> {
@Override
public MyOutput handleRequest(MyInput input, DurableContext ctx) {
var result = ctx.step("process", Result.class,
stepCtx -> processData(input));
return new MyOutput(result);
}
}
```

### Critical Rules

1. **All non-deterministic code MUST be in steps** (Date.now, Math.random, API calls)
1. **All non-deterministic code MUST be in steps** (Date.now, Math.random, UUID.randomUUID, API calls)
2. **Cannot nest durable operations** - use `runInChildContext` to group operations
3. **Closure mutations are lost on replay** - return values from steps
4. **Side effects outside steps repeat** - use `context.logger` (replay-aware)
4. **Side effects outside steps repeat** - use `context.logger` / `ctx.getLogger()` (replay-aware)

### Python API Differences

Expand All @@ -131,6 +166,22 @@ The Python SDK differs from TypeScript in several key areas:
- **Exceptions**: `ExecutionError` (permanent), `InvocationError` (transient), `CallbackError` (callback failures)
- **Testing**: Use `DurableFunctionTestRunner` class directly - instantiate with handler, use context manager, call `run(input=...)`

### Java API Differences

The Java SDK differs from TypeScript/Python in several key areas:

- **Handler**: Extend `DurableHandler<I, O>` and implement `handleRequest(I input, DurableContext ctx)`
- **Steps**: `ctx.step("name", ResultType.class, stepCtx -> ...)` — type class required for deserialization
- **Generic types**: Use `TypeToken` for parameterized types: `ctx.step("name", new TypeToken<List<User>>() {}, stepCtx -> ...)`
- **Wait**: `ctx.wait("name", Duration.ofMinutes(5))` — uses `java.time.Duration`
- **Async**: `stepAsync()`, `waitAsync()`, `mapAsync()`, `runInChildContextAsync()` return `DurableFuture<T>`
- **Callbacks**: `ctx.createCallback("name", Type.class)` returns `DurableCallbackFuture<T>`; or use `ctx.waitForCallback()`
- **Map**: `ctx.map("name", items, Type.class, (item, index, childCtx) -> ...)` with `MapFunction<I, O>` interface
- **Configuration**: Override `createConfiguration()` to return `DurableConfig` for custom SerDes, thread pools, Lambda client
- **Exceptions**: `StepFailedException`, `StepInterruptedException`, `CallbackTimeoutException`, `CallbackFailedException`, `WaitForConditionFailedException`
- **Testing**: `LocalDurableTestRunner.create(InputType.class, handler)` with `runUntilComplete(input)` and `getOperation("name")`
- **Logging**: `ctx.getLogger()` returns `DurableLogger` (SLF4J MDC-based, replay-aware)

### Invocation Requirements

Durable functions **require qualified ARNs** (version, alias, or `$LATEST`):
Expand Down Expand Up @@ -163,10 +214,10 @@ See here: https://docs.aws.amazon.com/lambda/latest/dg/durable-security.html

When writing or reviewing durable function code, ALWAYS check for these replay model violations:

1. **Non-deterministic code outside steps**: `Date.now()`, `Math.random()`, UUID generation, API calls, database queries must all be inside steps
1. **Non-deterministic code outside steps**: `Date.now()`, `Math.random()`, `UUID.randomUUID()`, API calls, database queries must all be inside steps
2. **Nested durable operations in step functions**: Cannot call `context.step()`, `context.wait()`, or `context.invoke()` inside a step function — use `context.runInChildContext()` instead
3. **Closure mutations that won't persist**: Variables mutated inside steps are NOT preserved across replays — return values from steps instead
4. **Side effects outside steps that repeat on replay**: Use `context.logger` for logging (it is replay-aware and deduplicates automatically)
4. **Side effects outside steps that repeat on replay**: Use `context.logger` / `ctx.getLogger()` for logging (it is replay-aware and deduplicates automatically)

When implementing or modifying tests for durable functions, ALWAYS verify:

Expand All @@ -175,10 +226,12 @@ When implementing or modifying tests for durable functions, ALWAYS verify:
3. Replay behavior is tested with multiple invocations
4. TypeScript: Use `LocalDurableTestRunner` for local testing
5. Python: Use `DurableFunctionTestRunner` class directly
6. Java: Use `LocalDurableTestRunner.create(InputType.class, handler)` with `runUntilComplete(input)`

## Resources

- [AWS Lambda durable functions Documentation](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)
- [JavaScript SDK Repository](https://github.com/aws/aws-durable-execution-sdk-js)
- [Python SDK Repository](https://github.com/aws/aws-durable-execution-sdk-python)
- [Java SDK Repository](https://github.com/aws/aws-durable-execution-sdk-java)
- [IAM Policy Reference](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSLambdaBasicDurableExecutionRolePolicy.html)
1 change: 1 addition & 0 deletions aws-lambda-durable-functions-power/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ When you mention these keywords, Kiro will automatically load this power:
- [AWS Lambda durable functions Documentation](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)
- [JavaScript SDK Repository](https://github.com/aws/aws-durable-execution-sdk-js)
- [Python SDK Repository](https://github.com/aws/aws-durable-execution-sdk-python)
- [Java SDK Repository](https://github.com/aws/aws-durable-execution-sdk-java)
- [Kiro Powers Documentation](https://kiro.dev/docs/powers/create/)

## License
Expand Down
49 changes: 49 additions & 0 deletions aws-lambda-durable-functions-power/steering/advanced-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,32 @@ def handler(event: dict, context: DurableContext) -> str:
context.logger.debug('Tool result added', extra={'tool': tool['name']})
```

**Java:**

```java
public class AIAgentHandler extends DurableHandler<AgentInput, String> {
@Override
public String handleRequest(AgentInput input, DurableContext ctx) {
ctx.getLogger().info("Starting AI agent: {}", input.getPrompt());
var messages = new ArrayList<Map<String, String>>();
messages.add(Map.of("role", "user", "content", input.getPrompt()));

while (true) {
var result = ctx.step("invoke-model", AIResponse.class,
stepCtx -> invokeAIModel(messages));

if (result.getTool() == null) return result.getResponse();

var toolResult = ctx.step("execute-tool-" + result.getTool().getName(),
String.class,
stepCtx -> executeTool(result.getTool(), result.getResponse()));

messages.add(Map.of("role", "assistant", "content", toolResult));
}
}
}
```

## Step Semantics Deep Dive

### AtMostOncePerRetry vs AtLeastOncePerRetry
Expand Down Expand Up @@ -267,6 +293,29 @@ const result = await context.step(
console.log(result.createdAt instanceof Date); // true
```

### Java Custom SerDes

```java
// Java uses Jackson by default — POJOs serialize automatically
// For custom serialization, implement the SerDes interface:
public interface SerDes {
String serialize(Object value);
<T> T deserialize(String data, Class<T> type);
<T> T deserialize(String data, TypeToken<T> typeToken);
}

// Per-step custom SerDes
var result = ctx.step("fetch-data", ComplexType.class,
stepCtx -> fetchComplexData(),
StepConfig.builder().serDes(new MyCustomSerDes()).build());

// Global custom SerDes via DurableConfig
@Override
protected DurableConfig createConfiguration() {
return DurableConfig.builder().withSerDes(new MyCustomSerDes()).build();
}
```

### Complex Object Graphs

**TypeScript:**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,24 @@ results.throw_if_error()
all_results = results.get_results()
```

**Java:**

```java
var items = List.of("order-1", "order-2", "order-3", "order-4", "order-5");
var result = ctx.map("process-items", items, OrderResult.class,
(item, index, childCtx) -> {
return childCtx.step("process-" + index, OrderResult.class,
stepCtx -> orderService.process(item));
},
MapConfig.builder()
.maxConcurrency(3)
.completionConfig(CompletionConfig.minSuccessful(4))
.build());

assertTrue(result.allSucceeded());
var allResults = result.results();
```

## Parallel Operations

Run heterogeneous operations concurrently:
Expand Down Expand Up @@ -113,6 +131,22 @@ results = context.parallel(
user, orders, preferences = results.get_results()
```

**Java (using async child contexts):**

```java
var futureA = ctx.runInChildContextAsync("fetch-user", User.class,
child -> child.step("fetch", User.class, stepCtx -> fetchUser(userId)));
var futureB = ctx.runInChildContextAsync("fetch-orders", OrderList.class,
child -> child.step("fetch", OrderList.class, stepCtx -> fetchOrders(userId)));
var futureC = ctx.runInChildContextAsync("fetch-prefs", Prefs.class,
child -> child.step("fetch", Prefs.class, stepCtx -> fetchPreferences(userId)));

var results = DurableFuture.allOf(futureA, futureB, futureC);
User user = futureA.get();
OrderList orders = futureB.get();
Prefs prefs = futureC.get();
```

## Completion Policies

### Minimum Successful
Expand Down
4 changes: 2 additions & 2 deletions aws-lambda-durable-functions-power/steering/deployment-iac.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Resources:
Type: AWS::Lambda::Function
Properties:
FunctionName: myDurableFunction
Runtime: nodejs24.x # or python3.14
Runtime: nodejs24.x # or python3.14 (see Java SAM section below for Java)
Handler: index.handler
Role: !GetAtt DurableFunctionRole.Arn
Code:
Expand Down Expand Up @@ -89,7 +89,7 @@ export class DurableFunctionStack extends cdk.Stack {
super(scope, id, props);

const durableFunction = new lambda.Function(this, 'DurableFunction', {
runtime: lambda.Runtime.NODEJS_24_X, // or PYTHON_3_14
runtime: lambda.Runtime.NODEJS_24_X, // or PYTHON_3_14 (see Java SAM section for Java)
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
durableConfig: {
Expand Down
75 changes: 75 additions & 0 deletions aws-lambda-durable-functions-power/steering/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ result = context.step(
)
```

**Java:**

```java
var result = ctx.step("api-call", Response.class,
stepCtx -> callAPI(),
StepConfig.builder()
.retryStrategy(RetryStrategies.exponentialBackoff(
5, Duration.ofSeconds(1), Duration.ofSeconds(60), 2.0, JitterStrategy.FULL))
.build());
```

## Custom Retry Logic

**TypeScript:**
Expand Down Expand Up @@ -100,6 +111,20 @@ def custom_retry(error: Exception, attempt: int) -> RetryDecision:
return RetryDecision(should_retry=False)
```

**Java:**

```java
var result = ctx.step("custom-retry", Response.class,
stepCtx -> riskyOperation(),
StepConfig.builder()
.retryStrategy((error, attemptCount) -> {
if (error instanceof ClientException) return RetryDecision.noRetry();
if (attemptCount < 5) return RetryDecision.retryAfter(Duration.ofSeconds((long) Math.pow(2, attemptCount)));
return RetryDecision.noRetry();
})
.build());
```

## Error Classification

### Retryable vs Non-Retryable
Expand Down Expand Up @@ -232,6 +257,41 @@ def handler(event: dict, context: DurableContext) -> dict:
raise error
```

**Java:**

```java
public class OrderSaga extends DurableHandler<OrderInput, OrderResult> {
@Override
public OrderResult handleRequest(OrderInput input, DurableContext ctx) {
var compensations = new ArrayList<Map.Entry<String, Runnable>>();
try {
var reservation = ctx.step("reserve-inventory", Reservation.class,
stepCtx -> inventoryService.reserve(input.getItems()));
compensations.add(Map.entry("cancel-reservation",
() -> inventoryService.cancelReservation(reservation.getId())));

var payment = ctx.step("charge-payment", Payment.class,
stepCtx -> paymentService.charge(input.getPaymentMethod(), input.getAmount()));
compensations.add(Map.entry("refund-payment",
() -> paymentService.refund(payment.getId())));

return new OrderResult(true, payment.getOrderId());
} catch (Exception error) {
ctx.getLogger().error("Order failed, executing compensations", error);
Collections.reverse(compensations);
for (var comp : compensations) {
try {
ctx.step(comp.getKey(), Void.class, stepCtx -> { comp.getValue().run(); return null; });
} catch (Exception compError) {
ctx.getLogger().error("Compensation {} failed", comp.getKey(), compError);
}
}
throw error;
}
}
}
```

## Unrecoverable Errors

Mark errors as unrecoverable to stop execution immediately:
Expand Down Expand Up @@ -285,6 +345,21 @@ The SDK provides these exception types for different failure scenarios:
| `CallbackError` | No | Callback handling failures |
| `DurableExecutionsError` | — | Base class for all SDK exceptions |

### Java Exception Hierarchy

```
DurableExecutionException - General durable exception
├── NonDeterministicExecutionException - Code changed between executions
├── SerDesException - Serialization/deserialization error
└── DurableOperationException - General operation exception
├── StepFailedException - Step exhausted all retry attempts
├── StepInterruptedException - AT_MOST_ONCE step interrupted
├── CallbackFailedException - External system sent error
├── CallbackTimeoutException - Callback exceeded timeout
├── WaitForConditionFailedException- Polling exceeded max attempts
└── ChildContextFailedException - Child context failed
```

## Error Determinism

Ensure errors are deterministic across replays:
Expand Down
Loading