使用 JavaScript 編寫 Amazon DynamoDB 程式 - Amazon DynamoDB

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

使用 JavaScript 編寫 Amazon DynamoDB 程式

本指南為想要搭配 JavaScript 使用 Amazon DynamoDB 的程式設計人員提供指導。了解 適用於 JavaScript 的 AWS SDK、可用的抽象層、設定連線、處理錯誤、定義重試政策、管理保持連線等。

關於 適用於 JavaScript 的 AWS SDK

適用於 JavaScript 的 AWS SDK 可讓您使用瀏覽器指令碼或 Node.js 存取 AWS 服務。本文件著重於最新版本的 SDK (V3)。適用於 JavaScript 的 AWS SDK V3 由 AWS 維護,它是託管於 GitHub 的開放原始碼專案。問題和功能請求是公開的,您可以在 GitHub 儲存庫的問題頁面上存取它們。

JavaScript V2 類似於 V3,但包含語法差異。V3 更模組化,可更輕鬆地提供較小的相依性,並具有一級 TypeScript 支援。我們建議您使用最新版的 SDK。

使用 適用於 JavaScript 的 AWS SDK V3

您可以使用 Node Package Manager 將 SDK 新增至 Node.js 應用程式。以下範例示範如何新增最常用於 DynamoDB 的 SDK 套件。

  • npm install @aws-sdk/client-dynamodb

  • npm install @aws-sdk/lib-dynamodb

  • npm install @aws-sdk/util-dynamodb

安裝套件會將參考新增至 package.json 專案檔案的相依性區段。您可以選擇使用較新的 ECMAScript 模組語法。如需這兩種方法的進一步詳細資訊,請參閱考量一節。

存取 JavaScript 文件

使用下列資源開始使用 JavaScript 文件:

  • 存取開發人員指南以取得核心 JavaScript 文件。安裝指示位於設定區段。

  • 存取 API 參考文件,以探索所有可用的類別和方法。

  • 適用於 JavaScript 的 SDK 支援 DynamoDB 以外的許多 AWS 服務。使用下列程序來尋找 DynamoDB 的特定 API 涵蓋範圍:

    1. 服務中,選擇 DynamoDB 和程式庫。這會記錄低階用戶端。

    2. 選擇 lib-dynamodb。這會記錄高階用戶端。兩個用戶端代表您可以選擇使用的兩個不同的抽象層。如需抽象層的詳細資訊,請參閱以下章節。

抽象層

適用於 JavaScript V3 的 SDK 具有低階用戶端 (DynamoDBClient) 和高階用戶端 (DynamoDBDocumentClient)。

低階客戶端 (DynamoDBClient)

低階用戶端對於基礎線路通訊協定不提供額外的抽象概念。它可讓您完全控制通訊的各個層面,但由於沒有抽象概念,因此您必須使用 DynamoDB JSON 格式執行提供項目定義等操作。

如以下範例所示,使用此格式的資料類型必須明確陳述。S 表示字串值,N 表示數值。線路上的數字一律會以標記為數字類型的字串傳送,以確保精確度不會遺失。低階 API 呼叫的命名模式例如 PutItemCommandGetItemCommand

下列範例使用低階用戶端,搭配利用 DynamoDB JSON 定義的 Item

const { DynamoDBClient, PutItemCommand } = require("@aws-sdk/client-dynamodb"); const client = new DynamoDBClient({}); async function addProduct() { const params = { TableName: "products", Item: { "id": { S: "Product01" }, "description": { S: "Hiking Boots" }, "category": { S: "footwear" }, "sku": { S: "hiking-sku-01" }, "size": { N: "9" } } }; try { const data = await client.send(new PutItemCommand(params)); console.log('result : ' + JSON.stringify(data)); } catch (error) { console.error("Error:", error); } } addProduct();

高階用戶端 (DynamoDBDocumentClient)

高階 DynamoDB 文件用戶端提供內建的便利功能,例如不需要手動封送資料,以及允許使用標準 JavaScript 物件直接讀取和寫入。lib-dynamodb 文件提供優點清單。

若要執行個體化 DynamoDBDocumentClient,請建構低階 DynamoDBClient,然後使用 DynamoDBDocumentClient 包裝它。函數命名慣例在兩個套件之間略有不同。例如,低階使用 PutItemCommand,而高階使用 PutCommand。不同的名稱允許兩組函數在相同的內容中共存,讓您可以在相同的指令碼中混合兩者。

