第十一天,洛基比往常更早到達茶室。他坐在熟悉的位置上,螢幕上顯示著一些程式碼,但看起來有些混亂。
諾斯克大師走進來時,注意到洛基臉上的困惑表情。
「早安,洛基上尉。看起來你今天有點...不開心?」
洛基深吸一口氣,抬起頭:「大師,我需要坦白一件事。昨天您說今天要學習將零散技巧統整成完整方法論,我也很期待...」
他顯得有些不好意思:「但是昨天晚上,當我想嘗試寫一個簡單的星際活動系統來驗證我的理解時,卻發現...我連最基本的程式碼都寫不出來。我學了很多概念—主鍵設計、Query 限制、Filter Expression、Access Pattern,但實際動手時卻完全不知道該怎麼開始。」
諾斯克大師坐下,溫和地問:「能具體說說遇到了什麼問題嗎?」
洛基有些不好意思:「我想用 JavaScript 來實作我們學過的 Query 操作,但我發現...我根本不知道該怎麼開始。我們一直在用 AWS CLI,但實際開發時應該用什麼?」
洛基帶點挫折地繼續說:「我理解了概念,但不知道在實際專案中該怎麼實現。感覺就像...知道戰術理論,但拿到真槍時不知道怎麼開火。」
諾斯克大師沉思了一下說:「前面十天我們用 AWS CLI 是為了讓你快速理解 DynamoDB 的核心概念,但現在你需要先具備將這些概念轉換成實際程式碼的能力。讓我們先把實戰基礎打穩,方法論的整合可以稍後再談。」
Hippo 在一旁補充道:「就像你學會了星際導航的理論,現在該學會實際駕駛太空船了。而在 JavaScript 世界裡,AWS SDK v3 就是你的太空船。」
「讓我們從頭開始,」大師說,「首先要理解 AWS DynamoDB 的 JavaScript SDK。Hippo,麻煩秀出比較圖,」
Hippo 應答:「馬上來!」
「我們之前用 AWS CLI 是為了讓你專注於概念學習,不被程式碼語法干擾。但現在你需要將這些概念轉換成實際的開發技能。」
Hippo 說:「菜鳥要注意喔,AWS DynamoDB 的 JavaScript SDK 有兩個版本,這是初學者經常困惑的地方。」
// 舊版 SDK v2
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB();
// 新版 SDK v3 (目前推薦)
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const client = new DynamoDBClient({ region: 'us-east-1' });
洛基皺眉:「為什麼會有兩個版本?該用哪個?」
「好問題!」大師說,「新版 SDK v3 有幾個重要優勢:」
// SDK v3 的優勢
const advantages = {
模組化: "只載入需要的功能,減少程式大小",
效能: "啟動更快,記憶體使用更少",
語法: "更現代的 JavaScript 語法",
維護: "AWS 官方主推,長期支援"
};
「但是,」大師繼續,「即使使用 SDK v3,你還會遇到一個問題。」
他在白板上寫下:
// 原生 DynamoDBClient 的語法複雜
const putCommand = new PutItemCommand({
TableName: 'IntergalacticEvents',
Item: {
PK: { S: 'EVENT#001' },
SK: { S: 'METADATA' },
eventName: { S: 'Foundation三部曲讀書會' },
capacity: { N: '100' },
isActive: { BOOL: true }
}
});
洛基看著語法:「這個語法我熟悉,跟 CLI 一樣需要指定型別,但是寫成 JavaScript 程式碼感覺好繁瑣...」
「正是這個問題,」大師說,「這就是為什麼我們需要 DocumentClient。」
// DocumentClient 簡化語法
const { DynamoDBDocumentClient, PutCommand } = require('@aws-sdk/lib-dynamodb');
const docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const putCommand = new PutCommand({
TableName: 'IntergalacticEvents',
Item: {
PK: 'EVENT#001',
SK: 'METADATA',
eventName: 'Foundation三部曲讀書會',
capacity: 100,
isActive: true
}
});
洛基眼睛一亮:「這看起來友善多了!DocumentClient 會自動處理型別轉換?」
「完全正確!」大師說,「DocumentClient 讓你用 JavaScript 的原生型別,它會自動轉換成 DynamoDB 需要的格式。」
「現在讓我們寫你的第一個真正的 DynamoDB 程式,」大師說。
他指導洛基一步步完成:
// 基礎設定
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const {
DynamoDBDocumentClient,
PutCommand,
GetCommand,
QueryCommand
} = require('@aws-sdk/lib-dynamodb');
// 建立連線
const client = new DynamoDBClient({
region: 'us-east-1',
endpoint: 'http://localhost:8000' // 本地 DynamoDB
});
const docClient = DynamoDBDocumentClient.from(client);
// 第一個實戰函式
async function createEvent(eventData) {
const command = new PutCommand({
TableName: 'IntergalacticEvents',
Item: eventData
});
try {
const result = await docClient.send(command);
console.log('活動建立成功:', result);
return result;
} catch (error) {
console.error('建立失敗:', error);
throw error;
}
}
// 實際使用
async function main() {
const newEvent = {
PK: 'PLANET#MARS',
SK: 'EVENT#SY210-04-01-001',
eventName: '火星基地開放日',
date: 'SY210-04-01',
capacity: 200,
registered: 0,
isActive: true
};
await createEvent(newEvent);
}
main();
洛基仔細研究程式碼:「我看懂了!這就是把我們在 CLI 中學的 PutItem 操作轉換成 JavaScript!」
Hippo 接著展示如何將之前學過的每個操作轉換:
// 第5天學過的 CLI 語法
// aws dynamodb query \
// --table-name IntergalacticEvents \
// --key-condition-expression "PK = :pk" \
// --expression-attribute-values '{":pk": {"S": "PLANET#MARS"}}'
// 對應的 SDK 語法
async function queryEventsByPlanet(planet) {
const command = new QueryCommand({
TableName: 'IntergalacticEvents',
KeyConditionExpression: 'PK = :pk',
ExpressionAttributeValues: {
':pk': `PLANET#${planet}`
}
});
const result = await docClient.send(command);
return result.Items;
}
// 使用範例
const marsEvents = await queryEventsByPlanet('MARS');
console.log('火星的活動:', marsEvents);
// 第4天學過的 Update 操作
async function registerForEvent(planetKey, eventKey) {
const command = new UpdateCommand({
TableName: 'IntergalacticEvents',
Key: {
PK: planetKey,
SK: eventKey
},
UpdateExpression: 'ADD registered :inc',
ExpressionAttributeValues: {
':inc': 1
}
});
const result = await docClient.send(command);
return result;
}
// 使用範例
await registerForEvent('PLANET#MARS', 'EVENT#SY210-04-01-001');
洛基說:「我開始看到模式了!每個 CLI 命令都有對應的 SDK Command!」
大師點頭:「很好的觀察!另外在實際開發中,你還需要處理各種錯誤情況。」
// 完整的錯誤處理
async function safeQueryEvents(planet) {
try {
const command = new QueryCommand({
TableName: 'IntergalacticEvents',
KeyConditionExpression: 'PK = :pk',
ExpressionAttributeValues: {
':pk': `PLANET#${planet}`
}
});
const result = await docClient.send(command);
// 檢查結果
if (!result.Items || result.Items.length === 0) {
console.log(`${planet} 目前沒有活動`);
return [];
}
return result.Items;
} catch (error) {
// 處理不同類型的錯誤
if (error.name === 'ResourceNotFoundException') {
console.error('表格不存在:', error.message);
} else if (error.name === 'ValidationException') {
console.error('查詢參數錯誤:', error.message);
} else {
console.error('查詢失敗:', error.message);
}
throw error;
}
}
洛基看完之後說:「實戰程式碼果然要考慮更多東西。」
大師點頭:「程式碼不只要會寫,還要會防錯。就像駕駛太空船,你不只要會操控,還要知道遇到危險時該怎麼辦。」
洛基:「我今天一開始的煩惱好像已經消除的差不多了。」
大師點頭:「很好!現在你有了真正的實戰基礎。理論與實作之間的橋樑已經建立起來了。」
洛基微笑地說:「我感覺現在可以真正開始寫 DynamoDB 應用了!雖然還有很多細節要學,但至少知道從哪裡開始了。」
諾斯克大師說,「明天我們就來補齊 CRUD 操作的最後一塊拼圖,讓你的實戰工具箱更加完整。」
// 必要的依賴安裝
// npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
// 基礎設定範本
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const {
DynamoDBDocumentClient,
PutCommand,
GetCommand,
UpdateCommand,
DeleteCommand,
QueryCommand,
ScanCommand
} = require('@aws-sdk/lib-dynamodb');
const client = new DynamoDBClient({
region: process.env.AWS_REGION || 'us-east-1',
endpoint: process.env.DYNAMODB_ENDPOINT // 本地開發用
});
const docClient = DynamoDBDocumentClient.from(client);
// 基本操作範本
class DynamoDBHelper {
constructor(tableName) {
this.tableName = tableName;
this.docClient = docClient;
}
async put(item) {
const command = new PutCommand({
TableName: this.tableName,
Item: item
});
return await this.docClient.send(command);
}
async get(key) {
const command = new GetCommand({
TableName: this.tableName,
Key: key
});
const result = await this.docClient.send(command);
return result.Item;
}
async query(keyCondition, values) {
const command = new QueryCommand({
TableName: this.tableName,
KeyConditionExpression: keyCondition,
ExpressionAttributeValues: values
});
const result = await this.docClient.send(command);
return result.Items;
}
}
// 環境設定最佳實踐
const config = {
development: {
endpoint: 'http://localhost:8000',
region: 'us-east-1',
credentials: {
accessKeyId: 'fake',
secretAccessKey: 'fake'
}
},
production: {
region: process.env.AWS_REGION,
// 使用 IAM 角色,不需要明確的 credentials
}
};
const clientConfig = process.env.NODE_ENV === 'production'
? config.production
: config.development;
const client = new DynamoDBClient(clientConfig);