を使用して基本的なServerlessアプリケーションを構築する

9718 ワード

このブログ記事では、AWS - CDKを使用して、APIゲートウェイ、ラムダ関数、ダイナモテーブルを展開する基本的なサーバレスアプリケーションを書く方法を紹介します.CDKのアセットサポートを使用して展開中にビジネスロジックを束ねます.

何を実装しているか


当社のアプリケーションは、リストに新しい項目を追加し、それを表示をサポートする簡単なToDoリストになります.更なる行動は、読者に実験に任せられます.私たちはデータをDynamoDBテーブルに格納し、タイプスクリプトで書かれたコードでAWSラムダを使用してビジネスロジックを実装します.APIエンドポイントとして、HTTP APIが使用されます.

CDKアプリケーションの初期化


プロジェクトを開始するには、CDKプロジェクトを初期化する必要があります.前のブログ記事で、必要な手順を説明します.
これらの手順を実行した後、以下のコマンドを使用して更なるライブラリをインストールすることでセットアップを強化します.
npm install --save-exact @aws-cdk/aws-lambda @aws-cdk/aws-lambda-nodejs @aws-cdk/aws-dynamodb @aws-cdk/aws-apigatewayv2

データストアの作成


我々のバッキングデータストアを作成するには、新しいDynamoDBテーブルを作成し、使用するPK テーブルのハッシュキーとして.cdkでは,表構造を用いてこれを達成する.セットアップを容易にするために、我々はDynamoDBのオンデマンド課金を使用するので、スループットを計算する必要はありません.これはあなたのAWSアカウントの無料層で覆われていないことに注意してください!
const table = new dynamodb.Table(this, 'Table', {
  partitionKey: {
    name: 'PK',
    type: dynamodb.AttributeType.STRING,
  },
  billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
});
データベースを調べることができるようにするために、テーブル名を知るために出力を作成します.
new cdk.CfnOutput(this, 'TableName', {value: table.tableName});

ビジネスロジックの作成


ビジネスロジックの場合、2つのラムダ関数を作成し、すべてのタスクのリストを取得し、テーブル内の新しいタスクを作成します.これらのラムダのコードを保持するには、フォルダを作成しますlambda/ そして、新しいNodeJSプロジェクトとしてそれを初期化してください.次の設定を使用して、タイプスクリプトでラムダを作成します.
{
  "name": "lambda",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "tslint -p tsconfig.json && jest"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "aws-sdk": "^2.714.0",
    "uuid": "^8.3.0"
  },
  "devDependencies": {
    "@types/aws-lambda": "^8.10.59",
    "@types/jest": "^26.0.4",
    "@types/node": "14.0.23",
    "@types/uuid": "^8.3.0",
    "jest": "^26.1.0",
    "sinon": "^9.0.2",
    "ts-jest": "^26.1.2",
    "ts-mock-imports": "^1.3.0",
    "ts-node": "^8.10.2",
    "tslint": "^6.1.3",
    "typescript": "~3.9.6"
  }
}

{
  "compilerOptions": {
    "target": "ES2018",
    "module": "commonjs",
    "lib": ["es2018"],
    "declaration": true,
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitThis": true,
    "esModuleInterop": true,
    "alwaysStrict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": false,
    "inlineSourceMap": true,
    "inlineSources": true,
    "experimentalDecorators": true,
    "strictPropertyInitialization": false,
    "typeRoots": ["./node_modules/@types"]
  },
  "exclude": ["cdk.out"]
}
最も重要な部分はAWS SDKとUUIDライブラリをインストールすることです.
私のラムダコードのヘッダーはlib/tasks.ts , は
import { DynamoDB } from 'aws-sdk';
import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';
import { env } from 'process';
import { v4 } from 'uuid';

const dynamoClient = new DynamoDB.DocumentClient();
データベースに新しいタスクを追加するには、“name”と“state”という2つのフィールドを持つAPIにJSONオブジェクトを投稿します.タスクの主なIDとして使用する新しいUUIDを生成します.
// Export new function to be called by Lambda
export async function post(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2> {
    // Log the event to debug the application during development
  console.log(event);

    // If we do not receive a body, we cannot continue...
  if (!event.body) {
        // ...so we return a Bad Request response
    return {
      statusCode: 400,
    };
  }

    // As we made sure we have a body, let's parse it
  const task = JSON.parse(event.body);
    // Let's create a new UUID for the task
  const id = v4();

    // define a new task entry and await its creation
  const put = await dynamoClient.put({
    TableName: env.TABLE_NAME!,
    Item: {
            // Hash key is set to the new UUID
      PK: id,
            // we just use the fields from the body
      Name: task.name,
      State: task.state,
    },
  }).promise();

    // Tell the caller that everything went great
  return {
    statusCode: 200,
    body: JSON.stringify({...task, id}),
  };
}
テーブルからデータを取得するには、同じファイルに存在する別の関数を実装します.より良い読みやすさのために、私は2つの機能(それはAPI呼び出しを取り扱うものとデータベースから読んでいるヘルパー機能)に分けました.
// Export new function to be called by Lambda
export async function get(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2> {
  // Log the event to debug the application during development
  console.log(event);

  // Get a list of all tasks from the DB, extract the method to do paging
  const tasks = (await getTasksFromDatabase()).map((task) => ({
    // let's reformat the data to our API model
    id: task.PK,
    name: task.Name,
    state: task.State,
  }));

  // Return the list as JSON objects
  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
    },
    // Body needs to be string so render the JSON to string
    body: JSON.stringify(tasks),
  };
}