const { DynamoDBClient } = require("@aws-sdk/client-dynamodb"); const { DynamoDBDocumentClient, PutCommand } = require("@aws-sdk/lib-dynamodb"); const client = new DynamoDBClient({}); const docClient = DynamoDBDocumentClient.from(client); async function addProduct() { const params = { TableName: "products", Item: { id: "Product01", description: "Hiking Boots", category: "footwear", sku: "hiking-sku-01", size: 9, }, }; try { const data = await docClient.send(new PutCommand(params)); console.log('result : ' + JSON.stringify(data)); } catch (error) { console.error("Error:", error); } } addProduct();

當您使用 GetItemQueryScan 等 API 操作讀取項目時,使用模式是一致的。

使用 marshall 公用程式函數

您可以自行使用低階用戶端和封送或取消封送資料類型。公用程式套件 util-dynamodb 具有 marshall() 公用程式函數,可接受 JSON 並產生 DynamoDB JSON,以及反向執行的 unmarshall() 函數。下列範例使用低階用戶端搭配由 marshall() 呼叫處理的資料封送。

const { DynamoDBClient, PutItemCommand } = require("@aws-sdk/client-dynamodb"); const { marshall } = require("@aws-sdk/util-dynamodb"); const client = new DynamoDBClient({}); async function addProduct() { const params = { TableName: "products", Item: marshall({ id: "Product01", description: "Hiking Boots", category: "footwear", sku: "hiking-sku-01", size: 9, }), }; try { const data = await client.send(new PutItemCommand(params)); } catch (error) { console.error("Error:", error); } } addProduct();

讀取項目

若要從 DynamoDB 資料表讀取項目,請使用 GetItem API 操作。與 PutItem 命令類似,您可以選擇使用低階用戶端或高階文件用戶端。以下範例示範如何使用高階文件用戶端擷取項目。

const { DynamoDBClient } = require("@aws-sdk/client-dynamodb"); const { DynamoDBDocumentClient, GetCommand } = require("@aws-sdk/lib-dynamodb"); const client = new DynamoDBClient({}); const docClient = DynamoDBDocumentClient.from(client); async function getProduct() { const params = { TableName: "products", Key: { id: "Product01", }, }; try { const data = await docClient.send(new GetCommand(params)); console.log('result : ' + JSON.stringify(data)); } catch (error) { console.error("Error:", error); } } getProduct();

使用 Query API 操作讀取多個項目。您可以使用低階用戶端或文件用戶端。以下範例使用高階文件用戶端。

const { DynamoDBClient } = require("@aws-sdk/client-dynamodb"); const { DynamoDBDocumentClient, QueryCommand, } = require("@aws-sdk/lib-dynamodb"); const client = new DynamoDBClient({}); const docClient = DynamoDBDocumentClient.from(client); async function productSearch() { const params = { TableName: "products", IndexName: "GSI1", KeyConditionExpression: "#category = :category and begins_with(#sku, :sku)", ExpressionAttributeNames: { "#category": "category", "#sku": "sku", }, ExpressionAttributeValues: { ":category": "footwear", ":sku": "hiking", }, }; try { const data = await docClient.send(new QueryCommand(params)); console.log('result : ' + JSON.stringify(data)); } catch (error) { console.error("Error:", error); } } productSearch();

條件式寫入

DynamoDB 寫入操作可以指定邏輯條件表達式,必須評估為 true 才能繼續寫入。如果條件未評估為 true,寫入操作會產生例外狀況。條件表達式可以檢查項目是否已存在,或其屬性是否符合特定限制條件。

ConditionExpression = "version = :ver AND size(VideoClip) < :maxsize"

當條件式表達式失敗時,您可以使用 ReturnValuesOnConditionCheckFailure 來請求錯誤回應包含不符合條件的項目,以推斷問題所在。如需詳細資訊,請參閱使用 Amazon DynamoDB 在高並行情況下處理條件式寫入錯誤

try { const response = await client.send(new PutCommand({ TableName: "YourTableName", Item: item, ConditionExpression: "attribute_not_exists(pk)", ReturnValuesOnConditionCheckFailure: "ALL_OLD" })); } catch (e) { if (e.name === 'ConditionalCheckFailedException') { console.log('Item already exists:', e.Item); } else { throw e; } }

其他程式碼範例顯示 JavsScript SDK V3 用量的其他層面,可在 JavaScript SDK V3 文件DynamoDB-SDK-Examples GitHub 儲存庫下取得。

分頁

ScanQuery 等讀取請求可能會傳回資料集中的多個項目。如果您使用 Limit 參數執行 ScanQuery,一旦系統讀取到該項目數時,就會傳送部分回應,而且您需要分頁才能擷取其他項目。

