無伺服器架構讓開發者擺脫伺服器管理的負擔,專注於程式碼的開發與佈署。本文將以 AWS 平台為例,示範如何結合 API Gateway、Lambda 和 DynamoDB,建構一個具備健康檢查、資料讀取和新增功能的無伺服器 API。透過 AWS CDK,我們可以簡化基礎設施的組態和管理,並使用 TypeScript 撰寫程式碼,提升開發效率。文章將逐步引導讀者建立 Lambda 函式、設定 API Gateway 路由、整合 DynamoDB 資料函式庫,並說明 CORS 設定的必要性,最後完成一個可實際運作的無伺服器應用程式。
無伺服器架構(Serverless)解析與 AWS 實作
前言
無伺服器架構(Serverless)是一種雲端運算模式,讓開發者能夠在無需管理伺服器的情況下建置和執行應用程式。本文將探討無伺服器架構的核心概念,並透過 AWS Lambda 和 API Gateway 實作一個無伺服器的 API。
什麼是無伺服器架構?
無伺服器架構是一種事件驅動的運算模式,開發者只需撰寫和佈署程式碼,無需擔心底層基礎設施的管理。這種模式提供了更高的靈活性、可擴充套件性和成本效益。
使用 AWS Lambda 和 API Gateway 建立 API
步驟一:建立和設定 REST API
首先,我們需要使用 AWS API Gateway 建立一個 RESTful API。API Gateway 是一個受管服務,簡化了 API 的建立、發布、維護、監控和安全性的過程。
const backEndSubDomain = process.env.NODE_ENV === 'Production' ? config.backend_subdomain : config.backend_dev_subdomain;
const restApi = new RestApi(this, 'chapter-7-rest-api', {
restApiName: `chapter-7-rest-api-${process.env.NODE_ENV || ''}`,
description: 'serverless api using lambda functions',
domainName: {
certificate: acm.certificate,
domainName: `${backEndSubDomain}.${config.domain_name}`,
endpointType: EndpointType.REGIONAL,
securityPolicy: SecurityPolicy.TLS_1_2,
},
deployOptions: {
stageName: process.env.NODE_ENV === 'Production' ? 'prod' : 'dev',
},
});
步驟二:建立健康檢查路徑
接下來,我們需要建立一個健康檢查路徑,以確保 API 在佈署後能夠正常運作。
const healthcheck = restApi.root.addResource('healthcheck');
const rootResource = restApi.root;
healthcheck.addMethod('GET', healthCheckLambdaIntegration);
healthcheck.addCorsPreflight({
allowOrigins: ['*'],
allowHeaders: ['*'],
allowMethods: ['*'],
statusCode: 204,
});
步驟三:整合 Lambda 函式
API Gateway 允許我們使用 Lambda 函式作為端點的處理程式。我們需要建立一個 Lambda 函式,並將其與 API 端點整合。
export class HealthCheckLambda extends Construct {
public readonly func: NodejsFunction;
constructor(scope: Construct, id: string, props: any) {
super(scope, id);
this.func = new NodejsFunction(scope, 'health-check-lambda', {
runtime: Runtime.NODEJS_16_X,
entry: path.resolve(__dirname, 'code', 'index.ts'),
handler: 'handler',
timeout: Duration.seconds(60),
environment: {},
logRetention: logs.RetentionDays.TWO_WEEKS,
});
}
}
#### 內容解密:
此段程式碼定義了一個名為 HealthCheckLambda 的類別,用於建立一個 Lambda 函式。該函式使用 Node.js 16.x 版本執行,執行位於 code/index.ts 的程式碼,並設定了處理函式為 handler。逾時時間被設定為 60 秒,且未傳遞任何環境變數。
使用 AWS CDK 進行無伺服器應用程式開發
設定 Lambda 函式的 Log Retention
在建立 Lambda 函式時,設定日誌保留(logRetention)是非常重要的。日誌保留決定了 CloudWatch 將為 Lambda 函式保留日誌的時間長度。每次 Lambda 執行時,都會在 CloudWatch 的日誌群組中建立一個日誌。建議設定保留政策,以避免日誌無限制地累積,從而產生不必要且不想要的成本。
Lambda 函式的程式碼解析
讓我們來檢查一下我們的 Lambda 函式所執行的程式碼:
export const handler = async () => {
try {
return httpResponse(200, JSON.stringify('OK'));
} catch (error: any) {
console.error(error);
return httpResponse(400, JSON.stringify({ message: error.message }));
}
};
這個程式碼非常簡單直接。它的主要目標是檢查 API 是否正常運作。
內容解密:
handler函式:這是 Lambda 函式的入口點。當 Lambda 被觸發時,它會執行這個函式。try-catch區塊:用於捕捉任何在執行過程中發生的錯誤。如果發生錯誤,會傳回一個包含錯誤訊息的 HTTP 回應。httpResponse函式:用於簡化 HTTP 回應的處理。它接收狀態碼和回應主體,並傳回一個包含適當標頭的 HTTP 回應。
httpResponse 函式的實作
export const httpResponse = (
statusCode: number,
body: string,
): IHttpResponse => ({
body,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
'Access-Control-Allow-Methods': 'GET,OPTIONS,POST',
},
statusCode,
});
內容解密:
httpResponse引數:接收狀態碼和回應主體。- 傳回的 HTTP 回應:包含回應主體、適當的標頭(如 CORS 設定)和狀態碼。
- CORS 設定:允許來自任何來源的請求,並支援 GET、OPTIONS 和 POST 方法。
將 Lambda 函式與 API Gateway 整合
在 infrastructure/lib/constructs/API-GW/index.ts 檔案中,我們建立了一個 Lambda 函式例項,並使用 LambdaIntegration 方法將其與 API Gateway 整合。
const healthCheckLambda = new HealthCheckLambda(
this,
'health-check-lambda-api-endpoint',
{},
);
const healthCheckLambdaIntegration = new LambdaIntegration(
healthCheckLambda.func,
);
healthcheck.addMethod('GET', healthCheckLambdaIntegration);
內容解密:
- 建立 Lambda 函式例項:使用
HealthCheckLambda建構函式建立一個新的 Lambda 函式。 - 建立 Lambda 與 API Gateway 的整合:使用
LambdaIntegration方法將 Lambda 函式與 API Gateway 的特定路徑和方法(這裡是 GET 方法)繫結。
建立 GET 和 POST 路由以進行 DynamoDB 操作
為了使我們的 API 更為實用,我們需要建立兩個新的端點:一個用於從 DynamoDB 表中取得所有資料,另一個用於向表中插入資料。
取得 DynamoDB 資料的 Lambda 函式
首先,讓我們建立一個用於取得 DynamoDB 表中所有資料的 Lambda 函式。
export const handler = async () => {
try {
const tableName = process.env.TABLE_NAME as string;
const dynamoDB = new DynamoDB.DocumentClient({
region: process.env.REGION as string,
});
const { Items }: DynamoDB.ScanOutput = await dynamoDB
.scan({ TableName: tableName })
.promise();
return httpResponse(200, JSON.stringify({ todos: Items }));
} catch (error: any) {
console.error(error);
return httpResponse(400, JSON.stringify({ message: error.message }));
}
};
內容解密:
tableName和dynamoDB:從環境變數中取得表名和區域,並初始化 DynamoDB DocumentClient。scan操作:掃描指定的 DynamoDB 表並傳回其專案。- 傳回結果:將掃描結果透過
httpResponse傳回給客戶端。
設定和佈署無伺服器後端
到目前為止,我們已經成功地建立了一個基本的健康檢查端點,並瞭解瞭如何建立由 Lambda 提供支援的 API 端點。然而,我們的健康檢查端點的功能相當有限。在本文中,我們將探討如何從 Lambda 函式處理程式查詢和寫入資料到 DynamoDB,以構建更複雜和有用的 API 端點。
圖表翻譯:
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title 圖表翻譯:
rectangle "GET 請求" as node1
rectangle "觸發 Lambda" as node2
rectangle "掃描 DynamoDB" as node3
rectangle "傳回資料" as node4
rectangle "傳回結果" as node5
node1 --> node2
node2 --> node3
node3 --> node4
node4 --> node5
@enduml
此圖示展示了客戶端如何透過 API Gateway 發起 GET 請求,觸發 Lambda 函式掃描 DynamoDB,並最終將結果傳回給客戶端的流程。
使用 AWS CDK 進行無伺服器應用程式開發
建立用於插入資料的 Lambda 函式
在完成前一步驟後,我們現在需要為 POST 請求建立 Lambda 函式,以便將專案插入到我們的 Table 中。前往 infrastructure/lib/constructs/Lambda/post/code/index.ts;你將找到以下程式碼:
export const handler = async (event: PostEvent) => {
try {
const { todo_name, todo_description, todo_completed } = JSON.parse(event.body).todo;
const tableName = process.env.TABLE_NAME as string;
const dynamoDB = new DynamoDB.DocumentClient({
region: process.env.REGION as string,
});
const todo: Todo = {
id: uuidv4(),
todo_completed,
todo_description,
todo_name,
};
await dynamoDB.put({ TableName: tableName, Item: todo }).promise();
return httpResponse(200, JSON.stringify({ todo }));
} catch (error: any) {
console.error(error);
return httpResponse(400, JSON.stringify({ message: error.message }));
}
};
內容解密:
- 首先,我們從請求主體中解析出必要的資料,如
todo_name、todo_description和todo_completed。 - 然後,我們初始化
DynamoDB.DocumentClient以執行對表的所需操作,在本例中為 PUT 操作。 - 建立一個
todo物件,其中包含一個使用uuidv4()函式生成的唯一id,以及其他相關資料。 - 使用
dynamoDB.put()方法將todo物件插入到指定的表中。 - 如果操作成功,則傳回一個包含新建
todo專案的 HTTP 200 回應;如果發生錯誤,則傳回一個包含錯誤訊息的 HTTP 400 回應。
建立 Lambda 函式
接下來,我們需要在 infrastructure/lib/constructs/Lambda/post/index.ts 中建立 Lambda 函式:
const { dynamoTable } = props;
this.func = new NodejsFunction(scope, 'dynamo-post', {
runtime: Runtime.NODEJS_16_X,
entry: path.resolve(__dirname, 'code', 'index.ts'),
handler: 'handler',
timeout: Duration.seconds(60),
environment: {
NODE_ENV: process.env.NODE_ENV as string,
TABLE_NAME: dynamoTable.tableName,
REGION: process.env.CDK_DEFAULT_REGION as string,
},
logRetention: logs.RetentionDays.TWO_WEEKS,
});
dynamoTable.grantWriteData(this.func);
內容解密:
- 初始化一個新的
NodejsFunction,指定執行環境、進入點、處理函式和超時時間。 - 設定環境變數,包括
NODE_ENV、TABLE_NAME和REGION。 - 設定日誌保留期限為兩週。
- 賦予 Lambda 函式寫入
dynamoTable的許可權。
簡化 Lambda 整合
除了前述方法外,還有一種更簡單的方式來整合 AWS Lambda,即使用 LambdaRestApi 建構函式。此函式設定了一個具有預設 Lambda 函式的 REST API,並使用 API Gateway 的貪婪代理選項 ({proxy+}) 和 ANY 方法。這意味著每個請求都將自動路由到此 Lambda 函式。
new LambdaRestApi(this, 'MyRestApi', {
handler: lambda.func,
restApiName: 'rest-api-name',
defaultCorsPreflightOptions: {
allowOrigins: ['*'],
allowHeaders: ['*'],
allowMethods: ['*'],
statusCode: 204,
},
});
內容解密:
- 初始化一個新的
LambdaRestApi,指定處理函式和 REST API 名稱。 - 設定預設的 CORS 預檢選項,以允許所有來源、標頭和方法。
建立和組態 DynamoDB 例項
在建立了 Lambdas 和 API Gateway 後,剩下的步驟是替換 RDS,建立一個 DynamoDB 表。
this.table = new Table(this, `Dynamo-Table-${process.env.NODE_ENV || ''}`, {
partitionKey: { name: 'id', type: AttributeType.STRING },
tableName: `todolist-${process.env.NODE_ENV?.toLowerCase() || ''}`,
billingMode: BillingMode.PAY_PER_REQUEST,
removalPolicy: RemovalPolicy.DESTROY,
});
new DynamoDBSeeder(this, `Dynamo-InlineSeeder-${process.env.NODE_ENV || ''}`, {
table: this.table,
seeds: Seeds.fromInline([
{
id: uuidv4(),
todo_name: 'First todo',
todo_description: "That's a todo for demonstration purposes",
todo_completed: true,
},
]),
});
內容解密:
- 建立一個新的 DynamoDB 表,指定分割鍵、表名、計費模式和移除策略。
- 使用
DynamoDBSeeder建構函式在佈署時自動為表填充資料。