本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
使用 JavaScript 編寫 Amazon DynamoDB 程式
本指南為想要搭配 JavaScript 使用 Amazon DynamoDB 的程式設計人員提供指導。了解 AWS SDK for JavaScript、可用的抽象層、設定連線、處理錯誤、定義重試政策、管理保持連線等。
主題
關於 AWS SDK for JavaScript
AWS SDK for JavaScript 可讓您 AWS 服務 使用瀏覽器指令碼或 Node.js 存取 。本文件著重於最新版本的 SDK (V3)。V3 AWS SDK for JavaScript 由 維護 AWS 為 GitHub 上託管的開放原始碼專案
JavaScript V2 類似於 V3,但包含語法差異。V3 更模組化,可更輕鬆地提供較小的相依性,並具有一級 TypeScript 支援。我們建議您使用最新版的 SDK。
使用 AWS SDK for JavaScript 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 V3 的 SDK 具有低階用戶端 (DynamoDBClient) 和高階用戶端 (DynamoDBDocumentClient)。
低階客戶端 (DynamoDBClient)
低階用戶端對於基礎線路通訊協定不提供額外的抽象概念。它可讓您完全控制通訊的各個層面,但由於沒有抽象概念,因此您必須使用 DynamoDB JSON 格式執行提供項目定義等操作。
如以下範例所示,使用此格式的資料類型必須明確陳述。S 表示字串值,N 表示數值。線路上的數字一律會以標記為數字類型的字串傳送,以確保精確度不會遺失。低階 API 呼叫的命名模式例如 PutItemCommand 和 GetItemCommand。
下列範例使用低階用戶端,搭配利用 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();
當您使用 GetItem、Query 或 Scan 等 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 儲存庫
分頁
Scan 或 Query 等讀取請求可能會傳回資料集中的多個項目。如果您使用 Limit 參數執行 Scan 或 Query,一旦系統讀取到該項目數時,就會傳送部分回應,而且您需要分頁才能擷取其他項目。
對於每個請求,系統最多只會讀取 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 便利方法
開發套件提供稱為 paginateScan 和 paginateQuery 的便利方法,可在幕後為您執行此操作並重複提出請求。使用標準 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 層的某些層面。例如,您可以調整金鑰逾時值 connectionTimeout 和 requestTimeout。connectionTimeout 是用戶端在嘗試建立連線後放棄連線時所等待的最長持續時間,以毫秒為單位。
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
除了設定逾時值之外,您還可以設定通訊端數量上限,以增加每個原始伺服器的並行連線數量。開發人員指南包含設定 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 功能只會在可以執行會顯示資料表狀態 ACTIVE 的 describe-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 } }
如果您管理自己的重試政策,您會想要區分限流和錯誤:
-
限流 (由
ProvisionedThroughputExceededException或ThrottlingException表示) 表示運作狀態良好的服務,通知您已超過 DynamoDB 資料表或分割區的讀取或寫入容量。每經過一毫秒,就能獲得更多讀取或寫入容量,因此您可以快速重試,例如每 50 毫秒,以嘗試存取該新發布的容量。使用限流時,您不需要特別使用指數退避,因為傳回限流對 DynamoDB 很輕量,而且不會向您收取每次請求的費用。指數退避會將較長的延遲指派給已等待最長時間的用戶端執行緒,以統計方式將 p50 和 p99 向外延伸。
-
錯誤 (由
InternalServerError或ServiceUnavailable等表示) 表示服務的暫時性問題,可能是整個資料表,也可能是您正在讀取或寫入的分割區。發生錯誤時,您可以在重試之前暫停更長的時間,例如 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 行為。如需詳細資訊,請參閱 部落格簡介模組化 Middleware Stack AWS SDK for JavaScript
考量事項
在專案 AWS SDK for JavaScript 中實作 時,以下是一些需要考慮的進一步因素。
- 模組系統
-
開發套件支援兩個模組系統:CommonJS 和 ES (ECMAScript)。CommonJS 使用
require函數,而 ES 則使用import關鍵字。-
CommonJS –
const { DynamoDBClient, PutItemCommand } = require("@aws-sdk/client-dynamodb"); -
ES (ECMAScript –
import { 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) 資料平面操作。