Skip to content

Commit

Permalink
Add basic TypeScript Lambda CDK example
Browse files Browse the repository at this point in the history
Introduces hello-world-lambda-cdk, a CDK-based example that showcases deploying a Lambda-backed service using the Restate CDK constructs library.
  • Loading branch information
pcholakov committed Jan 15, 2024
1 parent f6dcd81 commit f18e791
Show file tree
Hide file tree
Showing 22 changed files with 3,273 additions and 175 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ jobs:
typescript-food-ordering.zip
typescript-payment-api.zip
typescript-ticket-reservation.zip
typescript-lambda-greeter.zip
typescript-hello-world-lambda.zip
typescript-hello-world-lambda-cdk.zip
java-hello-world-http.zip
java-hello-world-lambda.zip
kotlin-hello-world-http.zip
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ Browse this repository to see how easy distributed applications development beco
wget https://github.com/restatedev/examples/releases/latest/download/typescript-hello-world-lambda.zip && unzip typescript-hello-world-lambda.zip -d typescript-hello-world-lambda && rm typescript-hello-world-lambda.zip
```

![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)

[Hello world on AWS Lambda + CDK](typescript/hello-world-lambda-cdk)
```shell
# Download the example
wget https://github.com/restatedev/examples/releases/latest/download/typescript-hello-world-lambda-cdk.zip && unzip typescript-hello-world-lambda-cdk.zip -d typescript-hello-world-lambda-cdk && rm typescript-hello-world-lambda-cdk.zip
```

![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=for-the-badge&logo=openjdk&logoColor=white)

[Hello World HTTP](java/hello-world-http)
Expand Down
1 change: 1 addition & 0 deletions scripts/prepare_release_zip.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ create_release_zip kotlin hello-world-lambda
create_release_zip kotlin hello-world-lambda-cdk

create_release_zip typescript hello-world-lambda
create_release_zip typescript hello-world-lambda-cdk
create_release_zip typescript ecommerce-store
create_release_zip typescript food-ordering
create_release_zip typescript payment-api
Expand Down
5 changes: 3 additions & 2 deletions typescript/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Typescript examples
# TypeScript examples

This directory contains Restate examples using the Typescript SDK.

## Starter examples

- [Lambda greeter](hello-world-lambda): A simple example of how you can run a Restate service on AWS Lambda.
- [Hello World - AWS Lambda](hello-world-lambda): A simple example of how you can run a Restate service on AWS Lambda.
- [Hello World - AWS Lambda & CDK](hello-world-lambda-cdk): A simple example of automating deployment to AWS Lambda using CDK.
- [Payment api](payment-api/): Example API for payments, inspired by the Stripe API.
- [Food ordering](typescript/food-ordering): See how to integrate Restate with external services using Awakeables and side effects.

Expand Down
3 changes: 3 additions & 0 deletions typescript/hello-world-lambda-cdk/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
cdk.out
15 changes: 15 additions & 0 deletions typescript/hello-world-lambda-cdk/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
5 changes: 5 additions & 0 deletions typescript/hello-world-lambda-cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
.cdk.staging
cdk.out
*.js
*.d.ts
7 changes: 7 additions & 0 deletions typescript/hello-world-lambda-cdk/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"arrowParens": "always",
"printWidth": 120
}
76 changes: 76 additions & 0 deletions typescript/hello-world-lambda-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Hello world - TypeScript Lambda (CDK) example

Sample project deploying a TypeScript-based Restate service to AWS Lambda using the AWS Cloud Development Kit (CDK).
This is functionally equivalent to the [`hello-world-lambda`](../hello-world-lambda) example but uses CDK to automate
the deployment of the Lambda function to AWS, and to register the service with a Restate 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-ts-cdk.ts`](bin/lambda-ts-cdk.ts)
* [CDK stack consisting of a Lambda function and providing Restate service registration](lib/lambda-ts-cdk-stack.ts)
* [TypeScript Lambda handler](lib/lambda/app.ts) - based on [`hello-world-lambda`](../hello-world-lambda)

## Download the example

```shell
wget https://github.com/restatedev/examples/releases/latest/download/typescript-hello-world-lambda-cdk.zip && unzip typescript-hello-world-lambda-cdk.zip -d typescript-hello-world-lambda-cdk && rm typescript-hello-world-lambda-cdk.zip
```

## Deploy

**Pre-requisites:**

* npm
* Restate Cloud access (cluster id + API token)
* AWS account, bootstrapped for CDK use