對於每個請求,系統最多只會讀取 1 MB 的資料。如果您包含 Filter 表達式,系統仍將從磁碟讀取最多 1 MB 的資料,但會傳回該 MB 中符合篩選條件的項目。篩選操作可能從頁面傳回 0 個項目,但在搜尋窮盡之前仍需要進一步分頁。

您應該在回應中尋找 LastEvaluatedKey,並在後續請求中使用它做為 ExclusiveStartKey 參數,以繼續資料擷取。這可做為書籤,如下列範例所述。

注意

在第一次迭代時,該範例會傳遞 null lastEvaluatedKey 做為 ExclusiveStartKey,這是允許的。

使用 LastEvaluatedKey 的範例:

const { DynamoDBClient, ScanCommand } = require("@aws-sdk/client-dynamodb"); const client = new DynamoDBClient({}); async function paginatedScan() { let lastEvaluatedKey; let pageCount = 0; do { const params = { TableName: "products", ExclusiveStartKey: lastEvaluatedKey, }; const response = await client.send(new ScanCommand(params)); pageCount++; console.log(`Page ${pageCount}, Items:`, response.Items); lastEvaluatedKey = response.LastEvaluatedKey; } while (lastEvaluatedKey); } paginatedScan().catch((err) => { console.error(err); });

使用 paginateScan 便利方法

開發套件提供稱為 paginateScanpaginateQuery 的便利方法,可在幕後為您執行此操作並重複提出請求。使用標準 Limit 參數指定每個請求要讀取的項目數量上限。

const { DynamoDBClient, paginateScan } = require("@aws-sdk/client-dynamodb"); const client = new DynamoDBClient({}); async function paginatedScanUsingPaginator() { const params = { TableName: "products", Limit: 100 }; const paginator = paginateScan({client}, params); let pageCount = 0; for await (const page of paginator) { pageCount++; console.log(`Page ${pageCount}, Items:`, page.Items); } } paginatedScanUsingPaginator().catch((err) => { console.error(err); });
注意

除非資料表很小,否則不建議採用定期執行完整資料表掃描的存取模式。

指定組態

設定 DynamoDBClient 時,您可以將組態物件傳遞至建構函數,以指定各種組態覆寫。例如,如果呼叫內容還不知道,您可以指定要連線的區域,或指定要使用的端點 URL。如果您想要針對 DynamoDB 本機版執行個體進行開發,這非常有用。

const client = new DynamoDBClient({ region: "eu-west-1", endpoint: "http://localhost:8000", });

逾時的組態

DynamoDB 使用 HTTPS 進行用戶端-伺服器通訊。您可以透過提供 NodeHttpHandler 物件來控制 HTTP 層的某些層面。例如,您可以調整金鑰逾時值 connectionTimeoutrequestTimeoutconnectionTimeout 是用戶端在嘗試建立連線後放棄連線時所等待的最長持續時間,以毫秒為單位。

requestTimeout 定義傳送請求後用戶端等待回應的時間長度,也以毫秒為單位。兩者的預設值都是零,表示逾時已停用,而且如果未收到回應,用戶端等待的時間將沒有極限。您應該將逾時設定為合理值,以便在發生網路問題時,請求會因逾時而發出錯誤,並可啟動新的請求。例如:

import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { NodeHttpHandler } from "@smithy/node-http-handler"; const requestHandler = new NodeHttpHandler({ connectionTimeout: 2000, requestTimeout: 2000, }); const client = new DynamoDBClient({ requestHandler });
注意

提供的範例使用 Smithy 匯入。Smithy 是一種用於定義服務和 SDK 的語言,屬於開放原始碼,並由 AWS 維護。

除了設定逾時值之外,您還可以設定通訊端數量上限,以增加每個原始伺服器的並行連線數量。開發人員指南包含設定 maxSockets 參數的詳細資訊

保持連線的組態

使用 HTTPS 時,第一個請求一律需要一些來回通訊才能建立安全連線。HTTP Keep-Alive 允許後續請求重複使用已建立的連線,使請求更有效率並降低延遲。JavaScript V3 預設會啟用 HTTP Keep-Alive。

閒置連線可以保持運作的時間有限制。如果您有閒置連線,但希望下一個請求使用已建立的連線,請考慮每分鐘傳送定期請求。

注意

請注意,在較舊的 SDK V2 中,保持連線預設為關閉,這表示每個連線都會在使用後立即關閉。如果使用 V2,您可以覆寫此設定。

重試的組態

