iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0
Build on AWS

DynamoDB銀河傳說首部曲-打造宇宙都打不倒的高效服務系列 第 11

Day 11:從理論回到實戰,使用 JavsScript SDK

  • 分享至 

  • xImage
  •  

第十一天,洛基比往常更早到達茶室。他坐在熟悉的位置上,螢幕上顯示著一些程式碼,但看起來有些混亂。

諾斯克大師走進來時,注意到洛基臉上的困惑表情。

「早安,洛基上尉。看起來你今天有點...不開心?」

洛基深吸一口氣,抬起頭:「大師,我需要坦白一件事。昨天您說今天要學習將零散技巧統整成完整方法論,我也很期待...」

他顯得有些不好意思:「但是昨天晚上,當我想嘗試寫一個簡單的星際活動系統來驗證我的理解時,卻發現...我連最基本的程式碼都寫不出來。我學了很多概念—主鍵設計、Query 限制、Filter Expression、Access Pattern,但實際動手時卻完全不知道該怎麼開始。」

理論與實戰的落差

諾斯克大師坐下,溫和地問:「能具體說說遇到了什麼問題嗎?」

洛基有些不好意思:「我想用 JavaScript 來實作我們學過的 Query 操作,但我發現...我根本不知道該怎麼開始。我們一直在用 AWS CLI,但實際開發時應該用什麼?」

洛基帶點挫折地繼續說:「我理解了概念,但不知道在實際專案中該怎麼實現。感覺就像...知道戰術理論,但拿到真槍時不知道怎麼開火。」

從命令列走向真正的開發

諾斯克大師沉思了一下說:「前面十天我們用 AWS CLI 是為了讓你快速理解 DynamoDB 的核心概念,但現在你需要先具備將這些概念轉換成實際程式碼的能力。讓我們先把實戰基礎打穩,方法論的整合可以稍後再談。」

Hippo 在一旁補充道:「就像你學會了星際導航的理論,現在該學會實際駕駛太空船了。而在 JavaScript 世界裡,AWS SDK v3 就是你的太空船。」

JavaScript SDK 的正確認識

「讓我們從頭開始,」大師說,「首先要理解 AWS DynamoDB 的 JavaScript SDK。Hippo,麻煩秀出比較圖,」

Hippo 應答:「馬上來!」

https://ithelp.ithome.com.tw/upload/images/20250926/20178813UvHcVPnpYu.jpg

「我們之前用 AWS CLI 是為了讓你專注於概念學習,不被程式碼語法干擾。但現在你需要將這些概念轉換成實際的開發技能。」

SDK 版本的重要選擇

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 官方主推,長期支援"
};

DocumentClient 的威力

「但是,」大師繼續,「即使使用 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!」

將 CLI 概念轉換為 SDK

Hippo 接著展示如何將之前學過的每個操作轉換:

Query 操作的轉換

// 第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);

Update 操作的轉換

// 第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 操作的最後一塊拼圖,讓你的實戰工具箱更加完整。」

Hippo 的 SDK 實戰指南

SDK v3 快速參考

// 必要的依賴安裝
// 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);

上一篇
Day 10:Filter Expression的用處與陷阱
下一篇
Day 12:補完 CRUD 失落的一片-刪除
系列文
DynamoDB銀河傳說首部曲-打造宇宙都打不倒的高效服務13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言