Create a secret in Secrets Manager to hold the authentication token. The secret name is up to you -- we suggest
using `/restate/` and an appropriate prefix to avoid confusion:

```shell
export AUTH_TOKEN_ARN=$(aws secretsmanager create-secret \
--name /restate/${CLUSTER_ID}/auth-token --secret-string ${RESTATE_AUTH_TOKEN} \
--query ARN --output text
)
```

Once you have the ARN for the auth token secret, you can deploy the stack using:

```shell
npx cdk deploy \
--context clusterId=${CLUSTER_ID} \
--context authTokenSecretArn=${AUTH_TOKEN_ARN}
```

Alternatively, you can save this information in the `cdk.context.json` file:

```json
{
"clusterId": "cluster-id",
"authTokenSecretArn": "arn:aws:secretsmanager:us-east-1:123456789012:secret:/restate/cluster-id/auth-token-abc123"
}
```

In that case, you can simply run:

```shell
npm run deploy
```

In this example, the Lambda handler function name is dynamically generated by CDK. You can see what it is from the `HandlerFunction`
output of the CDK stack after a successful deployment.

### Test

You can send a test request to the Restate cluster ingress endpoint to call the newly deployed service:

```shell
curl -H "Authorization: Bearer ${RESTATE_API_TOKEN}" \
-H 'content-type: application/json' -d '{"key":"Restate Customer"}' \
https://${CLUSTER_ID}.dev.restate.cloud:8080/Greeter/greet
```

### Useful commands

* `npm run build` compile the Lambda handler and synthesize CDK deployment artifacts
* `npm run deploy` perform a CDK deployment
11 changes: 11 additions & 0 deletions typescript/hello-world-lambda-cdk/bin/lambda-ts-cdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { LambdaTsCdkStack } from "../lib/lambda-ts-cdk-stack";

const app = new cdk.App();
new LambdaTsCdkStack(app, "LambdaTsCdkStack", {
selfHosted: Boolean(app.node.tryGetContext("selfHosted")),
clusterId: app.node.getContext("clusterId"),
authTokenSecretArn: app.node.getContext("authTokenSecretArn"),
});
4 changes: 4 additions & 0 deletions typescript/hello-world-lambda-cdk/cdk.context.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"clusterId": "PLACEHOLDER",
"authTokenSecretArn": "arn:aws:secretsmanager:region:12345789012:secret:/restate/PLACEHOLDER-abcdef"
}
59 changes: 59 additions & 0 deletions typescript/hello-world-lambda-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"app": "npx ts-node --prefer-ts-exts bin/lambda-ts-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
}
}
57 changes: 57 additions & 0 deletions typescript/hello-world-lambda-cdk/lib/lambda-ts-cdk-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as logs from "aws-cdk-lib/aws-logs";
import * as restate from "@restatedev/restate-cdk";
import { Construct } from "constructs";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";

export class LambdaTsCdkStack extends cdk.Stack {
constructor(
scope: Construct,
id: string,
props: (
| { selfHosted: true }
| {
selfHosted: false;
clusterId: string;
authTokenSecretArn: string;
}
) &
cdk.StackProps,
) {
super(scope, id, props);

const greeter: lambda.Function = new NodejsFunction(this, "GreeterService", {
runtime: lambda.Runtime.NODEJS_LATEST,
entry: "lib/lambda/handler.ts",
architecture: lambda.Architecture.ARM_64,
bundling: {
minify: true,
sourceMap: true,
},
environment: {
NODE_OPTIONS: "--enable-source-maps",
},
});

console.log(props);
const environment = props.selfHosted
? new restate.SingleNodeRestateDeployment(this, "Restate", {
logGroup: new logs.LogGroup(this, "RestateLogs", {
retention: logs.RetentionDays.THREE_MONTHS,
removalPolicy: cdk.RemovalPolicy.DESTROY,
}),
})
: new restate.RestateCloudEnvironment(this, "RestateCloud", {
clusterId: props.clusterId,
authTokenSecretArn: props.authTokenSecretArn,
});

new restate.LambdaServiceRegistry(this, "ServiceRegistry", {
environment,
handlers: {
Greeter: greeter,
},
});
}
}
6 changes: 6 additions & 0 deletions typescript/hello-world-lambda-cdk/lib/lambda/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as restate from "@restatedev/restate-sdk";

export const handler = restate
.createLambdaApiGatewayHandler()
.bindRouter("Greeter", restate.router({ greet: async () => "Hello!" }))
.handle();
Loading

0 comments on commit f18e791

Please sign in to comment.