當 SDK 收到錯誤回應,且 SDK 判斷該錯誤是可恢復的,例如限流例外狀況或暫時服務例外狀況時,將會再次重試。身為呼叫者的您不會察覺到這一切,但您可能會注意到請求需要更長的時間才能成功。

根據預設,適用於 JavaScript V3 的 SDK 會在放棄錯誤並將其傳遞至呼叫內容之前,發出總共 3 次請求。您可以調整這些重試次數和頻率。

DynamoDBClient 建構函數接受會限制將要嘗試多少次的 maxAttempts 設定。以下範例會將該值從預設值 3 提高為總計 5 次。如果您將其設定為 0 或 1,表示您不希望任何自動重試,並想要自行在擷取區塊內處理任何可恢復的錯誤。

const client = new DynamoDBClient({ maxAttempts: 5, });

您也可以使用自訂重試策略來控制重試的時間。若要這樣做,請匯入 util-retry 公用程式套件,並建立自訂退避函數,根據目前的重試計數計算重試之間的等待時間。

以下範例顯示,如果第一次嘗試失敗,最多嘗試 5 次,延遲為 15、30、90 和 360 毫秒。自訂退避函數 calculateRetryBackoff 會接受重試嘗試次數 (從第一次重試的 1 開始) 來計算延遲,並傳回該請求應等待的毫秒數。

