Serverless Typescript với AWS Lambda, API Gateway và DynamoDB trên môi trường offline - Phần 01
Bài đăng này đã không được cập nhật trong 3 năm
Function as Service
Function as Service (FaaS) là một trong 2 dịch vụ chính của nhóm dịch vụ serverless (Backend as Service (BaaS) và Function as Service (FaaS) ), ở mô hình này, bạn sẽ phải viết code ở phần backend, nhưng thay vì deploy lên server, bạn deploy dưới dạng một function. Như vậy cách này bạn sẽ chủ động hơn đối với phần backend và không cần quan tâm đến server. Function này sẽ được gọi dưới dạng RestAPI, bạn sẽ trả tiền theo số lần gọi function của mình. Dịch vụ FaaS khá nổi tiếng là AWS Lambda của Amazon. Khi công bố dịch vụ AWS Lambda nhóm phát triền Amazon đã nói "dịch vụ này cho phép các bạn chạy các đoạn code logic của mình mà không cần cung cấp hoặc phải quản lý một hoặc nhiều máy chủ. Ngoài ra, AWS API Gateway cung cấp các kết nối đầu cuối API có thể kết nối với các chức năng của Lambda function, kết nối giữa một API Gateway với một Lambda tạo ra một API endpoint mà client có thể gọi tới. Sự kết hợp này cho phép client lấy được mã truy cập (token) mà không tiết lộ ClientID và ClientSecret". Tập trung vào ứng dụng chứ không phải cơ sở hạ tầng của bạn
Typescript
Hiện tại aws lambda đã hỗ trợ các ngôn ngữ Java, Node.js, Python, C# và mới đây là Go. Trong bài này chúng ta sẽ dùng typescript để viết logic. Typescript có thể được coi là một phiên bản nâng cao của Javascript bởi việc bổ sung tùy chọn kiểu tĩnh và lớp hướng đối tượng mà điều này không có ở Javascript, ngoài ra nó sử dụng tất cả các tính năng của của ECMAScript 2015 (ES6) như classes, modules. Bản chất của TypeScript là biên dịch tạo ra các đoạn mã javascript nên ban có thê chạy bất kì ở đâu miễn ở đó có hỗ trợ biên dịch Javascript. Ngoài ra bạn có thể sử dụng trộn lẫn cú pháp của Javascript vào bên trong TypeScript, điều này giúp các lập trình viên tiếp cận TypeScript dễ dàng hơn. Các lợi ích khi phát triển dự án bằng typescript các bạn có thể tự tìm hiểu thêm.
Serverless js
serverless là một bộ công cụ giúp bạn triển khai và vận hành các ứng dụng theo mô hình serverless. Công cụ hỗ trợ nhiều nền tảng dịch vụ serverless nổi tiếng
Offline development
Việc phát triền ứng dụng theo mô hình serverless còn khá "mới", các bài viết trên trên mạng thường chưa chi tiết và chỉ dừng ở mức "Hello World!", những người muốn tìm hiểu cũng khó tiếp cận. Đặc biệt là việc phát triển dự án trên môi trường offline thường gặp nhiều vấn đề, trong đó có vấn đề mô tả được kết nối giữa API Gateway tới Lambda function. Làm sao để trigger một function bằng một lời gọi http request?
Xây dựng một ứng dụng serverless
Để thực hiện bài viết này các bạn cần phải có hiểu biết căn bản để xây dựng một ứng dụng với nodejs. Ứng dụng của chúng ta sẽ có 2 api endpoint: POST /api/v1/cats - để khởi tạo thông tin một chú mèo GET /api/v1/cats/:id - để lấy thông tin một chú mèo theo id Cơ sở dữ liệu sẽ sử dụng là DynamoDb (??)
Cấu trúc thư mục và package cần thiết
Tạo thư mục ứng dụng
mkdir crazy-cats & cd crazy-cats
Khởi tạo các file cơ bản
yarn init -y
git init
serverless: Bộ công cụ hỗ trợ quản lý ứng dụng serverless của chúng ta, deploy, quản lý ứng dụng khi triển khai nên aws.
yarn global add serverless
typescript: gói biên dịch ngôn ngữ typescript có thể đọc được theo tiêu chuẩn của javascript
webpack: công cụ hỗ trợ tạo build-tool
ts-load: "plugin" của webpack, biên dịch các file typescript thành ngôn ngữ javascript
serverless-webpack: plugin của serverless js
yarn add -D typescript webpack ts-loader serverless-webpack
các serverless plugin để phát triển ở môi trường offline
yarn add -D serverless-offline serverless-dynamodb-local
dynamodb chúng ta có thể cài đặt local bằng gói cài đặt từ aws, hoặc dùng docker.
Các file "Hello world!" serverless
touch serverless.yml && mkdir src && cd src && touch handler.ts
Cấu hình webpack
tạo file webpack.config.js với nội dung
var path = require('path');
module.exports = {
entry: './src/handler.ts',
target: 'node',
stats: {warnings: false},
module: {
exprContextCritical: false,
loaders: [
{
test: /\.ts(x?)$/,
loader: 'ts-loader',
exclude: [/node_modules/, /\.(spec|e2e)\.ts$/]
},
]
},
resolve: {
extensions: ['.ts', '.js', '.tsx', '.jsx']
},
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, 'dist'),
filename: 'handler.js'
},
externals: {
'aws-sdk': true
},
};
Tạo file cấu hình cho typescript tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"noEmitOnError": true,
"noImplicitAny": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node",
"sourceMap": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}
Định nghĩa dịch vụ serverless serverless.yml
service: crazy-cats
provider:
name: aws
runtime: nodejs6.10
stage: dev
plugins:
- serverless-webpack
- serverless-dynamodb-local
- serverless-offline
custom:
dynamodb:
start:
port: 8000
inMemory: true
migrate: true
seed: true
# Uncomment only if you already have a DynamoDB running locally
# noStart: true
functions:
createCat:
handler: handler.createCat
events:
- http:
path: cats
method: POST
findCatById:
handler: handler.findCatById
events:
- http:
path: cats/:id
method: GET
resources:
Resources:
usersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: cats
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
Để cài đặt dynamodb local cho project, chúng ta quay lại thư mục gốc của dự án, gõ lệnh
sls dynamodb install
Nội dung file /src/handler.js
export function createCat(event, context, callback) {
let response = {
statusCode: 200,
headers: {
"x-custom-header" : "my custom header value"
},
body: JSON.stringify({message: "Cat created!"})
};
callback(null, response);
}
export function findCatById(event, context, callback) {
let response = {
statusCode: 200,
headers: {
"x-custom-header" : "my custom header value"
},
body: JSON.stringify({cats: []})
};
callback(null, response);
}
Để chạy ứng dụng chúng ta sử dụng lệnh
sls offline start --location=./dist/service
output log
Serverless: Bundling with Webpack...
Hash: ddd335cb56f5819b1c75
Version: webpack 3.0.0
Time: 8808ms
Asset Size Chunks Chunk Names
handler.js 3.24 kB 0 [emitted] main
[0] ./src/handler.ts 682 bytes {0} [built]
Serverless: Watching for changes...
Dynamodb Local Started, Visit: http://localhost:8000/shell
Serverless: DynamoDB - created table cats
Serverless: Starting Offline: dev/us-east-1.
Serverless: Routes for createCat:
Serverless: POST /api/v1/cats
Serverless: Routes for findCatById:
Serverless: GET /api/v1/cats/{id}
Serverless: Offline listening on http://localhost:3000
Chúng ta có thể dùng Postman để gọi vào một trong các api tương ứng.
Giờ chúng ta sẽ thực hiện phần logic của ứng dụng, trước tiên để làm việc với Dynamodb chúng ta sẽ dùng DocumentClient của gói aws-sdk
yarn add aws-sdk -S
Trước tiên là api POST /api/v1/cats
Client sẽ gửi 2 thông tin của một chú mèo bao gồm name
và mood
giống như thế này:
{
"name": "Pikachu",
"mood": "crazy"
}
Parmameter đầu tiên của 1 Lambda function nếu được gọi bởi API Gateway sẽ có kiểu:
{
body: string | null;
headers: { [name: string]: string };
httpMethod: string;
isBase64Encoded: boolean;
path: string;
pathParameters: { [name: string]: string } | null;
queryStringParameters: { [name: string]: string } | null;
stageVariables: { [name: string]: string } | null;
requestContext: APIGatewayEventRequestContext;
resource: string;
}
function create
export function createCat(event, context, callback) {
let {name, mood} = parseBody(event.body);
const params = {
Item: {
id: uuid.v4(),
mood: mood,
name: name,
},
TableName: 'cats',
};
let response = {
statusCode: 200,
headers: {
'x-custom-header' : 'my custom header value'
},
body: ''
};
dynamoDb.put(params, (err) => {
if (err) {
response.statusCode = 500;
response.body = JSON.stringify({message: err.toString()})
} else {
response.body = JSON.stringify(params.Item);
}
callback(null, response);
});
}
function find by id
export function findCatById(event, context, callback) {
let id = event.pathParameters.id;
const params = {
Key: {
id,
},
TableName: 'cats',
};
let response = {
statusCode: 200,
headers: {
'x-custom-header' : 'my custom header value'
},
body: ''
};
dynamoDb.get(params, (err, data) => {
if (err) {
response.statusCode = 500;
response.body = JSON.stringify({message: err.toString()})
} else {
response.body = JSON.stringify(data.Item);
}
callback(null, response);
});
}
Nội dung của cả file src/handler.ts
import * as AWS from 'aws-sdk';
import * as uuid from 'uuid';
let dynamoDb = null;
if (process.env.IS_OFFLINE === 'true') {
dynamoDb = new AWS.DynamoDB.DocumentClient({
endpoint: process.env.DYNAMODB_ENDPOINT || 'http://localhost:8000',
region: 'localhost',
});
} else {
dynamoDb = new AWS.DynamoDB.DocumentClient();
}
function parseBody(body: string): any {
try {
return JSON.parse(body);
} catch {
return {};
}
}
export function createCat(event, context, callback) {
let {name, mood} = parseBody(event.body);
const params = {
Item: {
id: uuid.v4(),
mood: mood,
name: name,
},
TableName: 'cats',
};
let response = {
statusCode: 200,
headers: {
'x-custom-header' : 'my custom header value'
},
body: ''
};
dynamoDb.put(params, (err) => {
if (err) {
response.statusCode = 500;
response.body = JSON.stringify({message: err.toString()})
} else {
response.body = JSON.stringify(params.Item);
}
callback(null, response);
});
}
export function findCatById(event, context, callback) {
let id = event.pathParameters.id;
const params = {
Key: {
id,
},
TableName: 'cats',
};
let response = {
statusCode: 200,
headers: {
'x-custom-header' : 'my custom header value'
},
body: ''
};
dynamoDb.get(params, (err, data) => {
if (err) {
response.statusCode = 500;
response.body = JSON.stringify({message: err.toString()})
} else {
response.body = JSON.stringify(data.Item);
}
callback(null, response);
});
}
Giờ bạn chạy lại lệnh sls offline start --location=./dist/service
để xem những thay đổi!
Bài viết này mình sẽ dừng ở đây, ở phần tiếp theo mình sẽ đi sâu vào việc chuyển đổi một ứng dụng express sang dạng serverless.
All rights reserved