오늘은 AWS Lambda + DynamoDB + API Gateway 환경을 세팅해
간단하게 Lambda 함수로 DynamoDB에 접근하는 실습을 해보려고 한다.
시작하기 전에 간단하게!
- DynamoDB: 완전관리형 NoSQL Database
- Lambda: 서버리스 컴퓨팅 플랫폼
- API Gateway: REST 및 WebSocket API를 생성, 게시, 유지, 모니터링 및 보호하기 위한 AWS 서비스
1. DynamoDB 생성
DynamoDB 탭에 들어가서 테이블 생성을 해준다.
지금은 간단하게 실습만 해볼거라서 테이블 이름과 파티션 키 이름만 적어주고 생성해준다! (나머지는 기본값 그대로!)
DynamoDB에서는 2가지의 기본키를 지원하는데, 하나는 파티션키이고 다른 하나는 파티션키+정렬키이다.
- 파티션 키
하나의 속성으로 구성되는 단순 기본 키이다. DynamoDB는 내부 해시 함수에 대한 입력으로 파티션 키 값을 사용하는데, 해시 함수 출력에 따라 항목을 저장할 파티션(DynamoDB 내부의 물리적 스토리지)이 결정된다. 파티션 키로만 구성되어 있는 테이블에서는 어떤 두 개의 테이블 항목(=row)도 동일한 파티션 키 값을 가질 수 없다. - 파티션 키 + 정렬 키
복합 기본 키로 지칭되는 이 형식의 키는 두 개의 속성으로 구성된다. 첫 번째 속성은 파티션 키이고, 두 번째 속성은 정렬 키이다.
파티션 키 값이 동일한 모든 항목은 정렬 키 값을 기준으로 정렬되어 함께 저장된다. 파티션 키와 정렬 키로 구성되어 있는 테이블에서는 여러 항목이 동일한 파티션 키 값을 가질 수 있다. 그러나 이러한 아이템의 정렬 키 값은 달라야 한다.
그러니까, 정리해보면 파티션 키만 만들어주면 단일 속성으로 이루어진 기본 키가 만들어진다는 거고
정렬키를 지정해주면 파티션 키와 정렬 키 2가지 속성으로 이루어진 복합 기본 키가 만들어진다는 이야기다!
더 자세한 내용은 공식 문서에서 찾아보기로 하고,, 다음 단계로 넘어가보자!
2. Lambda 함수 생성
Lambda 탭에 들어가서 함수 생성을 해준다.
새로 작성을 클릭하고, 함수 이름을 작성해준다.
런타임은 기본값인 node 18 버전을 사용했고, 아키텍처도 x86_64 그대로 놔두었다.
그리고 밑에 기본 실행 역할을 변경해주었는데,
기본 lambda 권한을 가진 새 역할 생성을 해주어도 되지만
나는 역할 이름을 지정해주고 싶어서 + 추가로 권한도 설정해줘야 해서 그냥 새 역할 생성을 해주었다.
그러고 함수 생성 버튼을 눌러주면 생성이 완료된다!!
3. Lambda 함수 IAM Role 수정
Lambda 함수의 구성 탭에서 역할 이름을 눌러 IAM Role에 권한을 추가해 줄 것이다.
아래 사진처럼 CloudWatchLogsFullAccess와 AmazonDynamoDBFullAccess 권한을 추가해준다!
4. API Gateway 생성
다음으로 API Gateway를 생성해 줄 것이다.
JavaScript/Lambda 코드는 DynamoDB와 직접 상호 작용할 수 없기 때문에,
API Gateway로 JavaScript 코드가 Lambda 함수를 연결하고 실행할 수 있는 REST 환경을 생성해줘야 한다!
API Gateway 탭에서 시작하기 클릭 -> 위에서 3번째에 위치한 REST API 구축 클릭
아래 사진처럼 새 API를 클릭하고 API 이름을 적어준 후 파란색 API 생성 버튼을 눌러준다.
이제 여기에 API의 엔드포인트가 될 리소스와 메서드를 생성해 주면 된다.
일단 health check를 하기 위해 /health 리소스(=API 엔드포인트)를 만들어주자.
그다음, /health를 선택 후 작업 > 메서드 생성을 눌러준다.
/health는 GET이기 때문에 GET을 선택해주고, 동그란 체크 표시를 눌러주면 아래와 같은 화면이 나타난다.
여기서 우리는 Lambda 함수를 연결해 줄 것이기 때문에 Lambda를 선택해주고
아까 만들어준 Lambda 함수 이름을 입력해준다 (자동완성에 뜸!)
Lambda 프록시 통합 사용을 체크해준다! (공식 문서를 읽어봐도 이게 뭔지는 잘 이해가 안간다..🧐)
그 다음에 여러 메서드를 확인해보기 위해 아래 사진처럼 만들어준다.
방금 한 것처럼 리소스 만들고 메서드 추가해주면 된다!
이제 작업 > API 배포를 눌러 API를 배포해보자.
나는 아래 사진처럼 스테이지 이름만 설정해주고 배포해주었다.
이제 스테이지 탭에 들어가서 beta (위에서 설정한 스테이지 이름)을 클릭하면 배포 URL을 확인할 수 있다!!
5. Lambda 코드 수정
이제 Lambda 함수의 코드를 수정해보자.
사진에 보이는 코드 부분에 코드를 복붙해서 넣어준다.
👇🏻 코드 복붙
(참고로 나는 node 18 버전이라 이전 버전과 DynamoDB 사용하는 방법이 다르다!)
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
const ddbDocClient = new DynamoDBClient({ region: "ap-northeast-2" });
import { UpdateCommand, PutCommand, GetCommand, DeleteCommand } from "@aws-sdk/lib-dynamodb";
const dynamoDBTableName = "asc-dynamo-test";
// Resources(endpoints) created in API Gateway
const healthPath = "/health";
const productPath = "/product";
export const handler = async function (event) {
console.log("Request event" + event);
let response;
switch (true) {
case event.httpMethod === "GET" && event.path === healthPath:
response = buildResponse(200);
break;
case event.httpMethod === "GET" && event.path === productPath:
response = await getProduct(event.queryStringParameters.productId);
break;
case event.httpMethod === "POST" && event.path === productPath:
response = await saveProduct(JSON.parse(event.body));
break;
case event.httpMethod === "PATCH" && event.path === productPath:
const requestBody = JSON.parse(event.body);
response = await modifyProduct(
requestBody.productId,
requestBody.updateKey,
requestBody.updateValue
);
break;
case event.httpMethod === "DELETE" && event.path === productPath:
response = await deleteProduct(event.queryStringParameters.productId);
break;
default:
response = buildResponse(404, "404 Not Found");
}
return response;
};
// Get Specific Product
async function getProduct(productId) {
const params = {
TableName: dynamoDBTableName,
Key: {
primaryId: productId,
},
};
return await ddbDocClient
.send(new GetCommand(params))
.then((response) => {
return buildResponse(200, response.Item);
},
(err) => console.log("ERROR: ", err)
);
}
// Add a Product
async function saveProduct(requestBody) {
const params = {
TableName: dynamoDBTableName,
Item: requestBody,
};
return await ddbDocClient
.send(new PutCommand(params))
.then(() => {
const body = {
Operation: "SAVE",
Message: "SUCCESS",
Item: requestBody,
};
return buildResponse(200, body);
},(err) => {
console.log("ERROR in Save Product: ", err);
}
);
}
async function modifyProduct(productId, updateKey, updateValue) {
const params = {
TableName: dynamoDBTableName,
Key: {
primaryId: productId,
},
UpdateExpression: `set ${updateKey} = :value`,
ExpressionAttributeValues: {
":value": updateValue,
},
ReturnValues: "UPDATED_NEW",
};
return await ddbDocClient
.send(new UpdateCommand(params))
.then(
(response) => {
const body = {
Operation: "UPDATE",
Message: "SUCCESS",
UpdatedAttributes: response,
};
return buildResponse(200, body);
}, (err) => {
console.log("ERROR in Update Product: ", err);
}
);
}
// Delete a Product
async function deleteProduct(productId) {
const params = {
TableName: dynamoDBTableName,
Key: {
primaryId: productId,
},
ReturnValues: "ALL_OLD",
};
return await ddbDocClient
.send(new DeleteCommand(params))
.then((response) => {
const body = {
Operation: "DELETE",
Message: "SUCCESS",
Item: response,
};
return buildResponse(200, body);
},
(err) => {
console.log("ERROR in Delete Product: ", err);
}
);
}
// For specific response structure
function buildResponse(statusCode, body) {
return {
statusCode,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
};
}
이제 테스트를 해주면 되는데, 사실 테스트 하는 방법은 잘 모르겠어서 건너뛰었다.
바로 Deploy를 눌러주면 수정한 코드 배포 완료이다!
6. Postman으로 확인
이제 배포가 잘 되었는지 확인해보자~!
먼저 health check! 잘 돌아간다
그리고 save product 역시 잘 된다!
여기서 요청을 보낼 때 꼭 파티션 키 값이 있어야 한다!
나는 파티션 키 값을 primaryId (String)으로 지정해주었기 때문에 아래처럼 넣어주었다.
방금 넣은 product 조회해보기! 역시 잘 된다 ㅎㅎ
product 정보 업데이트하기!
updateKey는 업데이트할 속성 이름, updateValue는 해당 속성을 어떤 값으로 업데이트할건지 넣어주면 된다.
삭제하기! 잘 된다
여기까지 Lambda + DynamoDB + API Gateway를 활용한 예제를 구현해보았다🙌🏻
원래 get products API도 있어야 하지만.. 그건 scan을 이용해야 하는데 좀 더 찾아봐야 할 것 같다 ㅎㅎ;