diff --git a/docs/getting-started/development-environment.md b/docs/getting-started/development-environment.md new file mode 100644 index 0000000..d9f16d0 --- /dev/null +++ b/docs/getting-started/development-environment.md @@ -0,0 +1,25 @@ +# Development Environment + +## Development workflow + +```mermaid +flowchart LR + subgraph dev["Development (Local)"] + direction LR + A["1. Write Function"] + B["2. Write Tests"] + C["3. Run Tests"] + end + + subgraph prod["Production (AWS)"] + direction LR + D["4. Deploy"] + E["5. Test in Cloud"] + end + + A --> B --> C --> D --> E + + style dev fill:#e3f2fd + style prod fill:#fff3e0 +``` + diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index 65a4147..7a5c824 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -15,5 +15,11 @@ concepts introduced here. - [Key Concepts](key-concepts.md) Understand durable execution, checkpoints, replay, and the DurableContext before writing code -- [Quick Start](quick-start.md) Install the SDK, write your first durable function, and - test it locally +- [Quickstart](quickstart.md) Install the SDK, write your first durable function, and + deploy it with the AWS CLI +- [Quickstart for Container Image](quickstart-container-image.md) Deploy a durable + function in a container image +- [Manage Executions](manage-executions.md) List, inspect, stop, update, and delete + durable functions and executions +- [Development Environment](development-environment.md) Development workflow, SDK + installation, and testing setup diff --git a/docs/getting-started/manage-executions.md b/docs/getting-started/manage-executions.md new file mode 100644 index 0000000..430eb1b --- /dev/null +++ b/docs/getting-started/manage-executions.md @@ -0,0 +1,122 @@ +# Manage Executions + +Use the AWS CLI to inspect, stop, update, and clean up durable functions and their +executions. + +## List executions + +```console +aws lambda list-durable-executions-by-function \ + --function-name my-durable-function +``` + +## Get execution details + +```console +aws lambda get-durable-execution \ + --durable-execution-arn +``` + +## Get execution history + +View the checkpoint history for an execution: + +```console +aws lambda get-durable-execution-history \ + --durable-execution-arn +``` + +## Stop an execution + +```console +aws lambda stop-durable-execution \ + --durable-execution-arn +``` + +## Update function code + +After updating your code, publish a new version and point your alias to it. + +=== "Zip (TypeScript/Python)" + + ```console + aws lambda update-function-code \ + --function-name my-durable-function \ + --zip-file fileb://function.zip + + aws lambda wait function-updated \ + --function-name my-durable-function + + aws lambda publish-version \ + --function-name my-durable-function + + aws lambda update-alias \ + --function-name my-durable-function \ + --name prod \ + --function-version + ``` + +=== "Container image (Java)" + + ```console + aws lambda update-function-code \ + --function-name my-durable-function \ + --image-uri 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-durable-function:latest + + aws lambda wait function-updated \ + --function-name my-durable-function + + aws lambda publish-version \ + --function-name my-durable-function + + aws lambda update-alias \ + --function-name my-durable-function \ + --name prod \ + --function-version + ``` + +Running executions will continue to use the version they started with. New invocations +use the updated alias. + +If you're still actively developing and you don't want to publish a new version each +time you update, you could use `LATEST$` just during development, but please be very +aware that executions might not replay correctly (or even fail) if the underlying code +changes during running executions. Always use numbered versions or aliases in +production. + +## View logs + +```console +aws logs tail /aws/lambda/my-durable-function --follow +``` + +## Delete durable functions + +```console +aws lambda delete-function --function-name my-durable-function + +aws iam detach-role-policy \ + --role-name durable-function-role \ + --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicDurableExecutionRolePolicy + +aws iam delete-role --role-name durable-function-role +``` + +If you deployed as a container image, also +[delete the image](https://docs.aws.amazon.com/AmazonECR/latest/userguide/delete_image.html) +from ECR: + +```console +aws ecr batch-delete-image \ + --repository-name my-durable-function \ + --image-ids imageTag=latest +``` + +Replace `latest` with the tag you pushed if you used a different tag. To delete multiple +tags at once, specify each with a separate `imageTag=` argument: + +```console +aws ecr batch-delete-image \ + --repository-name my-durable-function \ + --image-ids imageTag=latest imageTag=v1.0.0 +``` diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md deleted file mode 100644 index 9260011..0000000 --- a/docs/getting-started/quick-start.md +++ /dev/null @@ -1,288 +0,0 @@ -# Getting started - -## Table of Contents - -- [Overview](#overview) -- [The two SDKs](#the-two-sdks) -- [How durable execution works](#how-durable-execution-works) -- [Your development workflow](#your-development-workflow) -- [Quick start](#quick-start) -- [Next steps](#next-steps) - -[← Back to Get Started](index.md) - -## Overview - -This guide explains the fundamental concepts behind durable execution and how the SDK works. You'll understand: - -- The difference between `aws-durable-execution-sdk-python` and `aws-durable-execution-sdk-python-testing` -- How checkpoints and replay enable reliable workflows -- Why your function code runs multiple times but side effects happen once -- The development workflow from writing to testing to deployment - -[↑ Back to top](#table-of-contents) - -## The two SDKs - -The durable execution ecosystem has two separate packages: - -### Execution SDK (aws-durable-execution-sdk-python) - -This is the **core SDK** that runs in your Lambda functions. It provides: - -- `DurableContext` - The main interface for durable operations -- Operations - Steps, waits, callbacks, parallel, map, child contexts -- Decorators - `@durable_execution`, `@durable_step`, etc. -- Configuration - StepConfig, CallbackConfig, retry strategies -- Serialization - How data is saved in checkpoints - -Install it in your Lambda deployment package: - -```console -pip install aws-durable-execution-sdk-python -``` - -### Testing SDK (aws-durable-execution-sdk-python-testing) - -This is a **separate SDK** for testing your durable functions. It provides: - -- `DurableFunctionTestRunner` - Run functions locally without AWS -- `DurableFunctionCloudTestRunner` - Test deployed Lambda functions -- Pytest integration - Fixtures and markers for writing tests -- Result inspection - Examine execution state and operation results - -Install it in your development environment only: - -```console -pip install aws-durable-execution-sdk-python-testing -``` - -**Key distinction:** The execution SDK runs in production Lambda. The testing SDK runs on your laptop or CI/CD. They're separate concerns. - -[↑ Back to top](#table-of-contents) - -## How durable execution works - -Let's trace through a simple workflow to understand the execution model: - -=== "TypeScript" - - ``` typescript - --8<-- "examples/typescript/getting-started/execution-model.ts" - ``` - -=== "Python" - - ``` python - --8<-- "examples/python/getting-started/execution-model.py" - ``` - -=== "Java" - - ``` java - --8<-- "examples/java/getting-started/execution-model.java" - ``` - -**First invocation (t=0s):** - -1. Lambda invokes your function -2. `fetch_data` executes and calls an external API -3. Result is checkpointed to AWS -4. `context.wait(Duration.from_seconds(30))` is reached -5. Function returns, Lambda can recycle the environment - -**Second invocation (t=30s):** - -1. Lambda invokes your function again -2. Function code runs from the beginning -3. `fetch_data` returns the checkpointed result instantly (no API call) -4. `context.wait(Duration.from_seconds(30))` is already complete, execution continues -5. `process_data` executes for the first time -6. Result is checkpointed -7. Function returns the final result - -**Key insights:** - -- Your function code runs twice, but `fetch_data` only calls the API once -- The wait doesn't block Lambda - your environment can be recycled -- You write linear code that looks synchronous -- The SDK handles all the complexity of state management - -[↑ Back to top](#table-of-contents) - -## Your development workflow - -```mermaid -flowchart LR - subgraph dev["Development (Local)"] - direction LR - A["1. Write Function
aws-durable-execution-sdk-python"] - B["2. Write Tests
aws-durable-execution-sdk-python-testing"] - C["3. Run Tests
pytest"] - end - - subgraph prod["Production (AWS)"] - direction LR - D["4. Deploy
SAM/CDK/Terraform"] - E["5. Test in Cloud
pytest --runner-mode=cloud"] - end - - A --> B --> C --> D --> E - - style dev fill:#e3f2fd - style prod fill:#fff3e0 -``` - -Here's how you build and test durable functions: - -### 1. Write your function (execution SDK) - -Install the execution SDK and write your Lambda handler: - -```console -pip install aws-durable-execution-sdk-python -``` - -=== "TypeScript" - - ``` typescript - --8<-- "examples/typescript/getting-started/write-function.ts" - ``` - -=== "Python" - - ``` python - --8<-- "examples/python/getting-started/write-function.py" - ``` - -=== "Java" - - ``` java - --8<-- "examples/java/getting-started/write-function.java" - ``` - -### 2. Test locally (testing SDK) - -Install the testing SDK and write tests: - -```console -pip install aws-durable-execution-sdk-python-testing -``` - -=== "TypeScript" - - ``` typescript - --8<-- "examples/typescript/getting-started/test-locally.ts" - ``` - -=== "Python" - - ``` python - --8<-- "examples/python/getting-started/test-locally.py" - ``` - -=== "Java" - - ``` java - --8<-- "examples/java/getting-started/test-locally.java" - ``` - -Run tests without AWS credentials: - -```console -pytest test_my_function.py -``` - -### 3. Deploy to Lambda - -Package your function with the execution SDK (not the testing SDK) and deploy using your preferred tool (SAM, CDK, Terraform, etc.). - -### 4. Test in the cloud (optional) - -Run the same tests against your deployed function: - -```console -export AWS_REGION=us-west-2 -export QUALIFIED_FUNCTION_NAME="MyFunction:$LATEST" -export LAMBDA_FUNCTION_TEST_NAME="my_function" - -pytest --runner-mode=cloud test_my_function.py -``` - -[↑ Back to top](#table-of-contents) - -## Quick start - -Ready to build your first durable function? Here's a minimal example: - -=== "TypeScript" - - ``` typescript - --8<-- "examples/typescript/getting-started/minimal-example.ts" - ``` - -=== "Python" - - ``` python - --8<-- "examples/python/getting-started/minimal-example.py" - ``` - -=== "Java" - - ``` java - --8<-- "examples/java/getting-started/minimal-example.java" - ``` - -Deploy this to Lambda and you have a durable function. The `greet_user` step is checkpointed automatically. - -### Using a custom boto3 Lambda client - -If you need to customize the boto3 Lambda client used for durable execution operations (for example, to configure custom endpoints, retry settings, or credentials), you can pass a `boto3_client` parameter to the decorator. The client must be a boto3 Lambda client: - -=== "TypeScript" - - ``` typescript - --8<-- "examples/typescript/getting-started/minimal-example.ts" - ``` - -=== "Python" - - ``` python - --8<-- "examples/python/getting-started/minimal-example.py" - ``` - -=== "Java" - - ``` java - --8<-- "examples/java/getting-started/minimal-example.java" - ``` - -The custom Lambda client is used for all checkpoint and state management operations. If you don't provide a `boto3_client`, the SDK initializes a default Lambda client from your environment. - -[↑ Back to top](#table-of-contents) - -## Next steps - -Now that you've built your first durable function, explore the core features: - -**Learn the operations:** -- [Steps](../sdk-reference/operations/step.md) - Execute code with retry strategies and checkpointing -- [Wait operations](../sdk-reference/operations/wait.md) - Pause execution for seconds, minutes, or hours -- [Callbacks](../sdk-reference/operations/callback.md) - Wait for external systems to respond -- [Child contexts](../sdk-reference/operations/child-context.md) - Organize complex workflows -- [Parallel operations](../sdk-reference/operations/parallel.md) - Run multiple operations concurrently -- [Map operations](../sdk-reference/operations/map.md) - Process collections in parallel - -**Dive deeper:** -- [Error handling](../sdk-reference/error-handling/errors.md) - Handle failures and implement retry strategies -- [Testing patterns](../testing/basic-tests.md) - Write effective tests for your workflows -- [Best practices](../patterns/best-practices.md) - Avoid common pitfalls - -[↑ Back to top](#table-of-contents) - -## See also - -- [Documentation index](../index.md) - Browse all guides and examples -- [Logger integration](../sdk-reference/observability/logging.md) - Replay-safe structured logging - -[↑ Back to top](#table-of-contents) diff --git a/docs/getting-started/quickstart-container-image.md b/docs/getting-started/quickstart-container-image.md new file mode 100644 index 0000000..2d7c2d4 --- /dev/null +++ b/docs/getting-started/quickstart-container-image.md @@ -0,0 +1,142 @@ +# Quickstart for Container Image with Java + +Deploy your first durable function as a container image using the AWS CLI. + +You can deploy any of the supported SDK languages in a container image. + +This quickstart shows how to do so with Java. + +## Prerequisites + +- AWS CLI installed and configured with credentials +- Docker installed and running +- An Amazon ECR repository to push your image to +- Java 17+ and Maven 3.8+ + +## Create the execution role + +Follow the [execution role setup](quickstart.md#create-the-execution-role) in the main +quickstart, then return here. + +## Write the function + +=== "Java" + + ```java + --8<-- "examples/java/getting-started/quickstart.java" + ``` + +## Set up your Maven project + +Add to your `pom.xml` dependencies: + +```xml + + software.amazon.lambda.durable + aws-durable-execution-sdk-java + + + com.amazonaws + aws-lambda-java-core + +``` + +Add the `maven-shade-plugin` to produce a fat jar with all dependencies bundled: + +```xml + + org.apache.maven.plugins + maven-shade-plugin + 3.6.2 + + false + + + + package + shade + + + +``` + +## Build and push the container image + +Create a `Dockerfile` using a multi-stage build: + +```dockerfile +FROM --platform=linux/amd64 amazoncorretto:21-alpine AS builder +WORKDIR /build +COPY pom.xml . +COPY src ./src +RUN apk add --no-cache maven && mvn clean package -DskipTests + +FROM public.ecr.aws/lambda/java:21 +COPY --from=builder /build/target/*.jar ${LAMBDA_TASK_ROOT}/lib/ +CMD ["QuickstartFunction::handleRequest"] +``` + +Authenticate, build, and push to ECR: + +```console +aws ecr get-login-password --region us-east-1 \ + | docker login --username AWS --password-stdin \ + 123456789012.dkr.ecr.us-east-1.amazonaws.com + +docker build -t my-durable-function . + +docker tag my-durable-function:latest \ + 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-durable-function:latest + +docker push \ + 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-durable-function:latest +``` + +Replace `123456789012` and `us-east-1` with your account ID and region. + +## Deploy + +Replace `123456789012` with your AWS account ID and the role arn with that of the +execution role you just created. + +```console +aws lambda create-function \ + --function-name my-durable-function \ + --package-type Image \ + --code ImageUri=123456789012.dkr.ecr.us-east-1.amazonaws.com/my-durable-function:latest \ + --role arn:aws:iam::123456789012:role/durable-function-role \ + --architectures x86_64 \ + --durable-config '{"ExecutionTimeout": 900, "RetentionPeriodInDays": 1}' +``` + +## Publish a version + +```console +aws lambda publish-version --function-name my-durable-function +``` + +## Invoke + +```console +aws lambda invoke \ + --function-name my-durable-function:1 \ + --cli-binary-format raw-in-base64-out \ + --payload '{}' \ + response.json + +cat response.json +``` + +## Clean up + +See [delete durable functions](manage-executions.md#delete-durable-functions) to clean +up your function and IAM role. + +## Next steps + +- [Manage Executions](manage-executions.md) list, inspect, stop, update, and clean up +- [Development Environment](development-environment.md) write and run tests locally + before deploying +- [Key Concepts](key-concepts.md) understand replay, checkpoints, and determinism +- [Steps](../sdk-reference/operations/step.md) retry strategies and checkpointing +- [Wait](../sdk-reference/operations/wait.md) pause execution up to a year diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md new file mode 100644 index 0000000..42111e0 --- /dev/null +++ b/docs/getting-started/quickstart.md @@ -0,0 +1,182 @@ +# Quickstart + +Create and deploy your first durable function using the AWS CLI. This guide covers +TypeScript and Python. + +!!! note "Adding all your dependencies to the deployment package" + + This guide shows you how to package all your dependencies, including the Durable + Execution SDK, and deploy together with your custom code as a zip archive. This ensures + that you control the exact version of the Durable Execution SDK that your code uses. You + can create a durable function for quick testing purposes in the AWS Console, but then + the version of the SDK might be older and it might not contain the latest features and + optimizations. + +## Prerequisites + +- AWS CLI installed and configured with credentials + +=== "TypeScript" + + - Node.js 22+ + +=== "Python" + + - Python 3.13+ + +## Create the execution role + +Create an IAM role that grants your function permission to perform checkpoint +operations. + +Save the following as `trust-policy.json`: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` + +Create the role and attach the +[AWSLambdaBasicDurableExecutionRolePolicy](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSLambdaBasicDurableExecutionRolePolicy.html) +managed policy: + +```console +# Replace durable-function-role with your preferred role name +aws iam create-role \ + --role-name durable-function-role \ + --assume-role-policy-document file://trust-policy.json + +aws iam attach-role-policy \ + --role-name durable-function-role \ + --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicDurableExecutionRolePolicy +``` + +Note the role ARN returned. You'll need it in the next step. + +## Write the durable function + +!!! note "Using Java?" + + Java durable functions currently only deploy in container images. See + [Quickstart for Container Image](quickstart-container-image.md). + +=== "TypeScript" + + Save as `index.mjs` + + ```typescript + --8<-- "examples/typescript/getting-started/quickstart.ts" + ``` + +=== "Python" + + Save as `lambda_function.py` + + ```python + --8<-- "examples/python/getting-started/quickstart.py" + ``` + +The wait here is for 10 seconds just for an easy quick example, but it could as easily +be 10 days without incurring extra compute. + +## Package and deploy + +Replace `123456789012` with your AWS account ID and the role arn with that of the +execution role you just created. + +=== "TypeScript" + + ```console + mkdir my-function && cd my-function + npm init -y + npm install @aws/durable-execution-sdk-js + ``` + + Save the function code above as `index.mjs`, then package and deploy: + + ```console + zip -r function.zip index.mjs node_modules/ + + aws lambda create-function \ + --function-name my-durable-function \ + --runtime nodejs22.x \ + --role arn:aws:iam::123456789012:role/durable-function-role \ + --handler index.handler \ + --zip-file fileb://function.zip \ + --durable-config '{"ExecutionTimeout": 900, "RetentionPeriodInDays": 1}' + ``` + +=== "Python" + + ```console + mkdir -p package + pip install aws-durable-execution-sdk-python --target package/ + cp lambda_function.py package/ + cd package && zip -r ../function.zip . && cd .. + + aws lambda create-function \ + --function-name my-durable-function \ + --runtime python3.14 \ + --role arn:aws:iam::123456789012:role/durable-function-role \ + --handler lambda_function.lambda_handler \ + --zip-file fileb://function.zip \ + --durable-config '{"ExecutionTimeout": 900, "RetentionPeriodInDays": 1}' + ``` + +### Publish a version + +You must invoke a durable functions with a published version or alias to ensure +deterministic replay. + +For quick testing here in the Quickstart we can just invoke the durable function with +`$LATEST`. Note that you should NOT do this for production workloads. + +Be sure to publish a version if this is for production. Invoking `$LATEST` directly is +not supported for production workloads. + +```console +aws lambda publish-version --function-name my-durable-function +``` + +Note the version number in the returned ARN (for example, `:1`). + +## Invoke + +For synchronous invocation: + +```console +aws lambda invoke \ + --function-name 'my-durable-function:$LATEST' \ + --cli-binary-format raw-in-base64-out \ + --payload '{}' \ + response.json + +cat response.json +``` + +The function runs `step-1`, then pauses for 10 seconds without consuming compute. After +the wait, it resumes and returns the result. + +## Clean up + +See [delete durable functions](manage-executions.md#delete-durable-functions) to clean +up your function and IAM role. + +## Next steps + +- [Manage Executions](manage-executions.md) list, inspect, stop, update, and clean up +- [Development Environment](development-environment.md) write and run tests locally + before deploying +- [Key Concepts](key-concepts.md) understand replay, checkpoints, and determinism +- [Steps](../sdk-reference/operations/step.md) retry strategies and checkpointing +- [Wait](../sdk-reference/operations/wait.md) pause execution up to a year diff --git a/docs/sdk-reference/configuration/custom-lambda-client.md b/docs/sdk-reference/configuration/custom-lambda-client.md new file mode 100644 index 0000000..7d1e962 --- /dev/null +++ b/docs/sdk-reference/configuration/custom-lambda-client.md @@ -0,0 +1,32 @@ +# Configuration + +## Custom Lambda client + +By default, the SDK initializes a Lambda client from your environment. You can provide a +custom client to control the region, retry settings, credentials, or other options. + +=== "TypeScript" + + Pass a `LambdaClient` instance via the `config` parameter of `withDurableExecution`. + + ```typescript + --8<-- "examples/typescript/configuration/custom-client.ts" + ``` + +=== "Python" + + Pass a boto3 Lambda client via the `boto3_client` parameter of `@durable_execution`. The + client must be a boto3 Lambda client. + + ```python + --8<-- "examples/python/configuration/custom-client.py" + ``` + +=== "Java" + + Override `createConfiguration()` in your `DurableHandler` subclass and use + `DurableConfig.builder().withLambdaClientBuilder(...)`. + + ```java + --8<-- "examples/java/configuration/custom-client.java" + ``` diff --git a/docs/sdk-reference/configuration/index.md b/docs/sdk-reference/configuration/index.md new file mode 100644 index 0000000..fe92b62 --- /dev/null +++ b/docs/sdk-reference/configuration/index.md @@ -0,0 +1,4 @@ +# Configuration + +- [Custom Lambda Client](custom-lambda-client.md) Configure the Lambda client used by + the SDK diff --git a/docs/sdk-reference/index.md b/docs/sdk-reference/index.md index aad618e..0227ee5 100644 --- a/docs/sdk-reference/index.md +++ b/docs/sdk-reference/index.md @@ -30,6 +30,11 @@ The core building blocks for constructing durable workflows: - [Logging](observability/logging.md) Structured logging within durable functions +## Configuration + +- [Custom Lambda Client](configuration/custom-lambda-client.md) Configure the Lambda + client used by the SDK + ## Language Guides Language-specific installation and configuration: diff --git a/examples/java/configuration/custom-client.java b/examples/java/configuration/custom-client.java new file mode 100644 index 0000000..85e9067 --- /dev/null +++ b/examples/java/configuration/custom-client.java @@ -0,0 +1,32 @@ +import java.util.Map; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.retries.AdaptiveRetryStrategy; +import software.amazon.awssdk.retries.api.RetryStrategy; +import software.amazon.awssdk.services.lambda.LambdaClient; +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableHandler; + +public class CustomLambdaClient extends DurableHandler, Map> { + + @Override + protected DurableConfig createConfiguration() { + RetryStrategy retryStrategy = AdaptiveRetryStrategy.builder() + .maxAttempts(5) + .build(); + + LambdaClient.Builder lambdaClientBuilder = LambdaClient.builder() + .region(Region.US_WEST_2) + .overrideConfiguration(o -> o.retryStrategy(retryStrategy)); + + return DurableConfig.builder() + .withLambdaClientBuilder(lambdaClientBuilder) + .build(); + } + + @Override + public Map handleRequest(Map event, DurableContext context) { + // Your durable function logic + return Map.of("status", "success"); + } +} diff --git a/examples/java/getting-started/minimal-example.java b/examples/java/getting-started/minimal-example.java deleted file mode 100644 index 48cdd22..0000000 --- a/examples/java/getting-started/minimal-example.java +++ /dev/null @@ -1 +0,0 @@ -// Coming soon... diff --git a/examples/java/getting-started/quickstart.java b/examples/java/getting-started/quickstart.java new file mode 100644 index 0000000..dfc5dc4 --- /dev/null +++ b/examples/java/getting-started/quickstart.java @@ -0,0 +1,23 @@ +import java.time.Duration; +import java.util.Map; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableHandler; + +public class QuickstartFunction extends DurableHandler, Map> { + + @Override + public Map handleRequest(Map event, DurableContext context) { + String message = context.step("step-1", String.class, stepCtx -> { + stepCtx.getLogger().info("Hello from step-1"); + return "Hello from Durable Lambda!"; + }); + + // Pause for 10 seconds without consuming CPU or incurring usage charges + context.wait("wait-10s", Duration.ofSeconds(10)); + + // Replay-aware: logs once even though the function replays after the wait + context.getLogger().info("Resumed after wait"); + + return Map.of("statusCode", 200, "body", message); + } +} diff --git a/examples/java/getting-started/test-locally.java b/examples/java/getting-started/test-locally.java index 48cdd22..740a436 100644 --- a/examples/java/getting-started/test-locally.java +++ b/examples/java/getting-started/test-locally.java @@ -1 +1,24 @@ -// Coming soon... +import java.util.Map; +import org.junit.jupiter.api.Test; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.model.ExecutionStatus; +import software.amazon.lambda.durable.testing.LocalDurableTestRunner; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestLocally { + + @Test + void testMyFunction() { + var runner = LocalDurableTestRunner.create( + Map.class, + (Map input, DurableContext context) -> + context.step("my-step", String.class, + stepCtx -> "processed-" + input.get("data"))); + + var result = runner.runUntilComplete(Map.of("data", "test")); + + assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); + assertEquals("processed-test", result.getResult()); + } +} diff --git a/examples/java/getting-started/write-function.java b/examples/java/getting-started/write-function.java deleted file mode 100644 index 48cdd22..0000000 --- a/examples/java/getting-started/write-function.java +++ /dev/null @@ -1 +0,0 @@ -// Coming soon... diff --git a/examples/python/configuration/custom-client.py b/examples/python/configuration/custom-client.py new file mode 100644 index 0000000..d1ce2b9 --- /dev/null +++ b/examples/python/configuration/custom-client.py @@ -0,0 +1,18 @@ +import boto3 +from botocore.config import Config +from aws_durable_execution_sdk_python import durable_execution, DurableContext + +# Create a custom boto3 Lambda client with specific configuration +custom_lambda_client = boto3.client( + 'lambda', + config=Config( + retries={'max_attempts': 5, 'mode': 'adaptive'}, + connect_timeout=10, + read_timeout=60, + ) +) + +@durable_execution(boto3_client=custom_lambda_client) +def handler(event: dict, context: DurableContext) -> dict: + # Your durable function logic + return {"status": "success"} diff --git a/examples/python/getting-started/minimal-example.py b/examples/python/getting-started/minimal-example.py deleted file mode 100644 index a727f42..0000000 --- a/examples/python/getting-started/minimal-example.py +++ /dev/null @@ -1,18 +0,0 @@ -from aws_durable_execution_sdk_python import ( - DurableContext, - durable_execution, - durable_step, - StepContext, -) - -@durable_step -def greet_user(step_context: StepContext, name: str) -> str: - """Generate a greeting.""" - return f"Hello {name}!" - -@durable_execution -def handler(event: dict, context: DurableContext) -> str: - """Simple durable function.""" - name = event.get("name", "World") - greeting = context.step(greet_user(name)) - return greeting diff --git a/examples/python/getting-started/quickstart.py b/examples/python/getting-started/quickstart.py new file mode 100644 index 0000000..5a9d6a4 --- /dev/null +++ b/examples/python/getting-started/quickstart.py @@ -0,0 +1,22 @@ +from aws_durable_execution_sdk_python.config import Duration +from aws_durable_execution_sdk_python.context import DurableContext, StepContext, durable_step +from aws_durable_execution_sdk_python.execution import durable_execution + + +@durable_step +def my_step(step_context: StepContext) -> str: + step_context.logger.info("Hello from my_step") + return "Hello from Durable Lambda!" + + +@durable_execution +def lambda_handler(event, context: DurableContext) -> dict: + message: str = context.step(my_step()) + + # Pause for 10 seconds without consuming CPU or incurring usage charges + context.wait(Duration.from_seconds(10)) + + # Replay-aware: logs once even though the function replays after the wait + context.logger.info("Resumed after wait") + + return {"statusCode": 200, "body": message} diff --git a/examples/python/getting-started/write-function.py b/examples/python/getting-started/write-function.py index bef7571..1ded10f 100644 --- a/examples/python/getting-started/write-function.py +++ b/examples/python/getting-started/write-function.py @@ -2,14 +2,17 @@ DurableContext, durable_execution, durable_step, + StepContext, ) + @durable_step -def my_step(step_context, data): - # Your business logic - return result +def my_step(step_context: StepContext, data: str) -> str: + # Your business logic here + return f"processed-{data}" + @durable_execution -def handler(event, context: DurableContext): +def handler(event: dict, context: DurableContext) -> str: result = context.step(my_step(event["data"])) return result diff --git a/examples/typescript/configuration/custom-client.ts b/examples/typescript/configuration/custom-client.ts new file mode 100644 index 0000000..61711f0 --- /dev/null +++ b/examples/typescript/configuration/custom-client.ts @@ -0,0 +1,22 @@ +import { LambdaClient } from "@aws-sdk/client-lambda"; +import { + DurableContext, + withDurableExecution, + DurableExecutionConfig, +} from "@aws/durable-execution-sdk-js"; + +const customClient = new LambdaClient({ + region: "us-west-2", + maxAttempts: 5, + retryMode: "adaptive", +}); + +const config: DurableExecutionConfig = { client: customClient }; + +export const handler = withDurableExecution( + async (event: Record, context: DurableContext) => { + // Your durable function logic + return { status: "success" }; + }, + config, +); diff --git a/examples/typescript/getting-started/custom-client.ts b/examples/typescript/getting-started/custom-client.ts new file mode 100644 index 0000000..61711f0 --- /dev/null +++ b/examples/typescript/getting-started/custom-client.ts @@ -0,0 +1,22 @@ +import { LambdaClient } from "@aws-sdk/client-lambda"; +import { + DurableContext, + withDurableExecution, + DurableExecutionConfig, +} from "@aws/durable-execution-sdk-js"; + +const customClient = new LambdaClient({ + region: "us-west-2", + maxAttempts: 5, + retryMode: "adaptive", +}); + +const config: DurableExecutionConfig = { client: customClient }; + +export const handler = withDurableExecution( + async (event: Record, context: DurableContext) => { + // Your durable function logic + return { status: "success" }; + }, + config, +); diff --git a/examples/typescript/getting-started/minimal-example.ts b/examples/typescript/getting-started/minimal-example.ts deleted file mode 100644 index 48cdd22..0000000 --- a/examples/typescript/getting-started/minimal-example.ts +++ /dev/null @@ -1 +0,0 @@ -// Coming soon... diff --git a/examples/typescript/getting-started/quickstart.ts b/examples/typescript/getting-started/quickstart.ts new file mode 100644 index 0000000..8233418 --- /dev/null +++ b/examples/typescript/getting-started/quickstart.ts @@ -0,0 +1,16 @@ +import { withDurableExecution } from "@aws/durable-execution-sdk-js"; + +export const handler = withDurableExecution(async (event, context) => { + const message = await context.step("step-1", (stepCtx) => { + stepCtx.logger.info("Hello from step-1"); + return "Hello from Durable Lambda!"; + }); + + // Pause for 10 seconds without consuming CPU or incurring usage charges + await context.wait({ seconds: 10 }); + + // Replay-aware: logs once even though the function replays after the wait + context.logger.info("Resumed after wait"); + + return { statusCode: 200, body: message }; +}); diff --git a/examples/typescript/getting-started/test-locally.ts b/examples/typescript/getting-started/test-locally.ts index 48cdd22..6f5c811 100644 --- a/examples/typescript/getting-started/test-locally.ts +++ b/examples/typescript/getting-started/test-locally.ts @@ -1 +1,20 @@ -// Coming soon... +import { LocalDurableTestRunner } from "@aws/durable-execution-sdk-js-testing"; +import { ExecutionStatus } from "@aws-sdk/client-lambda"; +import { handler } from "./write-function"; + +beforeAll(async () => { + await LocalDurableTestRunner.setupTestEnvironment(); +}); + +afterAll(async () => { + await LocalDurableTestRunner.teardownTestEnvironment(); +}); + +test("my function", async () => { + const runner = new LocalDurableTestRunner({ handlerFunction: handler }); + + const result = await runner.run({ payload: { data: "test" } }); + + expect(result.getStatus()).toBe(ExecutionStatus.SUCCEEDED); + expect(result.getResult()).toBe("processed-test"); +}); diff --git a/examples/typescript/getting-started/write-function.ts b/examples/typescript/getting-started/write-function.ts index 48cdd22..98d9f92 100644 --- a/examples/typescript/getting-started/write-function.ts +++ b/examples/typescript/getting-started/write-function.ts @@ -1 +1,14 @@ -// Coming soon... +import { + DurableContext, + withDurableExecution, +} from "@aws/durable-execution-sdk-js"; + +export const handler = withDurableExecution( + async (event: { data: string }, context: DurableContext) => { + const result = await context.step("my-step", async () => { + // Your business logic here + return `processed-${event.data}`; + }); + return result; + }, +); diff --git a/zensical.toml b/zensical.toml index 9ebfce3..876def3 100644 --- a/zensical.toml +++ b/zensical.toml @@ -52,7 +52,10 @@ nav = [ { "Getting Started" = [ { "Getting Started" = "getting-started/index.md" }, { "Key Concepts" = "getting-started/key-concepts.md" }, - { "Quick Start" = "getting-started/quick-start.md" }, + { "Quickstart" = "getting-started/quickstart.md" }, + { "Quickstart for Container Image" = "getting-started/quickstart-container-image.md" }, + { "Manage Executions" = "getting-started/manage-executions.md" }, + { "Development Environment" = "getting-started/development-environment.md" }, ]}, { "SDK Reference" = [ { "SDK Reference" = "sdk-reference/index.md" }, @@ -76,6 +79,10 @@ nav = [ { "Observability" = [ { "Logging" = "sdk-reference/observability/logging.md" }, ]}, + { "Configuration" = [ + { "Configuration" = "sdk-reference/configuration/index.md" }, + { "Custom Lambda Client" = "sdk-reference/configuration/custom-lambda-client.md" }, + ]}, { "Language Guides" = [ { "Language Guides" = "sdk-reference/languages/index.md" }, { "TypeScript" = "sdk-reference/languages/typescript/index.md" },