From 49ca47569b6ce90749fe36a2d68075809f1b06a9 Mon Sep 17 00:00:00 2001 From: Edward Amsden Date: Wed, 10 Jun 2026 18:07:09 -0500 Subject: [PATCH 1/6] Sample for AWS lambda worker --- .gitignore | 1 + README.md | 3 + lambda-worker/README.md | 250 ++++++++++++++++++ lambda-worker/build.gradle | 63 +++++ lambda-worker/deploy-lambda.sh | 34 +++ lambda-worker/extra-setup-steps | 44 +++ ...-role-for-temporal-lambda-invoke-test.yaml | 94 +++++++ lambda-worker/mk-iam-role.sh | 14 + .../otel-collector-config.yaml.sample | 33 +++ .../lambdaworker/GreetingActivities.java | 12 + .../lambdaworker/GreetingActivitiesImpl.java | 20 ++ .../samples/lambdaworker/LambdaFunction.java | 21 ++ .../lambdaworker/LambdaWorkerSample.java | 46 ++++ .../samples/lambdaworker/SampleWorkflow.java | 12 + .../lambdaworker/SampleWorkflowImpl.java | 28 ++ .../samples/lambdaworker/Starter.java | 50 ++++ lambda-worker/src/main/resources/logback.xml | 15 ++ .../lambdaworker/LambdaWorkerSampleTest.java | 62 +++++ .../lambdaworker/SampleWorkflowTest.java | 32 +++ lambda-worker/temporal.toml.sample | 9 + settings.gradle | 8 +- 21 files changed, 850 insertions(+), 1 deletion(-) create mode 100644 lambda-worker/README.md create mode 100644 lambda-worker/build.gradle create mode 100755 lambda-worker/deploy-lambda.sh create mode 100755 lambda-worker/extra-setup-steps create mode 100644 lambda-worker/iam-role-for-temporal-lambda-invoke-test.yaml create mode 100755 lambda-worker/mk-iam-role.sh create mode 100644 lambda-worker/otel-collector-config.yaml.sample create mode 100644 lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivities.java create mode 100644 lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivitiesImpl.java create mode 100644 lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java create mode 100644 lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java create mode 100644 lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflow.java create mode 100644 lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflowImpl.java create mode 100644 lambda-worker/src/main/java/io/temporal/samples/lambdaworker/Starter.java create mode 100644 lambda-worker/src/main/resources/logback.xml create mode 100644 lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java create mode 100644 lambda-worker/src/test/java/io/temporal/samples/lambdaworker/SampleWorkflowTest.java create mode 100644 lambda-worker/temporal.toml.sample diff --git a/.gitignore b/.gitignore index a1abef91..f587ebda 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ target .DS_Store .idea .gradle +settings.local.gradle **/build/ **/out/ .classpath diff --git a/README.md b/README.md index 1465a9cc..898cc5b1 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ It contains the following modules: * [SpringBoot](/springboot): showcases SpringBoot autoconfig integration. * [SpringBoot Basic](/springboot-basic): Minimal sample showing SpringBoot autoconfig integration without any extra external dependencies. * [Spring AI](/springai): demonstrates the Temporal Spring AI integration — durable AI agents with chat models, tools, MCP servers, vector stores, and embeddings. +* [Lambda Worker](/lambda-worker): demonstrates running a Temporal Java Worker inside AWS Lambda. ## Learn more about Temporal and Java SDK @@ -115,6 +116,8 @@ See the README.md file in each main sample directory for cut/paste Gradle comman - [**Environment Configuration**](/core/src/main/java/io/temporal/samples/envconfig): Load client configuration from TOML files with programmatic overrides. +- [**Lambda Worker**](/lambda-worker): Demonstrates running a Temporal Java Worker inside AWS Lambda with Worker Deployment Versioning. + #### API demonstrations - [**Workflow Streams**](/core/src/main/java/io/temporal/samples/workflowstreams): Demonstrates a durable publish/subscribe log hosted inside a workflow, using the experimental `temporal-workflowstreams` module. diff --git a/lambda-worker/README.md b/lambda-worker/README.md new file mode 100644 index 00000000..44cc4668 --- /dev/null +++ b/lambda-worker/README.md @@ -0,0 +1,250 @@ +# Lambda Worker + +This sample demonstrates a Temporal Java Worker running inside an AWS Lambda function. +It registers a simple greeting Workflow and Activity, configures Worker Deployment +Versioning, and includes helper scripts for packaging the Lambda and configuring Temporal +Cloud invocation. + +For local SDK co-development, create `settings.local.gradle` in the repository root and +add `includeBuild '../sdk-java'`. That file is ignored by Git and makes Gradle use the +in-development `io.temporal:temporal-aws-lambda` SDK add-on before it is published. + +## Prerequisites + +- Java 17+ +- AWS CLI configured with permissions to create Lambda functions, IAM roles, and + CloudFormation stacks +- A Temporal Cloud namespace with Serverless Workers enabled, or a self-hosted Temporal + Service configured for AWS Lambda Serverless Workers +- A Temporal Cloud API key. This walkthrough deploys it as a Lambda environment variable + because these are development-only secrets. + +## Files + +| File | Description | +|------|-------------| +| `src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java` | AWS Lambda handler | +| `src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java` | Shared task queue, deployment version, and worker registrations | +| `src/main/java/io/temporal/samples/lambdaworker/SampleWorkflow*.java` | Sample Workflow interface and implementation | +| `src/main/java/io/temporal/samples/lambdaworker/GreetingActivities*.java` | Sample Activity interface and implementation | +| `src/main/java/io/temporal/samples/lambdaworker/Starter.java` | Local helper that starts a Workflow for the Lambda worker | +| `temporal.toml.sample` | Temporal client connection configuration | +| `otel-collector-config.yaml.sample` | Optional ADOT collector configuration | +| `deploy-lambda.sh` | Builds and uploads the Lambda deployment package | +| `mk-iam-role.sh` | Creates the role Temporal Cloud assumes to invoke Lambda | +| `iam-role-for-temporal-lambda-invoke-test.yaml` | CloudFormation template for the invocation role | +| `extra-setup-steps` | Optional IAM and Lambda settings for OpenTelemetry | + +## Build + +```bash +./gradlew :lambda-worker:test +./gradlew :lambda-worker:shadowJar +``` + +The Lambda handler string is: + +```text +io.temporal.samples.lambdaworker.LambdaFunction::handleRequest +``` + +## Configure Environment + +Set AWS, Temporal, and sample names first. Use unique values if you share the account or +namespace with other developers. + +```bash +export AWS_PROFILE= +export AWS_REGION=us-west-2 +export AWS_DEFAULT_REGION="$AWS_REGION" + +export TEMPORAL_ADDRESS=..tmprl.cloud:7233 +export TEMPORAL_NAMESPACE=. +export TEMPORAL_API_KEY= +export TEMPORAL_TLS=true + +export FUNCTION_NAME=my-temporal-java-worker +export EXECUTION_ROLE_NAME="${FUNCTION_NAME}-exec" +export STACK_NAME="${FUNCTION_NAME}-invoke" +export EXTERNAL_ID="${FUNCTION_NAME}-external-id" + +export DEPLOYMENT_NAME=my-app +export BUILD_ID=build-1 +export TASK_QUEUE=serverless-task-queue-java +export WORKFLOW_PREFIX=serverless-workflow-id-java +``` + +The Lambda worker reads these environment variables: + +```bash +TEMPORAL_ADDRESS +TEMPORAL_NAMESPACE +TEMPORAL_API_KEY +TEMPORAL_TASK_QUEUE +TEMPORAL_WORKER_DEPLOYMENT_NAME +TEMPORAL_WORKER_BUILD_ID +``` + +The local starter also reads `TEMPORAL_TASK_QUEUE` and `TEMPORAL_WORKFLOW_ID_PREFIX`. + +You can also use `temporal.toml.sample` as a starting point for `temporal.toml` and set +`TEMPORAL_CONFIG_FILE=temporal.toml`. + +`TEMPORAL_TASK_QUEUE`, `TEMPORAL_WORKER_DEPLOYMENT_NAME`, +`TEMPORAL_WORKER_BUILD_ID`, and `TEMPORAL_WORKFLOW_ID_PREFIX` are optional. The values +above are the sample defaults. + +## Deploy Lambda + +Create the Lambda execution role: + +```bash +cat > /tmp/temporal-lambda-trust-policy.json <<'JSON' +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} +JSON + +aws iam create-role \ + --role-name "$EXECUTION_ROLE_NAME" \ + --assume-role-policy-document file:///tmp/temporal-lambda-trust-policy.json \ + --query 'Role.Arn' \ + --output text + +aws iam attach-role-policy \ + --role-name "$EXECUTION_ROLE_NAME" \ + --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + +export EXECUTION_ROLE_ARN="$( + aws iam get-role \ + --role-name "$EXECUTION_ROLE_NAME" \ + --query 'Role.Arn' \ + --output text +)" +``` + +Build the deployment jar and create the Java 17 Lambda function. The jar is small enough +for direct upload in this sample; use S3 if your local artifact grows beyond Lambda's +direct upload limit. + +```bash +./gradlew :lambda-worker:shadowJar + +aws lambda create-function \ + --function-name "$FUNCTION_NAME" \ + --runtime java17 \ + --handler io.temporal.samples.lambdaworker.LambdaFunction::handleRequest \ + --role "$EXECUTION_ROLE_ARN" \ + --zip-file fileb://lambda-worker/build/libs/lambda-worker-1.0.0-all.jar \ + --environment "Variables={TEMPORAL_ADDRESS=$TEMPORAL_ADDRESS,TEMPORAL_NAMESPACE=$TEMPORAL_NAMESPACE,TEMPORAL_API_KEY=$TEMPORAL_API_KEY,TEMPORAL_TASK_QUEUE=$TASK_QUEUE,TEMPORAL_WORKER_DEPLOYMENT_NAME=$DEPLOYMENT_NAME,TEMPORAL_WORKER_BUILD_ID=$BUILD_ID}" \ + --timeout 90 \ + --memory-size 1024 \ + --query 'FunctionArn' \ + --output text + +aws lambda wait function-active --function-name "$FUNCTION_NAME" + +export FUNCTION_ARN="$( + aws lambda get-function \ + --function-name "$FUNCTION_NAME" \ + --query 'Configuration.FunctionArn' \ + --output text +)" +``` + +To update code after the function exists: + +```bash +./lambda-worker/deploy-lambda.sh "$FUNCTION_NAME" +``` + +If direct upload is too large, set `LAMBDA_CODE_S3_BUCKET` and rerun: + +```bash +LAMBDA_CODE_S3_BUCKET= ./lambda-worker/deploy-lambda.sh "$FUNCTION_NAME" +``` + +## Configure Invocation + +Create the IAM role that Temporal Cloud assumes to invoke the Lambda: + +```bash +cd lambda-worker +./mk-iam-role.sh "$STACK_NAME" "$EXTERNAL_ID" "$FUNCTION_ARN" +cd .. + +aws cloudformation wait stack-create-complete --stack-name "$STACK_NAME" + +export INVOCATION_ROLE_ARN="$( + aws cloudformation describe-stacks \ + --stack-name "$STACK_NAME" \ + --query "Stacks[0].Outputs[?OutputKey=='RoleARN'].OutputValue | [0]" \ + --output text +)" +``` + +Create and route the Worker Deployment Version: + +```bash +temporal worker deployment create --name "$DEPLOYMENT_NAME" + +temporal worker deployment create-version \ + --deployment-name "$DEPLOYMENT_NAME" \ + --build-id "$BUILD_ID" \ + --aws-lambda-function-arn "$FUNCTION_ARN" \ + --aws-lambda-assume-role-arn "$INVOCATION_ROLE_ARN" \ + --aws-lambda-assume-role-external-id "$EXTERNAL_ID" + +temporal worker deployment set-current-version \ + --deployment-name "$DEPLOYMENT_NAME" \ + --build-id "$BUILD_ID" \ + --allow-no-pollers \ + --yes +``` + +An async Lambda smoke test returns immediately and should produce worker startup logs: + +```bash +aws lambda invoke \ + --function-name "$FUNCTION_NAME" \ + --invocation-type Event \ + --cli-binary-format raw-in-base64-out \ + --payload '{}' \ + /tmp/lambda-worker-response.json \ + --query 'StatusCode' \ + --output text +``` + +A synchronous invoke can run until the Lambda worker exits near the function timeout. If +you want to wait for that path, set the AWS CLI read timeout higher than the function +timeout. + +## Start Workflow + +After the Worker Deployment Version is current, start the sample Workflow: + +```bash +export TEMPORAL_TASK_QUEUE="$TASK_QUEUE" +export TEMPORAL_WORKFLOW_ID_PREFIX="$WORKFLOW_PREFIX" + +./gradlew -q :lambda-worker:execute \ + -PmainClass=io.temporal.samples.lambdaworker.Starter +``` + +The starter only creates a Workflow Execution. It does not start a local Worker. The +important value is `TEMPORAL_TASK_QUEUE`; it must match the task queue configured on the +Lambda function. + +## Local SDK Development + +For local development of the Workflow and Activity logic, run the unit tests. They use +`TestWorkflowRule` and do not require AWS or a running Temporal Service. diff --git a/lambda-worker/build.gradle b/lambda-worker/build.gradle new file mode 100644 index 00000000..a1658c53 --- /dev/null +++ b/lambda-worker/build.gradle @@ -0,0 +1,63 @@ +dependencies { + implementation "io.temporal:temporal-sdk:$javaSDKVersion" + implementation "io.temporal:temporal-envconfig:$javaSDKVersion" + implementation "io.temporal:temporal-aws-lambda:$javaSDKVersion" + implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.6' + + testImplementation "io.temporal:temporal-testing:$javaSDKVersion" + testImplementation "junit:junit:4.13.2" + testImplementation(platform("org.junit:junit-bom:5.10.3")) + testRuntimeOnly "org.junit.vintage:junit-vintage-engine" + + dependencies { + errorproneJavac('com.google.errorprone:javac:9+181-r4173-1') + errorprone('com.google.errorprone:error_prone_core:2.28.0') + } +} + +tasks.register('execute', JavaExec) { + mainClass = findProperty("mainClass") ?: "" + classpath = sourceSets.main.runtimeClasspath + if (findProperty("args")) { + args findProperty("args").tokenize() + } +} + +tasks.register('shadowJar', Jar) { + archiveBaseName = 'lambda-worker' + archiveClassifier = 'all' + archiveVersion = jarVersion + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + dependsOn configurations.runtimeClasspath + def mergedServicesDir = layout.buildDirectory.dir('generated/mergedServices') + + from sourceSets.main.output + from({ + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + }) { + exclude 'META-INF/services/**' + } + from(mergedServicesDir) + + doFirst { + File servicesOutput = mergedServicesDir.get().asFile + delete servicesOutput + File servicesDir = new File(servicesOutput, 'META-INF/services') + servicesDir.mkdirs() + + Map> services = [:].withDefault { new LinkedHashSet<>() } + configurations.runtimeClasspath.files.findAll { it.isFile() }.each { artifact -> + zipTree(artifact).matching { include 'META-INF/services/*' }.files.each { serviceFile -> + serviceFile.eachLine('UTF-8') { line -> + String service = line.trim() + if (!service.isEmpty() && !service.startsWith('#')) { + services[serviceFile.name].add(service) + } + } + } + } + services.each { name, providers -> + new File(servicesDir, name).text = providers.join(System.lineSeparator()) + System.lineSeparator() + } + } +} diff --git a/lambda-worker/deploy-lambda.sh b/lambda-worker/deploy-lambda.sh new file mode 100755 index 00000000..bcf94a2d --- /dev/null +++ b/lambda-worker/deploy-lambda.sh @@ -0,0 +1,34 @@ +#!/bin/bash +set -euo pipefail + +FUNCTION_NAME="${1:?Usage: deploy-lambda.sh }" +MAX_DIRECT_UPLOAD_BYTES=50000000 + +cd "$(dirname "$0")/.." +./gradlew :lambda-worker:shadowJar + +JAR_FILE="$(find lambda-worker/build/libs -name 'lambda-worker-*-all.jar' | head -n 1)" + +if stat -f%z "$JAR_FILE" >/dev/null 2>&1; then + JAR_SIZE="$(stat -f%z "$JAR_FILE")" +else + JAR_SIZE="$(stat -c%s "$JAR_FILE")" +fi + +if [[ -n "${LAMBDA_CODE_S3_BUCKET:-}" ]]; then + S3_KEY="${LAMBDA_CODE_S3_KEY:-lambda-worker/$(basename "$JAR_FILE")}" + aws s3 cp "$JAR_FILE" "s3://$LAMBDA_CODE_S3_BUCKET/$S3_KEY" + aws lambda update-function-code \ + --function-name "$FUNCTION_NAME" \ + --s3-bucket "$LAMBDA_CODE_S3_BUCKET" \ + --s3-key "$S3_KEY" + exit 0 +fi + +if (( JAR_SIZE > MAX_DIRECT_UPLOAD_BYTES )); then + echo "Artifact is ${JAR_SIZE} bytes, which is too large for direct Lambda upload." >&2 + echo "Set LAMBDA_CODE_S3_BUCKET and rerun to upload through S3." >&2 + exit 1 +fi + +aws lambda update-function-code --function-name "$FUNCTION_NAME" --zip-file "fileb://$JAR_FILE" diff --git a/lambda-worker/extra-setup-steps b/lambda-worker/extra-setup-steps new file mode 100755 index 00000000..8c6dd0f8 --- /dev/null +++ b/lambda-worker/extra-setup-steps @@ -0,0 +1,44 @@ +#!/bin/bash +set -euo pipefail + +ROLE_NAME="${1:?Usage: extra-setup-steps }" +FUNCTION_NAME="${2:?Usage: extra-setup-steps }" +REGION="${3:?Usage: extra-setup-steps }" +ACCOUNT_ID="${4:?Usage: extra-setup-steps }" + +aws iam put-role-policy \ + --role-name "$ROLE_NAME" \ + --policy-name ADOT-Telemetry-Permissions \ + --policy-document "{ + \"Version\": \"2012-10-17\", + \"Statement\": [ + { + \"Effect\": \"Allow\", + \"Action\": [ + \"logs:CreateLogGroup\", + \"logs:CreateLogStream\", + \"logs:PutLogEvents\" + ], + \"Resource\": \"arn:aws:logs:${REGION}:${ACCOUNT_ID}:log-group:/aws/lambda/${FUNCTION_NAME}:*\" + }, + { + \"Effect\": \"Allow\", + \"Action\": [ + \"xray:PutTraceSegments\", + \"xray:PutTelemetryRecords\" + ], + \"Resource\": \"*\" + }, + { + \"Effect\": \"Allow\", + \"Action\": [ + \"cloudwatch:PutMetricData\" + ], + \"Resource\": \"*\" + } + ] + }" + +aws lambda update-function-configuration \ + --function-name "$FUNCTION_NAME" \ + --tracing-config Mode=Active diff --git a/lambda-worker/iam-role-for-temporal-lambda-invoke-test.yaml b/lambda-worker/iam-role-for-temporal-lambda-invoke-test.yaml new file mode 100644 index 00000000..2e9054e2 --- /dev/null +++ b/lambda-worker/iam-role-for-temporal-lambda-invoke-test.yaml @@ -0,0 +1,94 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: Creates an IAM role that Temporal Cloud can assume to invoke Lambda functions for Serverless Workers. + +Parameters: + AssumeRoleExternalId: + Type: String + Description: A string you choose. Use the same value when creating the Worker Deployment Version. + AllowedPattern: "[a-zA-Z0-9_+=,.@-]*" + MinLength: 5 + MaxLength: 45 + + LambdaFunctionARNs: + Type: CommaDelimitedList + Description: Comma-separated list of Lambda function ARNs to invoke. + + RoleName: + Type: String + Default: "Temporal-Cloud-Serverless-Worker" + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Temporal Cloud Configuration" + Parameters: + - AssumeRoleExternalId + - Label: + default: "Lambda Configuration" + Parameters: + - LambdaFunctionARNs + - RoleName + ParameterLabels: + AssumeRoleExternalId: + default: "External ID" + LambdaFunctionARNs: + default: "Lambda Function ARNs" + RoleName: + default: "IAM Role Name" + +Resources: + TemporalCloudServerlessWorker: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub "${RoleName}-${AWS::StackName}" + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + AWS: + [ + arn:aws:iam::902542641901:role/wci-lambda-invoke, + arn:aws:iam::160190466495:role/wci-lambda-invoke, + arn:aws:iam::819232936619:role/wci-lambda-invoke, + arn:aws:iam::829909441867:role/wci-lambda-invoke, + arn:aws:iam::354116250941:role/wci-lambda-invoke, + ] + Action: sts:AssumeRole + Condition: + StringEquals: + "sts:ExternalId": [!Ref AssumeRoleExternalId] + Description: The role Temporal Cloud uses to invoke Lambda functions for Serverless Workers. + MaxSessionDuration: 3600 + + TemporalCloudLambdaInvokePermissions: + Type: AWS::IAM::Policy + DependsOn: TemporalCloudServerlessWorker + Properties: + PolicyName: "Temporal-Cloud-Lambda-Invoke-Permissions" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + - lambda:GetFunction + Resource: !Ref LambdaFunctionARNs + Roles: + - !Sub "${RoleName}-${AWS::StackName}" + +Outputs: + RoleARN: + Description: The ARN of the IAM role created for Temporal Cloud. + Value: !GetAtt TemporalCloudServerlessWorker.Arn + Export: + Name: !Sub "${AWS::StackName}-RoleARN" + + RoleName: + Description: The name of the IAM role. + Value: !Ref RoleName + + LambdaFunctionARNs: + Description: The Lambda function ARNs that can be invoked. + Value: !Join [", ", !Ref LambdaFunctionARNs] diff --git a/lambda-worker/mk-iam-role.sh b/lambda-worker/mk-iam-role.sh new file mode 100755 index 00000000..20aeae5d --- /dev/null +++ b/lambda-worker/mk-iam-role.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -euo pipefail + +STACK_NAME="${1:?Usage: mk-iam-role.sh }" +EXTERNAL_ID="${2:?Usage: mk-iam-role.sh }" +LAMBDA_ARN="${3:?Usage: mk-iam-role.sh }" + +aws cloudformation create-stack \ + --stack-name "$STACK_NAME" \ + --template-body file://iam-role-for-temporal-lambda-invoke-test.yaml \ + --parameters \ + ParameterKey=AssumeRoleExternalId,ParameterValue="$EXTERNAL_ID" \ + ParameterKey=LambdaFunctionARNs,ParameterValue="\"$LAMBDA_ARN\"" \ + --capabilities CAPABILITY_NAMED_IAM diff --git a/lambda-worker/otel-collector-config.yaml.sample b/lambda-worker/otel-collector-config.yaml.sample new file mode 100644 index 00000000..790be3d0 --- /dev/null +++ b/lambda-worker/otel-collector-config.yaml.sample @@ -0,0 +1,33 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: "localhost:4317" + http: + endpoint: "localhost:4318" + +exporters: + debug: + awsxray: + region: us-west-2 + awsemf: + namespace: TemporalWorkerMetrics + log_group_name: /aws/lambda/ + region: us-west-2 + dimension_rollup_option: NoDimensionRollup + resource_to_telemetry_conversion: + enabled: true + +service: + pipelines: + traces: + receivers: [otlp] + exporters: [awsxray, debug] + metrics: + receivers: [otlp] + exporters: [awsemf] + telemetry: + logs: + level: debug + metrics: + address: localhost:8888 diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivities.java b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivities.java new file mode 100644 index 00000000..620b7212 --- /dev/null +++ b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivities.java @@ -0,0 +1,12 @@ +package io.temporal.samples.lambdaworker; + +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; + +/** Activity interface used by the sample workflow. */ +@ActivityInterface +public interface GreetingActivities { + + @ActivityMethod + String createGreeting(String name); +} diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivitiesImpl.java b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivitiesImpl.java new file mode 100644 index 00000000..07349515 --- /dev/null +++ b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivitiesImpl.java @@ -0,0 +1,20 @@ +package io.temporal.samples.lambdaworker; + +import io.temporal.activity.Activity; +import io.temporal.activity.ActivityInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Activity implementation that returns a simple greeting. */ +public class GreetingActivitiesImpl implements GreetingActivities { + + private static final Logger logger = LoggerFactory.getLogger(GreetingActivitiesImpl.class); + + @Override + public String createGreeting(String name) { + ActivityInfo info = Activity.getExecutionContext().getInfo(); + logger.info( + "Running activity {} for workflow {}", info.getActivityType(), info.getWorkflowId()); + return "Hello, " + name + "!"; + } +} diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java new file mode 100644 index 00000000..547a6d71 --- /dev/null +++ b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java @@ -0,0 +1,21 @@ +package io.temporal.samples.lambdaworker; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import io.temporal.aws.lambda.LambdaWorker; +import io.temporal.common.WorkerDeploymentVersion; + +/** AWS Lambda entry point for the Temporal worker. */ +public class LambdaFunction implements RequestHandler { + + private static final RequestHandler WORKER = + LambdaWorker.run( + new WorkerDeploymentVersion( + LambdaWorkerSample.deploymentName(), LambdaWorkerSample.buildId()), + LambdaWorkerSample::configure); + + @Override + public Void handleRequest(Object input, Context context) { + return WORKER.handleRequest(input, context); + } +} diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java new file mode 100644 index 00000000..84868d0b --- /dev/null +++ b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java @@ -0,0 +1,46 @@ +package io.temporal.samples.lambdaworker; + +import io.temporal.aws.lambda.LambdaWorkerOptions; + +/** Shared constants and worker registration for the Lambda Worker sample. */ +public final class LambdaWorkerSample { + + public static final String TASK_QUEUE_ENV = "TEMPORAL_TASK_QUEUE"; + public static final String DEPLOYMENT_NAME_ENV = "TEMPORAL_WORKER_DEPLOYMENT_NAME"; + public static final String BUILD_ID_ENV = "TEMPORAL_WORKER_BUILD_ID"; + public static final String WORKFLOW_ID_PREFIX_ENV = "TEMPORAL_WORKFLOW_ID_PREFIX"; + + public static final String DEFAULT_TASK_QUEUE = "serverless-task-queue-java"; + public static final String DEFAULT_WORKFLOW_ID_PREFIX = "serverless-workflow-id-java"; + public static final String DEFAULT_DEPLOYMENT_NAME = "my-app"; + public static final String DEFAULT_BUILD_ID = "build-1"; + + public static void configure(LambdaWorkerOptions options) { + options.setTaskQueue(taskQueue()); + options.registerWorkflowImplementationTypes(SampleWorkflowImpl.class); + options.registerActivitiesImplementations(new GreetingActivitiesImpl()); + } + + public static String taskQueue() { + return envOrDefault(TASK_QUEUE_ENV, DEFAULT_TASK_QUEUE); + } + + public static String workflowIdPrefix() { + return envOrDefault(WORKFLOW_ID_PREFIX_ENV, DEFAULT_WORKFLOW_ID_PREFIX); + } + + public static String deploymentName() { + return envOrDefault(DEPLOYMENT_NAME_ENV, DEFAULT_DEPLOYMENT_NAME); + } + + public static String buildId() { + return envOrDefault(BUILD_ID_ENV, DEFAULT_BUILD_ID); + } + + private static String envOrDefault(String name, String defaultValue) { + String value = System.getenv(name); + return value == null || value.isBlank() ? defaultValue : value; + } + + private LambdaWorkerSample() {} +} diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflow.java b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflow.java new file mode 100644 index 00000000..25aa58f8 --- /dev/null +++ b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflow.java @@ -0,0 +1,12 @@ +package io.temporal.samples.lambdaworker; + +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +/** Sample workflow run by the Lambda worker. */ +@WorkflowInterface +public interface SampleWorkflow { + + @WorkflowMethod + String getGreeting(String name); +} diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflowImpl.java b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflowImpl.java new file mode 100644 index 00000000..0f6e7cbb --- /dev/null +++ b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflowImpl.java @@ -0,0 +1,28 @@ +package io.temporal.samples.lambdaworker; + +import io.temporal.activity.ActivityOptions; +import io.temporal.common.VersioningBehavior; +import io.temporal.workflow.Workflow; +import io.temporal.workflow.WorkflowVersioningBehavior; +import java.time.Duration; +import org.slf4j.Logger; + +/** Workflow implementation that executes a greeting Activity. */ +public class SampleWorkflowImpl implements SampleWorkflow { + + private static final Logger logger = Workflow.getLogger(SampleWorkflowImpl.class); + + private final GreetingActivities activities = + Workflow.newActivityStub( + GreetingActivities.class, + ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(10)).build()); + + @Override + @WorkflowVersioningBehavior(VersioningBehavior.PINNED) + public String getGreeting(String name) { + logger.info("SampleWorkflow started for {}", name); + String result = activities.createGreeting(name); + logger.info("SampleWorkflow completed with {}", result); + return result; + } +} diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/Starter.java b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/Starter.java new file mode 100644 index 00000000..c8e46a4b --- /dev/null +++ b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/Starter.java @@ -0,0 +1,50 @@ +package io.temporal.samples.lambdaworker; + +import io.temporal.api.common.v1.WorkflowExecution; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.client.WorkflowStub; +import io.temporal.envconfig.ClientConfigProfile; +import io.temporal.serviceclient.WorkflowServiceStubs; +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** Local helper that starts a Workflow execution for the Lambda worker to process. */ +public class Starter { + + public static void main(String[] args) { + String name = args.length > 0 ? String.join(" ", args) : "Serverless Lambda Worker!"; + + WorkflowServiceStubs service = null; + try { + ClientConfigProfile profile = ClientConfigProfile.load(); + service = WorkflowServiceStubs.newServiceStubs(profile.toWorkflowServiceStubsOptions()); + WorkflowClient client = + WorkflowClient.newInstance(service, profile.toWorkflowClientOptions()); + + SampleWorkflow workflow = + client.newWorkflowStub( + SampleWorkflow.class, + WorkflowOptions.newBuilder() + .setWorkflowId(LambdaWorkerSample.workflowIdPrefix() + "-" + UUID.randomUUID()) + .setTaskQueue(LambdaWorkerSample.taskQueue()) + .build()); + + WorkflowExecution execution = WorkflowClient.start(workflow::getGreeting, name); + System.out.printf( + "Started workflow WorkflowID=%s RunID=%s%n", + execution.getWorkflowId(), execution.getRunId()); + + String result = WorkflowStub.fromTyped(workflow).getResult(String.class); + System.out.println("Workflow result: " + result); + } catch (IOException e) { + throw new RuntimeException("Failed to load Temporal client configuration", e); + } finally { + if (service != null) { + service.shutdown(); + service.awaitTermination(10, TimeUnit.SECONDS); + } + } + } +} diff --git a/lambda-worker/src/main/resources/logback.xml b/lambda-worker/src/main/resources/logback.xml new file mode 100644 index 00000000..7c5faa44 --- /dev/null +++ b/lambda-worker/src/main/resources/logback.xml @@ -0,0 +1,15 @@ + + + + %d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n + + + + + + + + + + + diff --git a/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java b/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java new file mode 100644 index 00000000..a4ca9927 --- /dev/null +++ b/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java @@ -0,0 +1,62 @@ +package io.temporal.samples.lambdaworker; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.amazonaws.services.lambda.runtime.RequestHandler; +import io.temporal.aws.lambda.LambdaWorker; +import io.temporal.aws.lambda.LambdaWorkerOptions; +import io.temporal.common.WorkerDeploymentVersion; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; + +/** Tests Lambda worker registration without AWS. */ +public class LambdaWorkerSampleTest { + + @Test + public void configureSetsTaskQueueAndRegistrations() throws Exception { + LambdaWorkerOptions options = LambdaWorkerOptions.fromEnvironment(baseEnv()); + + LambdaWorkerSample.configure(options); + + assertEquals(LambdaWorkerSample.DEFAULT_TASK_QUEUE, options.getTaskQueue()); + assertEquals(2, registrations(options).size()); + + RequestHandler handler = + LambdaWorker.newHandler( + new WorkerDeploymentVersion( + LambdaWorkerSample.DEFAULT_DEPLOYMENT_NAME, LambdaWorkerSample.DEFAULT_BUILD_ID), + options); + assertNotNull(handler); + } + + @Test + public void configureUsesDefaultTaskQueueWhenNoProcessEnvironmentOverride() throws Exception { + Map env = baseEnv(); + env.put(LambdaWorkerOptions.TEMPORAL_TASK_QUEUE, "from-env"); + LambdaWorkerOptions options = LambdaWorkerOptions.fromEnvironment(env); + + LambdaWorkerSample.configure(options); + + assertEquals(LambdaWorkerSample.DEFAULT_TASK_QUEUE, options.getTaskQueue()); + assertEquals(2, registrations(options).size()); + } + + private static Map baseEnv() { + Map env = new HashMap<>(); + env.put(LambdaWorkerOptions.TEMPORAL_CONFIG_FILE, "/nonexistent/temporal.toml"); + return env; + } + + private static List registrations(LambdaWorkerOptions options) throws Exception { + Field registrations = LambdaWorkerOptions.class.getDeclaredField("registrations"); + registrations.setAccessible(true); + Object value = registrations.get(options); + assertTrue(value instanceof List); + return (List) value; + } +} diff --git a/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/SampleWorkflowTest.java b/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/SampleWorkflowTest.java new file mode 100644 index 00000000..d4d2367b --- /dev/null +++ b/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/SampleWorkflowTest.java @@ -0,0 +1,32 @@ +package io.temporal.samples.lambdaworker; + +import static org.junit.Assert.assertEquals; + +import io.temporal.client.WorkflowOptions; +import io.temporal.testing.TestWorkflowRule; +import org.junit.Rule; +import org.junit.Test; + +/** Unit test for the sample Workflow and Activity. */ +public class SampleWorkflowTest { + + @Rule + public TestWorkflowRule testWorkflowRule = + TestWorkflowRule.newBuilder() + .setWorkflowTypes(SampleWorkflowImpl.class) + .setActivityImplementations(new GreetingActivitiesImpl()) + .build(); + + @Test + public void workflowReturnsGreeting() { + SampleWorkflow workflow = + testWorkflowRule + .getWorkflowClient() + .newWorkflowStub( + SampleWorkflow.class, + WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.getTaskQueue()).build()); + + assertEquals( + "Hello, Serverless Lambda Worker!!", workflow.getGreeting("Serverless Lambda Worker!")); + } +} diff --git a/lambda-worker/temporal.toml.sample b/lambda-worker/temporal.toml.sample new file mode 100644 index 00000000..0117d5cf --- /dev/null +++ b/lambda-worker/temporal.toml.sample @@ -0,0 +1,9 @@ +[profile.default] +address = "..tmprl.cloud:7233" +namespace = "." +api_key = "" + +# For mTLS instead of API key auth, remove api_key and uncomment: +# [profile.default.tls] +# client_cert_path = "client.pem" +# client_key_path = "client.key" diff --git a/settings.gradle b/settings.gradle index b99ad281..c6756a18 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,10 @@ rootProject.name = 'temporal-java-samples' + +def localSettings = file('settings.local.gradle') +if (localSettings.exists()) { + apply from: localSettings +} + include 'core' include 'springai:basic' include 'springai:mcp' @@ -6,4 +12,4 @@ include 'springai:multimodel' include 'springai:rag' include 'springboot' include 'springboot-basic' - +include 'lambda-worker' From c2fd02ebc7e7496adca5bfe21e2ae207b2ab2cd3 Mon Sep 17 00:00:00 2001 From: Edward Amsden Date: Mon, 15 Jun 2026 17:25:13 -0500 Subject: [PATCH 2/6] Use builder for immutable LambdaWorkerOptions --- .../samples/lambdaworker/LambdaWorkerSample.java | 8 ++++---- .../samples/lambdaworker/LambdaWorkerSampleTest.java | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java index 84868d0b..77c3282a 100644 --- a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java +++ b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java @@ -15,10 +15,10 @@ public final class LambdaWorkerSample { public static final String DEFAULT_DEPLOYMENT_NAME = "my-app"; public static final String DEFAULT_BUILD_ID = "build-1"; - public static void configure(LambdaWorkerOptions options) { - options.setTaskQueue(taskQueue()); - options.registerWorkflowImplementationTypes(SampleWorkflowImpl.class); - options.registerActivitiesImplementations(new GreetingActivitiesImpl()); + public static void configure(LambdaWorkerOptions.Builder builder) { + builder.setTaskQueue(taskQueue()); + builder.registerWorkflowImplementationTypes(SampleWorkflowImpl.class); + builder.registerActivitiesImplementations(new GreetingActivitiesImpl()); } public static String taskQueue() { diff --git a/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java b/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java index a4ca9927..b1c9a21b 100644 --- a/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java +++ b/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java @@ -19,9 +19,10 @@ public class LambdaWorkerSampleTest { @Test public void configureSetsTaskQueueAndRegistrations() throws Exception { - LambdaWorkerOptions options = LambdaWorkerOptions.fromEnvironment(baseEnv()); + LambdaWorkerOptions.Builder builder = LambdaWorkerOptions.newBuilderFromEnvironment(baseEnv()); - LambdaWorkerSample.configure(options); + LambdaWorkerSample.configure(builder); + LambdaWorkerOptions options = builder.build(); assertEquals(LambdaWorkerSample.DEFAULT_TASK_QUEUE, options.getTaskQueue()); assertEquals(2, registrations(options).size()); @@ -38,9 +39,10 @@ public void configureSetsTaskQueueAndRegistrations() throws Exception { public void configureUsesDefaultTaskQueueWhenNoProcessEnvironmentOverride() throws Exception { Map env = baseEnv(); env.put(LambdaWorkerOptions.TEMPORAL_TASK_QUEUE, "from-env"); - LambdaWorkerOptions options = LambdaWorkerOptions.fromEnvironment(env); + LambdaWorkerOptions.Builder builder = LambdaWorkerOptions.newBuilderFromEnvironment(env); - LambdaWorkerSample.configure(options); + LambdaWorkerSample.configure(builder); + LambdaWorkerOptions options = builder.build(); assertEquals(LambdaWorkerSample.DEFAULT_TASK_QUEUE, options.getTaskQueue()); assertEquals(2, registrations(options).size()); From a4a78eb21a716e72bcd1195e9f490b792ab0b1bc Mon Sep 17 00:00:00 2001 From: Edward Amsden Date: Mon, 13 Jul 2026 15:26:39 -0500 Subject: [PATCH 3/6] Update Lambda worker sample for merged SDK --- lambda-worker/README.md | 7 ++++--- lambda-worker/build.gradle | 19 +++++++++++++++---- .../samples/lambdaworker/LambdaFunction.java | 2 +- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lambda-worker/README.md b/lambda-worker/README.md index 44cc4668..fb6b1a05 100644 --- a/lambda-worker/README.md +++ b/lambda-worker/README.md @@ -5,9 +5,10 @@ It registers a simple greeting Workflow and Activity, configures Worker Deployme Versioning, and includes helper scripts for packaging the Lambda and configuring Temporal Cloud invocation. -For local SDK co-development, create `settings.local.gradle` in the repository root and -add `includeBuild '../sdk-java'`. That file is ignored by Git and makes Gradle use the -in-development `io.temporal:temporal-aws-lambda` SDK add-on before it is published. +This sample uses the `1.37.0-SNAPSHOT` SDK artifacts for AWS Lambda worker support. For +local SDK co-development, create `settings.local.gradle` in the repository root and add +`includeBuild '../sdk-java'`. That file is ignored by Git and makes Gradle use the local +SDK checkout instead of resolving snapshot artifacts. ## Prerequisites diff --git a/lambda-worker/build.gradle b/lambda-worker/build.gradle index a1658c53..266a08d2 100644 --- a/lambda-worker/build.gradle +++ b/lambda-worker/build.gradle @@ -1,10 +1,21 @@ +def lambdaWorkerJavaSDKVersion = '1.37.0-SNAPSHOT' + +repositories { + maven { + url "https://central.sonatype.com/repository/maven-snapshots/" + mavenContent { + snapshotsOnly() + } + } +} + dependencies { - implementation "io.temporal:temporal-sdk:$javaSDKVersion" - implementation "io.temporal:temporal-envconfig:$javaSDKVersion" - implementation "io.temporal:temporal-aws-lambda:$javaSDKVersion" + implementation "io.temporal:temporal-sdk:$lambdaWorkerJavaSDKVersion" + implementation "io.temporal:temporal-envconfig:$lambdaWorkerJavaSDKVersion" + implementation "io.temporal:temporal-aws-lambda:$lambdaWorkerJavaSDKVersion" implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.6' - testImplementation "io.temporal:temporal-testing:$javaSDKVersion" + testImplementation "io.temporal:temporal-testing:$lambdaWorkerJavaSDKVersion" testImplementation "junit:junit:4.13.2" testImplementation(platform("org.junit:junit-bom:5.10.3")) testRuntimeOnly "org.junit.vintage:junit-vintage-engine" diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java index 547a6d71..9dd78c22 100644 --- a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java +++ b/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java @@ -9,7 +9,7 @@ public class LambdaFunction implements RequestHandler { private static final RequestHandler WORKER = - LambdaWorker.run( + LambdaWorker.define( new WorkerDeploymentVersion( LambdaWorkerSample.deploymentName(), LambdaWorkerSample.buildId()), LambdaWorkerSample::configure); From fdb039a074b6b667858ec8a5479f87882966fc60 Mon Sep 17 00:00:00 2001 From: Edward Amsden Date: Wed, 15 Jul 2026 12:32:40 -0500 Subject: [PATCH 4/6] Use released SDK dependencies for Lambda sample --- .gitignore | 1 - lambda-worker/README.md | 5 +---- lambda-worker/build.gradle | 19 ++++--------------- settings.gradle | 5 ----- 4 files changed, 5 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index f587ebda..a1abef91 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ target .DS_Store .idea .gradle -settings.local.gradle **/build/ **/out/ .classpath diff --git a/lambda-worker/README.md b/lambda-worker/README.md index fb6b1a05..62b03439 100644 --- a/lambda-worker/README.md +++ b/lambda-worker/README.md @@ -5,10 +5,7 @@ It registers a simple greeting Workflow and Activity, configures Worker Deployme Versioning, and includes helper scripts for packaging the Lambda and configuring Temporal Cloud invocation. -This sample uses the `1.37.0-SNAPSHOT` SDK artifacts for AWS Lambda worker support. For -local SDK co-development, create `settings.local.gradle` in the repository root and add -`includeBuild '../sdk-java'`. That file is ignored by Git and makes Gradle use the local -SDK checkout instead of resolving snapshot artifacts. +It uses the same published Temporal Java SDK version as the other samples in this repository. ## Prerequisites diff --git a/lambda-worker/build.gradle b/lambda-worker/build.gradle index 266a08d2..a1658c53 100644 --- a/lambda-worker/build.gradle +++ b/lambda-worker/build.gradle @@ -1,21 +1,10 @@ -def lambdaWorkerJavaSDKVersion = '1.37.0-SNAPSHOT' - -repositories { - maven { - url "https://central.sonatype.com/repository/maven-snapshots/" - mavenContent { - snapshotsOnly() - } - } -} - dependencies { - implementation "io.temporal:temporal-sdk:$lambdaWorkerJavaSDKVersion" - implementation "io.temporal:temporal-envconfig:$lambdaWorkerJavaSDKVersion" - implementation "io.temporal:temporal-aws-lambda:$lambdaWorkerJavaSDKVersion" + implementation "io.temporal:temporal-sdk:$javaSDKVersion" + implementation "io.temporal:temporal-envconfig:$javaSDKVersion" + implementation "io.temporal:temporal-aws-lambda:$javaSDKVersion" implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.6' - testImplementation "io.temporal:temporal-testing:$lambdaWorkerJavaSDKVersion" + testImplementation "io.temporal:temporal-testing:$javaSDKVersion" testImplementation "junit:junit:4.13.2" testImplementation(platform("org.junit:junit-bom:5.10.3")) testRuntimeOnly "org.junit.vintage:junit-vintage-engine" diff --git a/settings.gradle b/settings.gradle index c6756a18..b97d02a4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,10 +1,5 @@ rootProject.name = 'temporal-java-samples' -def localSettings = file('settings.local.gradle') -if (localSettings.exists()) { - apply from: localSettings -} - include 'core' include 'springai:basic' include 'springai:mcp' From a76c0882dfdf3635bd8e5b54f85cf6739e571439 Mon Sep 17 00:00:00 2001 From: Edward Amsden Date: Tue, 21 Jul 2026 12:24:26 -0500 Subject: [PATCH 5/6] Refactor Lambda worker sample layout --- lambda-worker/.gitignore | 2 + lambda-worker/README.md | 70 +++++++++---------- lambda-worker/{ => deploy}/deploy-lambda.sh | 7 +- .../enable-telemetry.sh} | 8 +-- ...-role-for-temporal-lambda-invoke-test.yaml | 0 lambda-worker/{ => deploy}/mk-iam-role.sh | 3 +- ...le => otel-collector-config.template.yaml} | 0 .../lambdaworker/LambdaWorkerSampleTest.java | 64 ----------------- lambda-worker/starter/build.gradle | 19 +++++ .../samples/lambdaworker/Starter.java | 10 ++- ...ral.toml.sample => temporal.template.toml} | 5 +- lambda-worker/{ => worker}/build.gradle | 8 --- .../lambdaworker/GreetingActivities.java | 0 .../lambdaworker/GreetingActivitiesImpl.java | 0 .../samples/lambdaworker/LambdaFunction.java | 6 +- .../lambdaworker/LambdaWorkerSample.java | 20 +----- .../samples/lambdaworker/SampleWorkflow.java | 0 .../lambdaworker/SampleWorkflowImpl.java | 0 .../src/main/resources/logback.xml | 0 .../lambdaworker/SampleWorkflowTest.java | 0 settings.gradle | 3 +- 21 files changed, 87 insertions(+), 138 deletions(-) create mode 100644 lambda-worker/.gitignore rename lambda-worker/{ => deploy}/deploy-lambda.sh (81%) rename lambda-worker/{extra-setup-steps => deploy/enable-telemetry.sh} (71%) rename lambda-worker/{ => deploy}/iam-role-for-temporal-lambda-invoke-test.yaml (100%) rename lambda-worker/{ => deploy}/mk-iam-role.sh (80%) rename lambda-worker/{otel-collector-config.yaml.sample => otel-collector-config.template.yaml} (100%) delete mode 100644 lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java create mode 100644 lambda-worker/starter/build.gradle rename lambda-worker/{ => starter}/src/main/java/io/temporal/samples/lambdaworker/Starter.java (80%) rename lambda-worker/{temporal.toml.sample => temporal.template.toml} (56%) rename lambda-worker/{ => worker}/build.gradle (90%) rename lambda-worker/{ => worker}/src/main/java/io/temporal/samples/lambdaworker/GreetingActivities.java (100%) rename lambda-worker/{ => worker}/src/main/java/io/temporal/samples/lambdaworker/GreetingActivitiesImpl.java (100%) rename lambda-worker/{ => worker}/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java (72%) rename lambda-worker/{ => worker}/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java (51%) rename lambda-worker/{ => worker}/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflow.java (100%) rename lambda-worker/{ => worker}/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflowImpl.java (100%) rename lambda-worker/{ => worker}/src/main/resources/logback.xml (100%) rename lambda-worker/{ => worker}/src/test/java/io/temporal/samples/lambdaworker/SampleWorkflowTest.java (100%) diff --git a/lambda-worker/.gitignore b/lambda-worker/.gitignore new file mode 100644 index 00000000..69b7c323 --- /dev/null +++ b/lambda-worker/.gitignore @@ -0,0 +1,2 @@ +temporal.toml +otel-collector-config.yaml diff --git a/lambda-worker/README.md b/lambda-worker/README.md index 62b03439..00eb109d 100644 --- a/lambda-worker/README.md +++ b/lambda-worker/README.md @@ -5,6 +5,9 @@ It registers a simple greeting Workflow and Activity, configures Worker Deployme Versioning, and includes helper scripts for packaging the Lambda and configuring Temporal Cloud invocation. +The deployable Worker and local Workflow starter are separate Gradle projects, so +starter-only code and dependencies are not included in the Lambda artifact. + It uses the same published Temporal Java SDK version as the other samples in this repository. ## Prerequisites @@ -17,27 +20,19 @@ It uses the same published Temporal Java SDK version as the other samples in thi - A Temporal Cloud API key. This walkthrough deploys it as a Lambda environment variable because these are development-only secrets. -## Files - -| File | Description | -|------|-------------| -| `src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java` | AWS Lambda handler | -| `src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java` | Shared task queue, deployment version, and worker registrations | -| `src/main/java/io/temporal/samples/lambdaworker/SampleWorkflow*.java` | Sample Workflow interface and implementation | -| `src/main/java/io/temporal/samples/lambdaworker/GreetingActivities*.java` | Sample Activity interface and implementation | -| `src/main/java/io/temporal/samples/lambdaworker/Starter.java` | Local helper that starts a Workflow for the Lambda worker | -| `temporal.toml.sample` | Temporal client connection configuration | -| `otel-collector-config.yaml.sample` | Optional ADOT collector configuration | -| `deploy-lambda.sh` | Builds and uploads the Lambda deployment package | -| `mk-iam-role.sh` | Creates the role Temporal Cloud assumes to invoke Lambda | -| `iam-role-for-temporal-lambda-invoke-test.yaml` | CloudFormation template for the invocation role | -| `extra-setup-steps` | Optional IAM and Lambda settings for OpenTelemetry | +## Layout + +- `worker/` contains the Lambda handler, Workflow, Activity, and deployable Worker project. +- `starter/` contains the local Workflow starter project. +- `deploy/` contains the AWS deployment scripts and CloudFormation template. +- `temporal.template.toml` and `otel-collector-config.template.yaml` are configuration + templates for local and Lambda setup. ## Build ```bash -./gradlew :lambda-worker:test -./gradlew :lambda-worker:shadowJar +./gradlew :lambda-worker:worker:test +./gradlew :lambda-worker:worker:shadowJar ``` The Lambda handler string is: @@ -79,18 +74,20 @@ TEMPORAL_ADDRESS TEMPORAL_NAMESPACE TEMPORAL_API_KEY TEMPORAL_TASK_QUEUE -TEMPORAL_WORKER_DEPLOYMENT_NAME -TEMPORAL_WORKER_BUILD_ID +TEMPORAL_LAMBDA_DEPLOYMENT_NAME +TEMPORAL_LAMBDA_BUILD_ID ``` -The local starter also reads `TEMPORAL_TASK_QUEUE` and `TEMPORAL_WORKFLOW_ID_PREFIX`. +The local starter also reads `TEMPORAL_TASK_QUEUE` and +`TEMPORAL_LAMBDA_WORKFLOW_ID_PREFIX`. -You can also use `temporal.toml.sample` as a starting point for `temporal.toml` and set -`TEMPORAL_CONFIG_FILE=temporal.toml`. +You can also copy `lambda-worker/temporal.template.toml` to +`lambda-worker/temporal.toml`, fill in the connection details, and set +`TEMPORAL_CONFIG_FILE=lambda-worker/temporal.toml`. The generated file is ignored by Git. -`TEMPORAL_TASK_QUEUE`, `TEMPORAL_WORKER_DEPLOYMENT_NAME`, -`TEMPORAL_WORKER_BUILD_ID`, and `TEMPORAL_WORKFLOW_ID_PREFIX` are optional. The values -above are the sample defaults. +`TEMPORAL_TASK_QUEUE`, `TEMPORAL_LAMBDA_DEPLOYMENT_NAME`, +`TEMPORAL_LAMBDA_BUILD_ID`, and `TEMPORAL_LAMBDA_WORKFLOW_ID_PREFIX` are optional. The +values above are the sample defaults. ## Deploy Lambda @@ -135,15 +132,15 @@ for direct upload in this sample; use S3 if your local artifact grows beyond Lam direct upload limit. ```bash -./gradlew :lambda-worker:shadowJar +./gradlew :lambda-worker:worker:shadowJar aws lambda create-function \ --function-name "$FUNCTION_NAME" \ --runtime java17 \ --handler io.temporal.samples.lambdaworker.LambdaFunction::handleRequest \ --role "$EXECUTION_ROLE_ARN" \ - --zip-file fileb://lambda-worker/build/libs/lambda-worker-1.0.0-all.jar \ - --environment "Variables={TEMPORAL_ADDRESS=$TEMPORAL_ADDRESS,TEMPORAL_NAMESPACE=$TEMPORAL_NAMESPACE,TEMPORAL_API_KEY=$TEMPORAL_API_KEY,TEMPORAL_TASK_QUEUE=$TASK_QUEUE,TEMPORAL_WORKER_DEPLOYMENT_NAME=$DEPLOYMENT_NAME,TEMPORAL_WORKER_BUILD_ID=$BUILD_ID}" \ + --zip-file fileb://lambda-worker/worker/build/libs/lambda-worker-1.0.0-all.jar \ + --environment "Variables={TEMPORAL_ADDRESS=$TEMPORAL_ADDRESS,TEMPORAL_NAMESPACE=$TEMPORAL_NAMESPACE,TEMPORAL_API_KEY=$TEMPORAL_API_KEY,TEMPORAL_TASK_QUEUE=$TASK_QUEUE,TEMPORAL_LAMBDA_DEPLOYMENT_NAME=$DEPLOYMENT_NAME,TEMPORAL_LAMBDA_BUILD_ID=$BUILD_ID}" \ --timeout 90 \ --memory-size 1024 \ --query 'FunctionArn' \ @@ -162,13 +159,13 @@ export FUNCTION_ARN="$( To update code after the function exists: ```bash -./lambda-worker/deploy-lambda.sh "$FUNCTION_NAME" +./lambda-worker/deploy/deploy-lambda.sh "$FUNCTION_NAME" ``` If direct upload is too large, set `LAMBDA_CODE_S3_BUCKET` and rerun: ```bash -LAMBDA_CODE_S3_BUCKET= ./lambda-worker/deploy-lambda.sh "$FUNCTION_NAME" +LAMBDA_CODE_S3_BUCKET= ./lambda-worker/deploy/deploy-lambda.sh "$FUNCTION_NAME" ``` ## Configure Invocation @@ -176,9 +173,7 @@ LAMBDA_CODE_S3_BUCKET= ./lambda-worker/deploy-lambda.sh "$FUNCTION_ Create the IAM role that Temporal Cloud assumes to invoke the Lambda: ```bash -cd lambda-worker -./mk-iam-role.sh "$STACK_NAME" "$EXTERNAL_ID" "$FUNCTION_ARN" -cd .. +./lambda-worker/deploy/mk-iam-role.sh "$STACK_NAME" "$EXTERNAL_ID" "$FUNCTION_ARN" aws cloudformation wait stack-create-complete --stack-name "$STACK_NAME" @@ -232,10 +227,9 @@ After the Worker Deployment Version is current, start the sample Workflow: ```bash export TEMPORAL_TASK_QUEUE="$TASK_QUEUE" -export TEMPORAL_WORKFLOW_ID_PREFIX="$WORKFLOW_PREFIX" +export TEMPORAL_LAMBDA_WORKFLOW_ID_PREFIX="$WORKFLOW_PREFIX" -./gradlew -q :lambda-worker:execute \ - -PmainClass=io.temporal.samples.lambdaworker.Starter +./gradlew -q :lambda-worker:starter:execute ``` The starter only creates a Workflow Execution. It does not start a local Worker. The @@ -246,3 +240,7 @@ Lambda function. For local development of the Workflow and Activity logic, run the unit tests. They use `TestWorkflowRule` and do not require AWS or a running Temporal Service. + +```bash +./gradlew :lambda-worker:worker:test +``` diff --git a/lambda-worker/deploy-lambda.sh b/lambda-worker/deploy/deploy-lambda.sh similarity index 81% rename from lambda-worker/deploy-lambda.sh rename to lambda-worker/deploy/deploy-lambda.sh index bcf94a2d..f18fd989 100755 --- a/lambda-worker/deploy-lambda.sh +++ b/lambda-worker/deploy/deploy-lambda.sh @@ -4,10 +4,11 @@ set -euo pipefail FUNCTION_NAME="${1:?Usage: deploy-lambda.sh }" MAX_DIRECT_UPLOAD_BYTES=50000000 -cd "$(dirname "$0")/.." -./gradlew :lambda-worker:shadowJar +REPOSITORY_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +cd "$REPOSITORY_DIR" +./gradlew :lambda-worker:worker:shadowJar -JAR_FILE="$(find lambda-worker/build/libs -name 'lambda-worker-*-all.jar' | head -n 1)" +JAR_FILE="$(find lambda-worker/worker/build/libs -name 'lambda-worker-*-all.jar' | head -n 1)" if stat -f%z "$JAR_FILE" >/dev/null 2>&1; then JAR_SIZE="$(stat -f%z "$JAR_FILE")" diff --git a/lambda-worker/extra-setup-steps b/lambda-worker/deploy/enable-telemetry.sh similarity index 71% rename from lambda-worker/extra-setup-steps rename to lambda-worker/deploy/enable-telemetry.sh index 8c6dd0f8..e0313194 100755 --- a/lambda-worker/extra-setup-steps +++ b/lambda-worker/deploy/enable-telemetry.sh @@ -1,10 +1,10 @@ #!/bin/bash set -euo pipefail -ROLE_NAME="${1:?Usage: extra-setup-steps }" -FUNCTION_NAME="${2:?Usage: extra-setup-steps }" -REGION="${3:?Usage: extra-setup-steps }" -ACCOUNT_ID="${4:?Usage: extra-setup-steps }" +ROLE_NAME="${1:?Usage: enable-telemetry.sh }" +FUNCTION_NAME="${2:?Usage: enable-telemetry.sh }" +REGION="${3:?Usage: enable-telemetry.sh }" +ACCOUNT_ID="${4:?Usage: enable-telemetry.sh }" aws iam put-role-policy \ --role-name "$ROLE_NAME" \ diff --git a/lambda-worker/iam-role-for-temporal-lambda-invoke-test.yaml b/lambda-worker/deploy/iam-role-for-temporal-lambda-invoke-test.yaml similarity index 100% rename from lambda-worker/iam-role-for-temporal-lambda-invoke-test.yaml rename to lambda-worker/deploy/iam-role-for-temporal-lambda-invoke-test.yaml diff --git a/lambda-worker/mk-iam-role.sh b/lambda-worker/deploy/mk-iam-role.sh similarity index 80% rename from lambda-worker/mk-iam-role.sh rename to lambda-worker/deploy/mk-iam-role.sh index 20aeae5d..da596849 100755 --- a/lambda-worker/mk-iam-role.sh +++ b/lambda-worker/deploy/mk-iam-role.sh @@ -4,10 +4,11 @@ set -euo pipefail STACK_NAME="${1:?Usage: mk-iam-role.sh }" EXTERNAL_ID="${2:?Usage: mk-iam-role.sh }" LAMBDA_ARN="${3:?Usage: mk-iam-role.sh }" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" aws cloudformation create-stack \ --stack-name "$STACK_NAME" \ - --template-body file://iam-role-for-temporal-lambda-invoke-test.yaml \ + --template-body "file://${SCRIPT_DIR}/iam-role-for-temporal-lambda-invoke-test.yaml" \ --parameters \ ParameterKey=AssumeRoleExternalId,ParameterValue="$EXTERNAL_ID" \ ParameterKey=LambdaFunctionARNs,ParameterValue="\"$LAMBDA_ARN\"" \ diff --git a/lambda-worker/otel-collector-config.yaml.sample b/lambda-worker/otel-collector-config.template.yaml similarity index 100% rename from lambda-worker/otel-collector-config.yaml.sample rename to lambda-worker/otel-collector-config.template.yaml diff --git a/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java b/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java deleted file mode 100644 index b1c9a21b..00000000 --- a/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/LambdaWorkerSampleTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.temporal.samples.lambdaworker; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import com.amazonaws.services.lambda.runtime.RequestHandler; -import io.temporal.aws.lambda.LambdaWorker; -import io.temporal.aws.lambda.LambdaWorkerOptions; -import io.temporal.common.WorkerDeploymentVersion; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.Test; - -/** Tests Lambda worker registration without AWS. */ -public class LambdaWorkerSampleTest { - - @Test - public void configureSetsTaskQueueAndRegistrations() throws Exception { - LambdaWorkerOptions.Builder builder = LambdaWorkerOptions.newBuilderFromEnvironment(baseEnv()); - - LambdaWorkerSample.configure(builder); - LambdaWorkerOptions options = builder.build(); - - assertEquals(LambdaWorkerSample.DEFAULT_TASK_QUEUE, options.getTaskQueue()); - assertEquals(2, registrations(options).size()); - - RequestHandler handler = - LambdaWorker.newHandler( - new WorkerDeploymentVersion( - LambdaWorkerSample.DEFAULT_DEPLOYMENT_NAME, LambdaWorkerSample.DEFAULT_BUILD_ID), - options); - assertNotNull(handler); - } - - @Test - public void configureUsesDefaultTaskQueueWhenNoProcessEnvironmentOverride() throws Exception { - Map env = baseEnv(); - env.put(LambdaWorkerOptions.TEMPORAL_TASK_QUEUE, "from-env"); - LambdaWorkerOptions.Builder builder = LambdaWorkerOptions.newBuilderFromEnvironment(env); - - LambdaWorkerSample.configure(builder); - LambdaWorkerOptions options = builder.build(); - - assertEquals(LambdaWorkerSample.DEFAULT_TASK_QUEUE, options.getTaskQueue()); - assertEquals(2, registrations(options).size()); - } - - private static Map baseEnv() { - Map env = new HashMap<>(); - env.put(LambdaWorkerOptions.TEMPORAL_CONFIG_FILE, "/nonexistent/temporal.toml"); - return env; - } - - private static List registrations(LambdaWorkerOptions options) throws Exception { - Field registrations = LambdaWorkerOptions.class.getDeclaredField("registrations"); - registrations.setAccessible(true); - Object value = registrations.get(options); - assertTrue(value instanceof List); - return (List) value; - } -} diff --git a/lambda-worker/starter/build.gradle b/lambda-worker/starter/build.gradle new file mode 100644 index 00000000..df615ecc --- /dev/null +++ b/lambda-worker/starter/build.gradle @@ -0,0 +1,19 @@ +dependencies { + implementation project(':lambda-worker:worker') + implementation "io.temporal:temporal-sdk:$javaSDKVersion" + implementation "io.temporal:temporal-envconfig:$javaSDKVersion" + runtimeOnly group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.6' + + dependencies { + errorproneJavac('com.google.errorprone:javac:9+181-r4173-1') + errorprone('com.google.errorprone:error_prone_core:2.28.0') + } +} + +tasks.register('execute', JavaExec) { + mainClass = 'io.temporal.samples.lambdaworker.Starter' + classpath = sourceSets.main.runtimeClasspath + if (findProperty("args")) { + args findProperty("args").tokenize() + } +} diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/Starter.java b/lambda-worker/starter/src/main/java/io/temporal/samples/lambdaworker/Starter.java similarity index 80% rename from lambda-worker/src/main/java/io/temporal/samples/lambdaworker/Starter.java rename to lambda-worker/starter/src/main/java/io/temporal/samples/lambdaworker/Starter.java index c8e46a4b..6ad4afa4 100644 --- a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/Starter.java +++ b/lambda-worker/starter/src/main/java/io/temporal/samples/lambdaworker/Starter.java @@ -13,6 +13,9 @@ /** Local helper that starts a Workflow execution for the Lambda worker to process. */ public class Starter { + private static final String WORKFLOW_ID_PREFIX_ENV = "TEMPORAL_LAMBDA_WORKFLOW_ID_PREFIX"; + private static final String DEFAULT_WORKFLOW_ID_PREFIX = "serverless-workflow-id-java"; + public static void main(String[] args) { String name = args.length > 0 ? String.join(" ", args) : "Serverless Lambda Worker!"; @@ -27,7 +30,7 @@ public static void main(String[] args) { client.newWorkflowStub( SampleWorkflow.class, WorkflowOptions.newBuilder() - .setWorkflowId(LambdaWorkerSample.workflowIdPrefix() + "-" + UUID.randomUUID()) + .setWorkflowId(workflowIdPrefix() + "-" + UUID.randomUUID()) .setTaskQueue(LambdaWorkerSample.taskQueue()) .build()); @@ -47,4 +50,9 @@ public static void main(String[] args) { } } } + + private static String workflowIdPrefix() { + String value = System.getenv(WORKFLOW_ID_PREFIX_ENV); + return value == null || value.isBlank() ? DEFAULT_WORKFLOW_ID_PREFIX : value; + } } diff --git a/lambda-worker/temporal.toml.sample b/lambda-worker/temporal.template.toml similarity index 56% rename from lambda-worker/temporal.toml.sample rename to lambda-worker/temporal.template.toml index 0117d5cf..d478e3eb 100644 --- a/lambda-worker/temporal.toml.sample +++ b/lambda-worker/temporal.template.toml @@ -1,9 +1,10 @@ [profile.default] address = "..tmprl.cloud:7233" namespace = "." -api_key = "" -# For mTLS instead of API key auth, remove api_key and uncomment: +# Set TEMPORAL_API_KEY in the environment instead of storing it in this file. + +# For mTLS instead of API key auth, leave TEMPORAL_API_KEY unset and uncomment: # [profile.default.tls] # client_cert_path = "client.pem" # client_key_path = "client.key" diff --git a/lambda-worker/build.gradle b/lambda-worker/worker/build.gradle similarity index 90% rename from lambda-worker/build.gradle rename to lambda-worker/worker/build.gradle index a1658c53..a1937981 100644 --- a/lambda-worker/build.gradle +++ b/lambda-worker/worker/build.gradle @@ -15,14 +15,6 @@ dependencies { } } -tasks.register('execute', JavaExec) { - mainClass = findProperty("mainClass") ?: "" - classpath = sourceSets.main.runtimeClasspath - if (findProperty("args")) { - args findProperty("args").tokenize() - } -} - tasks.register('shadowJar', Jar) { archiveBaseName = 'lambda-worker' archiveClassifier = 'all' diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivities.java b/lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivities.java similarity index 100% rename from lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivities.java rename to lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivities.java diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivitiesImpl.java b/lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivitiesImpl.java similarity index 100% rename from lambda-worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivitiesImpl.java rename to lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/GreetingActivitiesImpl.java diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java b/lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java similarity index 72% rename from lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java rename to lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java index 9dd78c22..5fc5eb39 100644 --- a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java +++ b/lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/LambdaFunction.java @@ -12,7 +12,11 @@ public class LambdaFunction implements RequestHandler { LambdaWorker.define( new WorkerDeploymentVersion( LambdaWorkerSample.deploymentName(), LambdaWorkerSample.buildId()), - LambdaWorkerSample::configure); + builder -> { + builder.setTaskQueue(LambdaWorkerSample.taskQueue()); + builder.registerWorkflowImplementationTypes(SampleWorkflowImpl.class); + builder.registerActivitiesImplementations(new GreetingActivitiesImpl()); + }); @Override public Void handleRequest(Object input, Context context) { diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java b/lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java similarity index 51% rename from lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java rename to lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java index 77c3282a..b24c87ed 100644 --- a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java +++ b/lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/LambdaWorkerSample.java @@ -1,34 +1,20 @@ package io.temporal.samples.lambdaworker; -import io.temporal.aws.lambda.LambdaWorkerOptions; - -/** Shared constants and worker registration for the Lambda Worker sample. */ +/** Shared configuration for the Lambda Worker sample. */ public final class LambdaWorkerSample { public static final String TASK_QUEUE_ENV = "TEMPORAL_TASK_QUEUE"; - public static final String DEPLOYMENT_NAME_ENV = "TEMPORAL_WORKER_DEPLOYMENT_NAME"; - public static final String BUILD_ID_ENV = "TEMPORAL_WORKER_BUILD_ID"; - public static final String WORKFLOW_ID_PREFIX_ENV = "TEMPORAL_WORKFLOW_ID_PREFIX"; + public static final String DEPLOYMENT_NAME_ENV = "TEMPORAL_LAMBDA_DEPLOYMENT_NAME"; + public static final String BUILD_ID_ENV = "TEMPORAL_LAMBDA_BUILD_ID"; public static final String DEFAULT_TASK_QUEUE = "serverless-task-queue-java"; - public static final String DEFAULT_WORKFLOW_ID_PREFIX = "serverless-workflow-id-java"; public static final String DEFAULT_DEPLOYMENT_NAME = "my-app"; public static final String DEFAULT_BUILD_ID = "build-1"; - public static void configure(LambdaWorkerOptions.Builder builder) { - builder.setTaskQueue(taskQueue()); - builder.registerWorkflowImplementationTypes(SampleWorkflowImpl.class); - builder.registerActivitiesImplementations(new GreetingActivitiesImpl()); - } - public static String taskQueue() { return envOrDefault(TASK_QUEUE_ENV, DEFAULT_TASK_QUEUE); } - public static String workflowIdPrefix() { - return envOrDefault(WORKFLOW_ID_PREFIX_ENV, DEFAULT_WORKFLOW_ID_PREFIX); - } - public static String deploymentName() { return envOrDefault(DEPLOYMENT_NAME_ENV, DEFAULT_DEPLOYMENT_NAME); } diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflow.java b/lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflow.java similarity index 100% rename from lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflow.java rename to lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflow.java diff --git a/lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflowImpl.java b/lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflowImpl.java similarity index 100% rename from lambda-worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflowImpl.java rename to lambda-worker/worker/src/main/java/io/temporal/samples/lambdaworker/SampleWorkflowImpl.java diff --git a/lambda-worker/src/main/resources/logback.xml b/lambda-worker/worker/src/main/resources/logback.xml similarity index 100% rename from lambda-worker/src/main/resources/logback.xml rename to lambda-worker/worker/src/main/resources/logback.xml diff --git a/lambda-worker/src/test/java/io/temporal/samples/lambdaworker/SampleWorkflowTest.java b/lambda-worker/worker/src/test/java/io/temporal/samples/lambdaworker/SampleWorkflowTest.java similarity index 100% rename from lambda-worker/src/test/java/io/temporal/samples/lambdaworker/SampleWorkflowTest.java rename to lambda-worker/worker/src/test/java/io/temporal/samples/lambdaworker/SampleWorkflowTest.java diff --git a/settings.gradle b/settings.gradle index b97d02a4..b19f2fd7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,4 +7,5 @@ include 'springai:multimodel' include 'springai:rag' include 'springboot' include 'springboot-basic' -include 'lambda-worker' +include 'lambda-worker:starter' +include 'lambda-worker:worker' From de942142ac3ccef1f9c089e4f865ce96d4aa0562 Mon Sep 17 00:00:00 2001 From: Edward Amsden Date: Tue, 21 Jul 2026 12:55:53 -0500 Subject: [PATCH 6/6] Avoid exposing Lambda environment during deploy --- lambda-worker/deploy/deploy-lambda.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lambda-worker/deploy/deploy-lambda.sh b/lambda-worker/deploy/deploy-lambda.sh index f18fd989..7341193e 100755 --- a/lambda-worker/deploy/deploy-lambda.sh +++ b/lambda-worker/deploy/deploy-lambda.sh @@ -22,7 +22,9 @@ if [[ -n "${LAMBDA_CODE_S3_BUCKET:-}" ]]; then aws lambda update-function-code \ --function-name "$FUNCTION_NAME" \ --s3-bucket "$LAMBDA_CODE_S3_BUCKET" \ - --s3-key "$S3_KEY" + --s3-key "$S3_KEY" \ + --query 'FunctionArn' \ + --output text exit 0 fi @@ -32,4 +34,8 @@ if (( JAR_SIZE > MAX_DIRECT_UPLOAD_BYTES )); then exit 1 fi -aws lambda update-function-code --function-name "$FUNCTION_NAME" --zip-file "fileb://$JAR_FILE" +aws lambda update-function-code \ + --function-name "$FUNCTION_NAME" \ + --zip-file "fileb://$JAR_FILE" \ + --query 'FunctionArn' \ + --output text