From 3911e00807d191ddf026a28c31b8d353ffc1ed59 Mon Sep 17 00:00:00 2001 From: Stephan Ewen Date: Tue, 19 Nov 2024 12:17:31 +0100 Subject: [PATCH 1/3] Some fixes to Payment State Machine example --- .../java/my/example/PaymentProcessor.java | 65 +++++++++---------- .../main/java/my/example/types/Result.java | 9 +++ .../java/my/example/utils/TypeChecks.java | 26 -------- 3 files changed, 41 insertions(+), 59 deletions(-) delete mode 100644 patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/utils/TypeChecks.java diff --git a/patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/PaymentProcessor.java b/patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/PaymentProcessor.java index 804ac682..38c5a556 100644 --- a/patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/PaymentProcessor.java +++ b/patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/PaymentProcessor.java @@ -11,11 +11,12 @@ package my.example; +import static my.example.types.PaymentStatus.*; + import dev.restate.sdk.ObjectContext; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.common.TerminalException; import dev.restate.sdk.serde.jackson.JacksonSerdes; import java.time.Duration; import my.example.accounts.AccountClient; @@ -24,35 +25,38 @@ import my.example.types.Result; /** - * The service that processes the payment requests. + * A service that processes the payment requests. * *

This is implemented as a virtual object to ensure that only one concurrent request can happen - * per token (requests are queued and processed sequentially per token). + * per payment-id. Requests are queued and processed sequentially per id. * - *

Note that this idempotency-token is more of an operation/payment-id. Methods can be called - * multiple times with the same token, but payment will be executed only once. Also, if a - * cancellation is triggered for that token, the payment will not happen or be undine, regardless of - * whether the cancel call comes before or after the payment call. + *

Methods can be called multiple times with the same payment-id, but payment will be executed + * only once. If a 'cancelPayment' is called for an id, the payment will either be undone, or + * blocked from being made in the future, depending on whether the cancel call comes before or after + * the 'makePayment' call. */ @VirtualObject public class PaymentProcessor { - private static final Duration EXPIRY_TIMEOUT = Duration.ofHours(1); - + /** The key under which we store the status. */ private static final StateKey STATUS = StateKey.of("status", JacksonSerdes.of(PaymentStatus.class)); + + /** The key under which we store the original payment request. */ private static final StateKey PAYMENT = StateKey.of("payment", JacksonSerdes.of(Payment.class)); + private static final Duration EXPIRY_TIMEOUT = Duration.ofDays(1); + @Handler public Result makePayment(ObjectContext ctx, Payment payment) { - // De-duplication to make calls idempotent - PaymentStatus status = ctx.get(STATUS).orElse(PaymentStatus.NEW); - if (status == PaymentStatus.CANCELLED) { + final String paymentId = ctx.key(); + final PaymentStatus status = ctx.get(STATUS).orElse(NEW); + + if (status == CANCELLED) { return new Result(false, "Payment already cancelled"); } - - if (status == PaymentStatus.COMPLETED_SUCCESSFULLY) { + if (status == COMPLETED_SUCCESSFULLY) { return new Result(false, "Payment already completed in prior call"); } @@ -63,13 +67,11 @@ public Result makePayment(ObjectContext ctx, Payment payment) { .await(); // Remember only on success, so that on failure (when we didn't charge) the external - // caller may retry this (with the same token), for the sake of this example + // caller may retry this (with the same payment-id), for the sake of this example if (paymentResult.isSuccess()) { - ctx.set(STATUS, PaymentStatus.COMPLETED_SUCCESSFULLY); + ctx.set(STATUS, COMPLETED_SUCCESSFULLY); ctx.set(PAYMENT, payment); - - String idempotencyToken = ctx.key(); - PaymentProcessorClient.fromContext(ctx, idempotencyToken).send(EXPIRY_TIMEOUT).expireToken(); + PaymentProcessorClient.fromContext(ctx, paymentId).send(EXPIRY_TIMEOUT).expire(); } return paymentResult; @@ -77,26 +79,24 @@ public Result makePayment(ObjectContext ctx, Payment payment) { @Handler public void cancelPayment(ObjectContext ctx) { - PaymentStatus status = ctx.get(STATUS).orElse(PaymentStatus.NEW); + PaymentStatus status = ctx.get(STATUS).orElse(NEW); switch (status) { case NEW -> { - // not seen this token before, mark as canceled, in case the cancellation + // not seen this payment-id before, mark as canceled, in case the cancellation // overtook the actual payment request (on the external caller's side) - ctx.set(STATUS, PaymentStatus.CANCELLED); - - PaymentProcessorClient.fromContext(ctx, ctx.key()).send(EXPIRY_TIMEOUT).expireToken(); - } - case CANCELLED -> { - // already cancelled, this is a repeated request + ctx.set(STATUS, CANCELLED); + PaymentProcessorClient.fromContext(ctx, ctx.key()).send(EXPIRY_TIMEOUT).expire(); } + + case CANCELLED -> {} + case COMPLETED_SUCCESSFULLY -> { // remember this as cancelled - ctx.set(STATUS, PaymentStatus.CANCELLED); + ctx.set(STATUS, CANCELLED); // undo the payment - Payment payment = - ctx.get(PAYMENT).orElseThrow(() -> new TerminalException("Payment not found")); + Payment payment = ctx.get(PAYMENT).get(); AccountClient.fromContext(ctx, payment.getAccountId()) .send() .deposit(payment.getAmountCents()); @@ -105,8 +105,7 @@ public void cancelPayment(ObjectContext ctx) { } @Handler - public void expireToken(ObjectContext ctx) { - ctx.clear(STATUS); - ctx.clear(PAYMENT); + public void expire(ObjectContext ctx) { + ctx.clearAll(); } } diff --git a/patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/types/Result.java b/patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/types/Result.java index 88bed4c0..4928f482 100644 --- a/patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/types/Result.java +++ b/patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/types/Result.java @@ -22,4 +22,13 @@ public Result(boolean success, String reason) { public boolean isSuccess() { return success; } + + public String getReason() { + return reason; + } + + @Override + public String toString() { + return "Result{" + "success=" + success + ", reason='" + reason + '\'' + '}'; + } } diff --git a/patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/utils/TypeChecks.java b/patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/utils/TypeChecks.java deleted file mode 100644 index 0e0c68ec..00000000 --- a/patterns-use-cases/payment-state-machine/payment-state-machine-java/src/main/java/my/example/utils/TypeChecks.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate examples, - * which is released under the MIT license. - * - * You can find a copy of the license in the file LICENSE - * in the root directory of this repository or package or at - * https://github.com/restatedev/examples/ - */ -package my.example.utils; - -import dev.restate.sdk.common.TerminalException; -import my.example.types.Payment; - -public class TypeChecks { - - public static void validatePayment(Payment payment) { - if (payment.getAccountId() == null || payment.getAccountId().isEmpty()) { - throw new TerminalException("Account ID is required"); - } - if (payment.getAmountCents() <= 0) { - throw new TerminalException("Amount must be greater than 0"); - } - } -} From 4bec1ff06a390358de848d85182dece299e8af61 Mon Sep 17 00:00:00 2001 From: Stephan Ewen Date: Tue, 19 Nov 2024 15:02:40 +0100 Subject: [PATCH 2/3] Update .gitignore Format and add more useful exclusions --- .gitignore | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index e576ec8a..92def6c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,20 @@ -**/node_modules -**/dist -**/.vscode -**/.idea -**/package-lock.json -**/target -**/restate-data +# IDEs +.vscode +.idea *.iml + +# NodeJS +node_modules +dist +package-lock.json + +# Java +.gradle +build +target +out +**/src/main/generated +hs_err_pid* + +# Restate Server +restate-data From 6821fb89413e41cd90284ba78697f3b1e233cf31 Mon Sep 17 00:00:00 2001 From: Jack Kleeman Date: Sun, 15 Dec 2024 11:22:44 +0000 Subject: [PATCH 3/3] Add go lambda cdk example (#218) * Add go lambda cdk example * Update templates/go-lambda-cdk/README.md Co-authored-by: Pavel Tcholakov * go mod tidy --------- Co-authored-by: Pavel Tcholakov --- templates/go-lambda-cdk/.eslintignore | 3 + templates/go-lambda-cdk/.eslintrc.json | 15 ++++ templates/go-lambda-cdk/.gitignore | 10 +++ templates/go-lambda-cdk/.prettierrc | 7 ++ templates/go-lambda-cdk/README.md | 69 ++++++++++++++++ templates/go-lambda-cdk/bin/go-lambda-cdk.ts | 24 ++++++ templates/go-lambda-cdk/cdk.json | 59 ++++++++++++++ .../go-lambda-cdk/cdk/go-lambda-cdk-stack.ts | 78 +++++++++++++++++++ templates/go-lambda-cdk/lambda/.gitignore | 1 + templates/go-lambda-cdk/lambda/go.mod | 22 ++++++ templates/go-lambda-cdk/lambda/go.sum | 39 ++++++++++ templates/go-lambda-cdk/lambda/greeter.go | 33 ++++++++ templates/go-lambda-cdk/lambda/main.go | 23 ++++++ templates/go-lambda-cdk/lambda/utils.go | 24 ++++++ templates/go-lambda-cdk/package.json | 34 ++++++++ templates/go-lambda-cdk/tsconfig.json | 23 ++++++ 16 files changed, 464 insertions(+) create mode 100644 templates/go-lambda-cdk/.eslintignore create mode 100644 templates/go-lambda-cdk/.eslintrc.json create mode 100644 templates/go-lambda-cdk/.gitignore create mode 100644 templates/go-lambda-cdk/.prettierrc create mode 100644 templates/go-lambda-cdk/README.md create mode 100644 templates/go-lambda-cdk/bin/go-lambda-cdk.ts create mode 100644 templates/go-lambda-cdk/cdk.json create mode 100644 templates/go-lambda-cdk/cdk/go-lambda-cdk-stack.ts create mode 100644 templates/go-lambda-cdk/lambda/.gitignore create mode 100644 templates/go-lambda-cdk/lambda/go.mod create mode 100644 templates/go-lambda-cdk/lambda/go.sum create mode 100644 templates/go-lambda-cdk/lambda/greeter.go create mode 100644 templates/go-lambda-cdk/lambda/main.go create mode 100644 templates/go-lambda-cdk/lambda/utils.go create mode 100644 templates/go-lambda-cdk/package.json create mode 100644 templates/go-lambda-cdk/tsconfig.json diff --git a/templates/go-lambda-cdk/.eslintignore b/templates/go-lambda-cdk/.eslintignore new file mode 100644 index 00000000..5035c46c --- /dev/null +++ b/templates/go-lambda-cdk/.eslintignore @@ -0,0 +1,3 @@ +node_modules +dist +cdk.out \ No newline at end of file diff --git a/templates/go-lambda-cdk/.eslintrc.json b/templates/go-lambda-cdk/.eslintrc.json new file mode 100644 index 00000000..77b1f4de --- /dev/null +++ b/templates/go-lambda-cdk/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es2021": true + }, + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "overrides": [], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest" + }, + "plugins": ["@typescript-eslint"], + "rules": {} +} diff --git a/templates/go-lambda-cdk/.gitignore b/templates/go-lambda-cdk/.gitignore new file mode 100644 index 00000000..cea52e47 --- /dev/null +++ b/templates/go-lambda-cdk/.gitignore @@ -0,0 +1,10 @@ +!jest.config.js +node_modules +.cdk.staging +cdk.out +.gradle +lambda/go +*.js +*.d.ts +*.class +cdk.context.json diff --git a/templates/go-lambda-cdk/.prettierrc b/templates/go-lambda-cdk/.prettierrc new file mode 100644 index 00000000..da245af4 --- /dev/null +++ b/templates/go-lambda-cdk/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "semi": true, + "arrowParens": "always", + "printWidth": 120 +} diff --git a/templates/go-lambda-cdk/README.md b/templates/go-lambda-cdk/README.md new file mode 100644 index 00000000..148ba91f --- /dev/null +++ b/templates/go-lambda-cdk/README.md @@ -0,0 +1,69 @@ +# Go Lambda (CDK) example + +Sample project deploying a Go-based Restate service to AWS Lambda using the AWS Cloud Development Kit (CDK). +The stack uses the Restate CDK constructs library to register the service with a Restate Cloud environment. + +For more information on CDK, please see [Getting started with the AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html). + +* [CDK app entry point `lambda-go-cdk.ts`](bin/lambda-go-cdk.ts) +* [CDK stack consisting of a Lambda function and providing Restate service registration](cdk/lambda-go-cdk-stack.ts) +* [Go Lambda handler](lambda) - based on [`go` template](../go) + +## Download the example + +- Via the CLI: + ```shell + restate example go-lambda-cdk && cd go-lambda-cdk + ``` + +- Via git clone: + ```shell + git clone git@github.com:restatedev/examples.git + cd examples/templates/go-lambda-cdk + ``` + +- Via `wget`: + ```shell + wget https://github.com/restatedev/examples/releases/latest/download/go-lambda-cdk.zip && unzip go-lambda-cdk.zip -d go-lambda-cdk && rm go-lambda-cdk.zip + ``` + +## Deploy + +**Pre-requisites:** + +* npm +* Go >= 1.21 +* AWS account, bootstrapped for CDK use +* valid AWS credentials with sufficient privileges to create the necessary resources +* an existing [Restate Cloud](https://restate.dev) environment (environment id + API key) + +Install npm dependencies: + +```shell +npm install +``` + +To deploy the stack, export the Restate Cloud environment id and admin API key, and run `cdk deploy`: + +```shell +export RESTATE_ENV_ID=env_... RESTATE_API_KEY=key_... +npx cdk deploy +``` + +The stack output will print out the Restate server ingress URL. + +### Test + +You can send a test request to the Restate ingress endpoint to call the newly deployed service: + +```shell +curl -k ${restateIngressUrl}/Greeter/Greet \ + -H "Authorization: Bearer $RESTATE_API_KEY" \ + -H 'content-type: application/json' -d '"Restate"' +``` + +### Useful commands + +* `npm run build` compile the Lambda handler and synthesize CDK deployment artifacts +* `npm run deploy` perform a CDK deployment +* `npm run destroy` delete the stack and all its resources diff --git a/templates/go-lambda-cdk/bin/go-lambda-cdk.ts b/templates/go-lambda-cdk/bin/go-lambda-cdk.ts new file mode 100644 index 00000000..b1f9cfb1 --- /dev/null +++ b/templates/go-lambda-cdk/bin/go-lambda-cdk.ts @@ -0,0 +1,24 @@ +#!/usr/bin/env node + +/* + * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH + * + * This file is part of the Restate examples, + * which is released under the MIT license. + * + * You can find a copy of the license in the file LICENSE + * in the root directory of this repository or package or at + * https://github.com/restatedev/examples/ + */ + +import "source-map-support/register"; +import * as cdk from "aws-cdk-lib"; +import { GoLambdaCdkStack } from "../cdk/go-lambda-cdk-stack"; + +const app = new cdk.App(); +new GoLambdaCdkStack(app, "GoLambdaCdkStack", { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, +}); diff --git a/templates/go-lambda-cdk/cdk.json b/templates/go-lambda-cdk/cdk.json new file mode 100644 index 00000000..f3e4baed --- /dev/null +++ b/templates/go-lambda-cdk/cdk.json @@ -0,0 +1,59 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/go-lambda-cdk.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": ["aws", "aws-cn"], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true + } +} diff --git a/templates/go-lambda-cdk/cdk/go-lambda-cdk-stack.ts b/templates/go-lambda-cdk/cdk/go-lambda-cdk-stack.ts new file mode 100644 index 00000000..7dc6d9b9 --- /dev/null +++ b/templates/go-lambda-cdk/cdk/go-lambda-cdk-stack.ts @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH + * + * This file is part of the Restate examples, + * which is released under the MIT license. + * + * You can find a copy of the license in the file LICENSE + * in the root directory of this repository or package or at + * https://github.com/restatedev/examples/ + */ + +import * as restate from "@restatedev/restate-cdk"; +import * as cdk from "aws-cdk-lib"; +import * as lambda from "aws-cdk-lib/aws-lambda"; +import * as secrets from "aws-cdk-lib/aws-secretsmanager"; +import * as go from "@aws-cdk/aws-lambda-go-alpha"; +import { Construct } from "constructs"; + +export class GoLambdaCdkStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: cdk.StackProps) { + super(scope, id, props); + + const handler = new go.GoFunction( + this, + "GreeterService", + { + runtime: lambda.Runtime.PROVIDED_AL2023, + architecture: lambda.Architecture.ARM_64, + entry: "lambda", + timeout: cdk.Duration.seconds(10), + loggingFormat: lambda.LoggingFormat.JSON, + applicationLogLevelV2: lambda.ApplicationLogLevel.DEBUG, + systemLogLevelV2: lambda.SystemLogLevel.INFO, + }, + ); + + // Set the RESTATE_ENV_ID and RESTATE_API_KEY environment variables to point to your Restate Cloud environment. + // This construct automatically creates an invoker role that Restate Cloud will be able to assume to invoke handlers + // on behalf of your environment. See https://docs.restate.dev/deploy/cloud for more information. + const restateEnvironment = new restate.RestateCloudEnvironment( + this, + "RestateCloud", + { + environmentId: process.env.RESTATE_ENV_ID! as restate.EnvironmentId, + // Warning: this will result in the API key being baked into the CloudFormation template! + // For improved security, pre-populate the secret and pass it to the construct as a reference. + // See: https://docs.aws.amazon.com/secretsmanager/latest/userguide/cdk.html + apiKey: new secrets.Secret(this, "RestateCloudApiKey", { + secretStringValue: cdk.SecretValue.unsafePlainText( + process.env.RESTATE_API_KEY!, + ), + }), + }, + ); + const deployer = new restate.ServiceDeployer(this, "ServiceDeployer"); + + // Alternatively, you can deploy a standalone Restate server using the SingleNodeRestateDeployment construct. + // Please see https://docs.restate.dev/deploy/lambda/self-hosted and the construct documentation for more details. + // const vpc = ec2.Vpc.fromLookup(this, "Vpc", { vpcId: "..." }); + // const restateEnvironment = new restate.SingleNodeRestateDeployment(this, "Restate", { + // vpc, + // networkConfiguration: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, + // }); + // const deployer = new restate.ServiceDeployer(this, "ServiceDeployer", { + // vpc, + // securityGroups: [restateEnvironment.adminSecurityGroup], + // }); + + deployer.deployService( + "Greeter", + handler.currentVersion, + restateEnvironment, + ); + new cdk.CfnOutput(this, "restateIngressUrl", { + value: restateEnvironment.ingressUrl, + }); + } +} diff --git a/templates/go-lambda-cdk/lambda/.gitignore b/templates/go-lambda-cdk/lambda/.gitignore new file mode 100644 index 00000000..4023f209 --- /dev/null +++ b/templates/go-lambda-cdk/lambda/.gitignore @@ -0,0 +1 @@ +go diff --git a/templates/go-lambda-cdk/lambda/go.mod b/templates/go-lambda-cdk/lambda/go.mod new file mode 100644 index 00000000..70c476af --- /dev/null +++ b/templates/go-lambda-cdk/lambda/go.mod @@ -0,0 +1,22 @@ +module github.com/restatedev/examples/templates/go-lambda-cdk/lambda + +go 1.22.5 + +require ( + github.com/aws/aws-lambda-go v1.47.0 + github.com/restatedev/sdk-go v0.13.1 +) + +require ( + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/protobuf v1.35.2 // indirect +) diff --git a/templates/go-lambda-cdk/lambda/go.sum b/templates/go-lambda-cdk/lambda/go.sum new file mode 100644 index 00000000..b3365e4f --- /dev/null +++ b/templates/go-lambda-cdk/lambda/go.sum @@ -0,0 +1,39 @@ +github.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI= +github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/restatedev/sdk-go v0.13.1 h1:6DdbDaGI7mI8uXxpwBKjbGnByvYP18UHQ83bANi+wAM= +github.com/restatedev/sdk-go v0.13.1/go.mod h1:Xalv67a5uOgGcbz7U1BgZQydCrsmENq2RAeTwTGXHng= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/templates/go-lambda-cdk/lambda/greeter.go b/templates/go-lambda-cdk/lambda/greeter.go new file mode 100644 index 00000000..b939349d --- /dev/null +++ b/templates/go-lambda-cdk/lambda/greeter.go @@ -0,0 +1,33 @@ +package main + +import ( + restate "github.com/restatedev/sdk-go" + "time" +) + +// Greeter is a struct which represents a Restate service; reflection will turn exported methods into service handlers +type Greeter struct{} + +func (Greeter) Greet(ctx restate.Context, name string) (string, error) { + // Durably execute a set of steps; resilient against failures + greetingId := restate.Rand(ctx).UUID().String() + + if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { + return restate.Void{}, SendNotification(greetingId, name) + }); err != nil { + return "", err + } + + if err := restate.Sleep(ctx, 1*time.Second); err != nil { + return "", err + } + + if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { + return restate.Void{}, SendReminder(greetingId) + }); err != nil { + return "", err + } + + // Respond to caller + return "You said hi to " + name + "!", nil +} diff --git a/templates/go-lambda-cdk/lambda/main.go b/templates/go-lambda-cdk/lambda/main.go new file mode 100644 index 00000000..b1d7bf86 --- /dev/null +++ b/templates/go-lambda-cdk/lambda/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "log/slog" + "os" + + "github.com/aws/aws-lambda-go/lambda" + restate "github.com/restatedev/sdk-go" + "github.com/restatedev/sdk-go/server" +) + +func main() { + server := server.NewRestate(). + Bind(restate.Reflect(Greeter{})) + + handler, err := server.LambdaHandler() + if err != nil { + slog.Error("failed to create lambda handler", "err", err.Error()) + os.Exit(1) + } + + lambda.Start(handler) +} diff --git a/templates/go-lambda-cdk/lambda/utils.go b/templates/go-lambda-cdk/lambda/utils.go new file mode 100644 index 00000000..cf93dee5 --- /dev/null +++ b/templates/go-lambda-cdk/lambda/utils.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "math/rand" +) + +func SendNotification(greetingId string, name string) error { + if rand.Float32() < 0.5 { // 50% chance of failure + fmt.Printf("👻 Failed to send notification: %s - %s\n", greetingId, name) + return fmt.Errorf("failed to send notification: %s - %s", greetingId, name) + } + fmt.Printf("Notification sent: %s - %s\n", greetingId, name) + return nil +} + +func SendReminder(greetingId string) error { + if rand.Float32() < 0.5 { // 50% chance of failure + fmt.Printf("👻 Failed to send reminder: %s\n", greetingId) + return fmt.Errorf("failed to send reminder: %s", greetingId) + } + fmt.Printf("Reminder sent: %s\n", greetingId) + return nil +} diff --git a/templates/go-lambda-cdk/package.json b/templates/go-lambda-cdk/package.json new file mode 100644 index 00000000..a168235f --- /dev/null +++ b/templates/go-lambda-cdk/package.json @@ -0,0 +1,34 @@ +{ + "name": "go-lambda-cdk", + "version": "0.1.0", + "bin": { + "go-lambda-cdk": "bin/go-lambda-cdk.js" + }, + "scripts": { + "lint": "eslint --ignore-path .eslintignore --ext .ts .", + "build": "npm run build-lambda && npm run build-cdk", + "build-lambda": "cd lambda && ./gradlew build", + "build-cdk": "cdk synth", + "watch": "tsc -w", + "format": "prettier --ignore-path .eslintignore --write \"**/*.+(js|ts|json)\"", + "verify": "npm run format -- --check && npm run lint && npm run build", + "deploy": "npm run build && cdk deploy", + "destroy": "npx cdk destroy" + }, + "devDependencies": { + "@restatedev/restate-cdk": "^1.1.0-rc.1", + "@types/node": "22.5.2", + "@typescript-eslint/eslint-plugin": "^8.3.0", + "aws-cdk": "^2.155.0", + "esbuild": "^0.23.1", + "prettier": "^3.3.3", + "ts-node": "^10.9.2", + "typescript": "^5.5.4" + }, + "dependencies": { + "@aws-cdk/aws-lambda-go-alpha": "^2.171.0-alpha.0", + "aws-cdk-lib": "^2.155.0", + "constructs": "^10.3.0", + "source-map-support": "^0.5.21" + } +} diff --git a/templates/go-lambda-cdk/tsconfig.json b/templates/go-lambda-cdk/tsconfig.json new file mode 100644 index 00000000..f2915dc2 --- /dev/null +++ b/templates/go-lambda-cdk/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "nodenext", + "lib": ["es2020", "dom"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": ["./node_modules/@types"] + }, + "exclude": ["node_modules", "cdk.out"] +}