DEV Community

Masayoshi Mizutani
Masayoshi Mizutani

Posted on

How to build distributable serverless application module by AWS CDK

Background

We used to create serverless applications using AWS SAM (Serverless Application Model), but the serverless configuration information and the parameters required for it tended to be tightly coupled, making it difficult to create an application that can be used anywhere. Although there were functions such as Parameters that allowed you to embed values in definition files afterwards, there was a sense of being forced to use YAML and JSON, and there was a lot of wear and tear.

On the other hand, AWS CDK (Cloud Development Kit) allows you to write Constrcut, which is the equivalent of a definition file, in code and is quite flexible. I've created several serverless applications as distributable module, and I'll summarize my findings in an article.

Please see https://docs.aws.amazon.com/cdk/latest/guide/home.html for more detail of AWS CDK.

How to build

Create a new CDK project

Let's create a module named onigiri, which is a normal CDK project. Let's start by creating a normal CDK project.

% mkdir onigiri
% cd onigiri
% cdk init --language typescript

Write CDK Construct

Construct is the template for the Stack that will eventually be deployed in CloudFormation. It can be written in the file lib/onigiri-stack.ts, which will be generated when you init with the name onigiri. In the following example, a Lambda Function and a DynamoDB are deployed in a construct where a Lambda Function is queried and its result is written to DynamoDB.

import * as cdk from "@aws-cdk/core";
import * as dynamodb from "@aws-cdk/aws-dynamodb";
import * as lambda from "@aws-cdk/aws-lambda";
import * as events from "@aws-cdk/aws-events";
import * as eventsTargets from "@aws-cdk/aws-events-targets";
import { NodejsFunction } from "@aws-cdk/aws-lambda-nodejs";

import * as path from "path";

export interface properties extends cdk.StackProps {
  someParameter: string;
}

export class OnigiriStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props: properties) {
    super(scope, id, props);

    const cacheTable = new dynamodb.Table(this, "cacheTable", {
      partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
    });

    const lambdaQuery = new NodejsFunction(this, "query", {
      entry: path.join(__dirname, "lambda/query.js"),
      handler: "main",
      environment: {
        TABLE_NAME: cacheTable.tableName,
        SOME_PARAM: props.someParameter,
      },
    });

    cacheTable.grantReadWriteData(lambdaQuery);

    new events.Rule(this, "periodicQuery", {
      schedule: events.Schedule.rate(cdk.Duration.minutes(10)),
      targets: [new eventsTargets.LambdaFunction(lambdaQuery)],
    });
  }
}

Here are some tips for writing Construct

  • As we will be using TypeScript to create the Lambda function, we will use the NodejsFunction in the @aws-cdk/aws-lambda-nodejs package to deploy the modules under node_modules at the same time. We use the NodejsFunction in the @aws-cdk/aws-lambda-nodejs package to deploy the following modules at the same time. It's also possible to store a node_modules in a separate layer. /node_modules` in another layer, but I'll spare you the trouble.
    • Although the path parameter of NodejsFunction can be relative to this file, the standard of relative path is changed when distributed as a module, so __dirname is joined to specify absolute path.
    • The path is specified as a .js file. This is because it doesn't work well if you try to transcompile the module via NodejsFunction after the module is distributed.
  • When you add a CDK module such as @aws-cdk/aws-lambda, make sure to install it with the same version of aws-cdk/core added at init time. If the version is slightly out of alignment, the tsc error occurs.
  • If you use it as a single CDK project, you should add CDK modules like @aws-cdk/aws-lambda as devDependencies as npm i --save-dev @aws-cdk/aws-lambda.

Also, as it's good to have variable parameters in the distribution construct, I've extended the cdk.StackProps argument from the OnigiriStack to create an interface called properties. This allows you to pass in some environment variables and external resources as arguments.

Create a Lambda Function at the same time. In this case, I will create a Lambda Function file in . /onigiri/lib/lambda/query.ts, where the Lambda Function file is located.

Edit package.json

Make the following changes to package.json.

  • Added files: ["lib"]: to place transcompiled code under lib.
  • Add "main": ". /Add lib/onigiri-stack.js"`: Construct code can now be read from the distribution project.
  • Added "type": ". /lib/onigiri-stack.d.ts" added: Ibid.
  • Added "prepare": "tsc" in the scripts: to make it possible to transcompile the package when it is published or installed from github.

You can also set the output of transcompilation to dist and you can adjust it as you like. See https://github.com/m-mizutani/dnstrack/blob/master/package.json for a sample.

Deployment

The purpose of this article is to distribute your CDK construct as a module, but you may want to deploy it as a test. One way to do this is to make bin/onigiri.ts deployable with environment variables. As an example, let's set up bin/onigiri.ts as follows.

#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "@aws-cdk/core";
import { OnigiriStack } from "../lib/onigiri-stack";

const stackName = process.env.ONIGIRI_STACK_NAME || "onigiri";
const app = new cdk.App();
new OnigiriStack(app, stackName, {
  someParameter: process.env.ONIGIRI_PARAM!,
});

By doing so, you can specify an environment variable to deploy directly from the developing repository, but avoid embedding parameters in the development environment.

% env ONIGIRI_STACK_NAME=onigiri-1 ONIGIRI_PARAM=nanika cdk deploy

Publish and distribute the pacakge

You can reuse your CDK constructs in external projects by publishing them with npm publish and npm i onigiri. Also you can install the pacakge from github by npm i your-github-account/onigiri .

After that, create another construct to deploy the package.

#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "@aws-cdk/core";
import { OnigiriStack } from "onigiri";

const app = new cdk.App();
new OnigiriStack(app, "wagaya-no-onigiri", {
  someParameter: "nanika-sugoi-guzai",
});

Sample

The following is a construct made according to the above method, for your reference.

Discussion (0)