How to build distributable serverless application module by AWS CDK
Tags: aws, cdk, typescript, npm
Published: 2020-07-29 08:43:54

---
title: How to build distributable serverless application module by AWS CDK
published: true
description: 
tags: aws,cdk,typescript,npm
//cover_image: https://direct_url_to_image.jpg
---

# 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.

```sh
% 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.

```ts
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.

```ts
#!/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.

```bash
% 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.

```ts
#!/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.

- DNS track: https://github.com/m-mizutani/dnstrack