const { ConfiguredRetryStrategy } = require("@aws-sdk/util-retry"); const calculateRetryBackoff = (attempt) => { const backoffTimes = [15, 30, 90, 360]; return backoffTimes[attempt - 1] || 0; }; const client = new DynamoDBClient({ retryStrategy: new ConfiguredRetryStrategy( 5, // max attempts. calculateRetryBackoff // backoff function. ), });

等待程式

DynamoDB 用戶端包含兩個有用的等待程式函數,當您希望程式碼等待資料表修改完成後才繼續執行時,可在建立、修改或刪除資料表時使用。例如,您可以部署資料表、呼叫 waitUntilTableExists 函數,而且程式碼會封鎖,直到資料表變成 ACTIVE 為止。等待程式每 20 秒會以 describe-table 輪詢一次 DynamoDB 服務。

import {waitUntilTableExists, waitUntilTableNotExists} from "@aws-sdk/client-dynamodb"; … <create table details> const results = await waitUntilTableExists({client: client, maxWaitTime: 180}, {TableName: "products"}); if (results.state == 'SUCCESS') { return results.reason.Table } console.error(`${results.state} ${results.reason}`);

waitUntilTableExists 功能只會在可以執行會顯示資料表狀態 ACTIVEdescribe-table 命令時傳回控制項。這可確保您可以使用 waitUntilTableExists 等待建立完成,以及新增 GSI 索引等修改,這可能需要一些時間來套用,資料表才會返回 ACTIVE 狀態。

錯誤處理

在這裡的早期範例中,我們概括地發現了所有錯誤。不過,在實際的應用程式中,區分各種錯誤類型並實作更精確的錯誤處理非常重要。

DynamoDB 錯誤回應包含中繼資料,包括錯誤的名稱。您可以擷取錯誤,然後比對可能的錯誤情況字串名稱,以判斷如何繼續。對於伺服器端錯誤,您可以利用 instanceof 運算子搭配 @aws-sdk/client-dynamodb 套件匯出的錯誤類型,有效率地管理錯誤處理。

請務必注意,這些錯誤只有在所有重試都用盡之後才會顯示。如果重試錯誤,且最終接續成功呼叫,從程式碼的角度來看並沒有錯誤,只是延遲稍微增加了。重試會在 Amazon CloudWatch 圖表中顯示為失敗的請求,例如限流或錯誤請求。如果用戶端達到重試計數上限,則會放棄並產生例外狀況。這是用戶端表達其不會重試的方式。

以下是擷取錯誤並根據傳回的錯誤類型採取行動的程式碼片段。

import { ResourceNotFoundException ProvisionedThroughputExceededException, DynamoDBServiceException, } from "@aws-sdk/client-dynamodb"; try { await client.send(someCommand); } catch (e) { if (e instanceof ResourceNotFoundException) { // Handle ResourceNotFoundException } else if (e instanceof ProvisionedThroughputExceededException) { // Handle ProvisionedThroughputExceededException } else if (e instanceof DynamoDBServiceException) { // Handle DynamoDBServiceException } else { // Other errors such as those from the SDK if (e.name === "TimeoutError") { // Handle SDK TimeoutError. } else { // Handle other errors. } } }

請參閱《DynamoDB 開發人員指南》中的常見 使用 DynamoDB 時發生錯誤 錯誤字串。您可以在該 API 呼叫的文件中找到任何特定 API 呼叫的確切錯誤,例如查詢 API 文件

錯誤中繼資料包含其他屬性,視錯誤而定。對於 TimeoutError,中繼資料包含已嘗試的次數和 totalRetryDelay,如下所示。

{ "name": "TimeoutError", "$metadata": { "attempts": 3, "totalRetryDelay": 199 } }

如果您管理自己的重試政策,您會想要區分限流和錯誤:

  • 限流 (由 ProvisionedThroughputExceededExceptionThrottlingException 表示) 表示運作狀態良好的服務,通知您已超過 DynamoDB 資料表或分割區的讀取或寫入容量。每經過一毫秒,就能獲得更多讀取或寫入容量,因此您可以快速重試,例如每 50 毫秒,以嘗試存取該新發布的容量。

    使用限流時,您不需要特別使用指數退避,因為傳回限流對 DynamoDB 很輕量,而且不會向您收取每次請求的費用。指數退避會將較長的延遲指派給已等待最長時間的用戶端執行緒,以統計方式將 p50 和 p99 向外延伸。

  • 錯誤 (由 InternalServerErrorServiceUnavailable 等表示) 表示服務的暫時性問題,可能是整個資料表,也可能是您正在讀取或寫入的分割區。發生錯誤時,您可以在重試之前暫停更長的時間,例如 250 毫秒或 500 毫秒,並使用抖動來交錯重試。

日誌

開啟記錄功能,以取得 SDK 正在執行之作業的詳細資訊。您可以在 DynamoDBClient 上設定參數,如以下範例所示。更多日誌資訊會顯示在主控台中,並包含中繼資料,例如狀態碼和耗用容量。如果您於本機上在終端機視窗中執行程式碼,日誌會顯示在該處。如果您在 AWS Lambda 中執行程式碼,且已設定 Amazon CloudWatch logs,則會在該處寫入主控台輸出。

const client = new DynamoDBClient({ logger: console });

您也可以連接到內部 SDK 活動,並在發生特定事件時執行自訂記錄。以下範例使用用戶端的 middlewareStack 攔截從 SDK 傳送的每個請求,並在發生時記錄該請求。

const client = new DynamoDBClient({}); client.middlewareStack.add( (next) => async (args) => { console.log("Sending request from AWS SDK", { request: args.request }); return next(args); }, { step: "build", name: "log-ddb-calls", } );

MiddlewareStack 提供強大的勾點,用於觀察和控制 SDK 行為。如需詳細資訊,請參閱《簡介模組化 適用於 JavaScript 的 AWS SDK 中的 Middleware Stack》部落格。

考量事項

在專案中實作 適用於 JavaScript 的 AWS SDK 時,以下是一些需要考慮的進一步因素。

模組系統

開發套件支援兩個模組系統:CommonJS 和 ES (ECMAScript)。CommonJS 使用 require 函數,而 ES 則使用 import 關鍵字。

  1. CommonJSconst { DynamoDBClient, PutItemCommand } = require("@aws-sdk/client-dynamodb");

  2. ES (ECMAScriptimport { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";

專案類型指定要使用的模組系統,並在 package.json 檔案的類型區段中指定。預設為 CommonJS。使用 "type": "module" 來指示 ES 專案。如果您有使用 CommonJS 套件格式的現有 Node.JS 專案,您仍然可以使用 .mjs 副檔名來命名函數檔案,以新增具有更現代化 SDK V3 匯入語法的函數。這將允許把程式碼檔案視為 ES (ECMAScript)。

非同步操作

您將看到許多使用回呼的程式碼範例,並承諾處理 DynamoDB 操作的結果。使用現代 JavaScript 不再需要這種複雜性,開發人員可以利用更簡潔且可讀取的非同步/等待語法進行非同步操作。

Web 瀏覽器執行時期

使用 React 或 React Native 進行建置的 Web 和行動開發人員可以在其專案中使用適用於 JavaScript 的 SDK。使用舊版的 SDK V2,Web 開發人員必須將完整的 SDK 載入瀏覽器,並參考託管於 https://sdk.amazonaws.com/js/ 的 SDK 映像。

使用 V3 時,您可以使用 Webpack 將所需的 V3 用戶端模組和所有必要的 JavaScript 函數綁定到單一 JavaScript 檔案中,並將其新增至 HTML 頁面 <head> 中的指令碼標籤,如 SDK 文件的瀏覽器指令碼入門一節所述。

DAX 資料平面操作

適用於 JavaScript V3 的 SDK 支援 Amazon DynamoDB Streams Accelerator (DAX) 資料平面操作。