// Helper method to fetch all tasks
async function getTasksFromDatabase(): Promise<DynamoDB.DocumentClient.ItemList> {
  // This variable will hold our paging key
  let startKey;
  // start with an empty list of tasks
  const result: DynamoDB.DocumentClient.ItemList = [];

  // start a fetch loop
  do {
    // Scan the table for all tasks
    const res: DynamoDB.DocumentClient.ScanOutput = await dynamoClient.scan({
      TableName: env.TABLE_NAME!,
      // Start with the given paging key
      ExclusiveStartKey: startKey,
    }).promise();
    // If we got tasks, store them into our list
    if (res.Items) {
      result.push(...res.Items);
    }
    // Keep the new paging token if there is one and repeat when necessary
    startKey = res.LastEvaluatedKey;
  } while (startKey);
  // return the accumulated list of tasks
  return result;
}
このコードをtasks.ts ファイルは、我々のCDKアプリケーション内の関数をインスタンス化し、自動バンドルを構成することができます.
ラムダ関数を設定するには、nodejsfunction構造体が優れています.エクスポートされた関数のエントリポイントと名前として使用するファイルを指定できます.CDKは、このコードをSPACELを使ってsynth中にパッケージ化してパッケージ化します.この手順では、バンドルがコンテナの内部で発生するときに、マシン上で実行中のDockerセットアップが必要です.
const postFunction = new lambdaNode.NodejsFunction(this, 'PostFunction', {
  runtime: lambda.Runtime.NODEJS_12_X,
  // name of the exported function
  handler: 'post',
  // file to use as entry point for our Lambda function
  entry: __dirname + '/../lambda/lib/tasks.ts',
  environment: {
    TABLE_NAME: table.tableName,
  },
});
// Grant full access to the data
table.grantReadWriteData(postFunction);

const getFunction = new lambdaNode.NodejsFunction(this, 'GetFunction', {
  runtime: lambda.Runtime.NODEJS_12_X,
  handler: 'get',
  entry: __dirname + '/../lambda/lib/tasks.ts',
  environment: {
    TABLE_NAME: table.tableName,
  },
});
// Grant only read access for this function
table.grantReadData(getFunction);

APIの設定


APIへのエントリポイントとして、サービスAPIゲートウェイの新しいHTTP APIを使用します.APIを作成し、URLを出力するには、次のコードを使用します.
const api = new apiGW.HttpApi(this, 'Api');
new cdk.CfnOutput(this, 'ApiUrl', {value: api.url!});
次に、このパスを実行するすべてのパスとメソッドに対してルートを追加する必要があります.
api.addRoutes({
  path: '/tasks',
  methods: [apiGW.HttpMethod.POST],
  integration: new apiGW.LambdaProxyIntegration({handler: postFunction})
});
api.addRoutes({
  path: '/tasks',
  methods: [apiGW.HttpMethod.GET],
  integration: new apiGW.LambdaProxyIntegration({handler: getFunction})
});

AWSアカウントへの配備


走ることでcdk synth , CloudFormationテンプレートを合成し、CDKパッケージをラムダコードにします.The cdk.out フォルダーには、コードバンドルとテンプレートが含まれています.

使用cdk deploy コマンドは、我々のアプリケーションは、私たちのAWSのアカウントの内部に移動し、我々のDynamoDBテーブルとAPIのURLのテーブル名を出力します.

APIのテスト


その後、PostmanまたはCURLを使用してデータベースにタスクを追加したり、エントリの一覧を取得できます.
API_URL=https://XXXXX.execute-api.eu-central-1.amazonaws.com/

# Add new task to the table
curl -v -X POST -d '{"name":"Testtask","state":"OPEN"}' -H "Content-Type: application/json" ${API_URL}tasks

# Retrieve the list
curl -v ${API_URL}tasks

結論


この記事では、ダイナモテーブル、2つのラムダ関数、およびHTTP APIを作成するAWS CDKアプリケーションを設定することを学びました.これは、コードのCDKネイティブのバンドルを使用してJavaScriptにTypesScriptを転送し、コードをパッケージにラムダに展開する準備ができてパッケージを使用します.