Node.js 서버를 통해 Amazon DynamoDB 사용하기
실제 DynamoDB 웹 서비스에 액세스하지 않고 로컬에서 애플리케이션 작성 및 테스트를 할 수 있음
1. 다운로드 링크에서 DynamoDB 무료 다운로드
2. 압축 해제 후 해당 디렉터리에서 아래의 명령어로 실행
java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb
* Ctrl+C로 중지할 수 있고 중지하기 전까지 수신 요청을 처리함
* 기본적으로 8000번 포트를 사용
1. 설치
npm install aws-sdk
2. 실행
// app.js
var AWS = require("aws-sdk");
var s3 = new AWS.S3();
// 버킷 이름은 모든 S3 사용자에게 고유한 것이어야 합니다.
var myBucket = "dynamodb.sample.wonny";
var myKey = "myBucketKey";
s3.createBucket({ Bucket: myBucket }, function(err, data) {
if (err) {
console.log(err);
} else {
params = { Bucket: myBucket, Key: myKey, Body: "Hello!" };
s3.putObject(params, function(err, data) {
if (err) {
console.log(err);
} else {
console.log("Successfully uploaded data to myBucket/myKey");
}
});
}
});
node app.js
// CreateTable.js
var AWS = require("aws-sdk");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var dynamodb = new AWS.DynamoDB();
var params = {
TableName: "Movies",
KeySchema: [
{ AttributeName: "year", KeyType: "HASH" }, // Partition key
{ AttributeName: "title", KeyType: "RANGE" } // Sort key
],
AttributeDefinitions: [
{ AttributeName: "year", AttributeType: "N" },
{ AttributeName: "title", AttributeType: "S" }
],
// 다운로드 버전인 경우 아래 코드 무시
ProvisionedThroughput: {
ReadCapacityUnits: 10,
WriteCapacityUnits: 10
}
};
dynamodb.createTable(params, function(err, data) {
if (err) {
console.log(
"Unable to create table. Error JSON: ",
JSON.stringify(err, null, 2)
);
} else {
console.log(
"Created table. Table description JSON: ",
JSON.stringify(data, null, 2)
);
}
});
node CreateTable.js
1. 이곳에서 샘플 데이터 파일 다운로드
데이터 형태는 아래와 같음
[
{
"year": 2013,
"title": "Rush",
"info": {
"directors": ["Ron Howard"],
"release_date": "2013-09-02T00:00:00Z",
"rating": 8.3,
"genres": [
"Action",
"Biography",
"Drama",
"Sport"
],
"image_url": "http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg",
"plot": "A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda.",
"rank": 2,
"running_time_secs": 7380,
"actors": [
"Daniel Bruhl",
"Chris Hemsworth",
"Olivia Wilde"
]
}
},
...
]
- year 및 title을 Movies 테이블을 위한 기본 키 속성 값으로 사용
- info의 나머지 값들은 info라는 단일 속성에 저장
- JSON을 DynamoDB 속성에 저장
2. 샘플 데이터 Movies 테이블에 로드
// LoadData.js
var AWS = require("aws-sdk");
var fs = require("fs");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient();
console.log("Importing movies info DynamoDB. Please wait.");
var allMovies = JSON.parse(fs.readFileSync("moviedata.json", "utf8"));
allMovies.forEach(function(movie) {
var params = {
TableName: "Moves",
Item: {
year: movie.year,
title: movie.title,
info: movie.info
}
};
docClient.put(params, function(err, data) {
if (err) {
console.error(
"Unable to add movie",
movie.title,
". Error JSON:",
JSON.stringify(err, null, 2)
);
} else {
console.log("PutItem succeeded:", movie.title);
}
});
});
node LoadData.js
// PutItem.js
var AWS = require("aws-sdk");
var fs = require("fs");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient();
var table = "Movies";
var year = 2017;
var title = "The Big Wonny";
var params = {
TableName: table,
Item: {
year: year,
title: title,
info: {
plot: "Nothing happens at all.",
rating: 0
}
}
};
console.log("Adding a new item...");
docClient.put(params, function(err, data) {
if (err) {
console.error(
"Unable to add item. Error JSON:",
JSON.stringify(err, null, 2)
);
} else {
console.log("Added item:", JSON.stringify(data, null, 2));
}
});
node PutItem.js
- 기본 키가 필요하므로 기본 키 (year, title) 및 info 속성 추가
// GetItem.js
var AWS = require("aws-sdk");
var fs = require("fs");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient();
var table = "Movies";
var year = 2017;
var title = "The Big Wonny";
var params = {
TableName: table,
Key: {
year: year,
title: title
}
};
docClient.get(params, function(err, data) {
if (err) {
console.error(
"Unable to read item. Error JSON:",
JSON.stringify(err, null, 2)
);
} else {
console.log("GetItem succeeded:", JSON.stringify(data, null, 2));
}
});
node GetItem.js
// UpdateItem.js
var AWS = require("aws-sdk");
var fs = require("fs");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient();
var table = "Movies";
var year = 2017;
var title = "The Big Wonny";
var params = {
TableName: table,
Key: {
year: year,
title: title
},
UpdateExpression: "set info.rating = :r, info.plot=:p, info.actors=:a",
ExpressionAttributeValues: {
":r": 5.5,
":p": "Everything happens all at once.",
":a": ["Larry", "Moe", "Curly"]
},
ReturnValues: "UPDATED_NEW"
};
console.log("Updating the item...");
docClient.update(params, function(err, data) {
if (err) {
console.error(
"Unable to update item. Error JSON:",
JSON.stringify(err, null, 2)
);
} else {
console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));
}
});
node UpdateItem.js
- 지정된 항목에 대해 수행하고자 하는 모든 업데이트를 설명하기 위해 UpdateExpression을 사용
- ReturnValues 파라미터는 DynamoDB에게 업데이트된 속성("UPDATED_NEW")만 반환하도록 지시
update 메서드를 사용하여 다른 쓰기 요청을 방해하지 않으면서 기존 속성의 값을 증가시키거나 감소시킬 수 있음 (모든 쓰기 요청은 수신된 순서대로 적용)
실행 시 rating 속성이 1씩 증가하는 프로그램
// Increment.js
var AWS = require("aws-sdk");
var fs = require("fs");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient();
var table = "Movies";
var year = 2017;
var title = "The Big Wonny";
// Increment an atomic counter
var params = {
TableName: table,
Key: {
year: year,
title: title
},
UpdateExpression: "set info.rating = info.rating + :val",
ExpressionAttributeValues: {
":val": 1
},
ReturnValues: "UPDATED_NEW"
};
console.log("Updating the item...");
docClient.update(params, function(err, data) {
if (err) {
console.error(
"Unable to update item. Error JSON:",
JSON.stringify(err, null, 2)
);
} else {
console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));
}
});
node Increment.js
UpdateItem을 조건과 함께 사용하는 방법
조건이 true로 평가되면 업데이트가 성공하지만 그렇지 않으면 수행되지 않음
// ConditionalUpdateItem.js
var AWS = require("aws-sdk");
var fs = require("fs");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient();
var table = "Movies";
var year = 2017;
var title = "The Big Wonny";
// Increment an atomic counter
var params = {
TableName: table,
Key: {
year: year,
title: title
},
UpdateExpression: "remove info.actors[0]",
ConditionExpression: "size(info.actors) > :num",
ExpressionAttributeValues: {
":num": 3
},
ReturnValues: "UPDATED_NEW"
};
console.log("Attempting a conditional update...");
docClient.update(params, function(err, data) {
if (err) {
console.error(
"Unable to update item. Error JSON:",
JSON.stringify(err, null, 2)
);
} else {
console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));
}
});
node ConditionalUpdateItem.js
다음과 같이 작성하면 아래와 같은 에러 메시지가 표시 됨
The conditional request failed"
영화에는 3명의 배우가 있는데 배우가 3명보다 많은지를 확인하고 있어 에러가 발생
다음과 같이 수정하면 정상적으로 항목이 업데이트 됨
ConditionExpression: "size(info.actors) >= :num",
// DeleteItem.js
var AWS = require("aws-sdk");
var fs = require("fs");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient();
var table = "Movies";
var year = 2017;
var title = "The Big Wonny";
var params = {
TableName: table,
Key: {
year: year,
title: title
},
ConditionExpression: "info.rating <= :val",
ExpressionAttributeValues: {
":val": 5.0
}
};
console.log("Attempting a conditional delete...");
docClient.delete(params, function(err, data) {
if (err) {
console.error(
"Unable to update item. Error JSON:",
JSON.stringify(err, null, 2)
);
} else {
console.log("DeleteItem succeeded:", JSON.stringify(data, null, 2));
}
});
node DeleteItem.js
다음과 같이 작성하면 아래와 같은 에러 메시지가 표시 됨
The conditional request failed
특정 영화에 대한 평점이 5보다 크기 때문에 에러가 발생
다음과 같이 수정하면 정상적으로 항목이 삭제 됨
var params = {
TableName: table,
Key: {
year: year,
title: title
}
};
- 파티션 키 값을 지정해야 하며, 정렬 키는 선택 사항
- 1년 동안 개봉한 모든 영화를 찾으려면 year만 지정, title을 입력하면 2014년 개봉된 "A"로 시작하는 영화를 검색하는 것과 같이 정렬 키에 대한 어떤 조건을 바탕으로 일부 영화를 검색할 수도 있음
한 해 동안 개봉한 모든 영화
// QueryYear.js
var AWS = require("aws-sdk");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: "Movies",
KeyConditionExpression: "#yr = :yyyy",
ExpressionAttributeNames: {
"#yr": "year"
},
ExpressionAttributeValues: {
":yyyy": 1985
}
};
docClient.query(params, function(err, data) {
if (err) {
console.error("Unable to query. Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("Query succeeded.");
data.Items.forEach(function(item) {
console.log(" -", item.year + ": " + item.title);
});
}
});
node QueryYear.js
ExpressionAttributeNames는 이름을 교체함. 이를 사용하는 이유는 year가 DynamoDB에서 예약어이기 때문. KeyConditionExpression을 포함해 어떤 표현식에서도 사용할 수 없으므로 표현식 속성 이름인 #yr을 사용하여 이를 지칭
ExpressionAttributeValues는 값을 교체함. 이를 사용하는 이유는 KeyConditionExpresssion을 포함해 어떤 표현식에서도 리터럴을 사용할 수 없기 때문. 표현식 속성 값인 :yyyy를 사용해 지칭
* 위의 프로그램은 기본 키 속성으로 테이블을 쿼리하는 방법. DynamoDB에서 1개 이상의 보조 인덱스를 테이블에 생성하여 그 인덱스로 테이블을 쿼리하는 것과 동일한 방식으로 쿼리 작업 가능. 보조 인덱스는 키가 아닌 속성에 대한 쿼리를 허용하여 애플리케이션에 더 많은 유연성을 부여함
한 해 동안 개봉한 모든ㄴ 영화 중에 특정 제목을 지닌 영화
year 1992에 개봉한 영화 중에 title이 "A"부터 "L"까지의 알파벳으로 시작하는 영화를 모두 조회합니다.
// QueryTitle.js
var AWS = require("aws-sdk");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient();
console.log(
"Querying for movies from 1992 - titles A-L, with genres and lead actor"
);
var params = {
TableName: "Movies",
ProjectionExpression: "#yr, title, info.genres, info.actors[0]",
KeyConditionExpression: "#yr = :yyyy and title between :letter1 and :letter2",
ExpressionAttributeNames: {
"#yr": "year"
},
ExpressionAttributeValues: {
":yyyy": 1992,
":letter1": "A",
":letter2": "L"
}
};
docClient.query(params, function(err, data) {
if (err) {
console.error("Unable to query. Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("Query succeeded.");
data.Items.forEach(function(item) {
console.log(
" -",
item.year + ": " + item.title + " ... " + item.info.genres + " ... ",
item.info.actors[0]
);
});
}
});
node QueryTtiel.js
테이블의 모든 항목을 읽고 테이블의 모든 데이터를 반환
선택 사항인 filter_expression을 제공할 수 있으며 그 결과 기준이 일치하는 항목만 반환하지만 필터는 테이블 전체를 스캔한 후에만 적용됨
// Scan.js
var AWS = require("aws-sdk");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: "Movies",
ProjectionExpression: "#yr, title, info.rating",
FilterExpression: "#yr between :start_yr and :end_yr",
ExpressionAttributeNames: {
"#yr": "year"
},
ExpressionAttributeValues: {
":start_yr": 1950,
":end_yr": 1959
}
};
console.log("Scanning Movies table.");
docClient.scan(params, onScan);
function onScan(err, data) {
if (err) {
console.error(
"Unable to scan the table. Error JSON:",
JSON.stringify(err, null, 2)
);
} else {
// print all the movies
console.log("Scan succeeded.");
data.Items.forEach(function(movie) {
console.log(
movie.year + ": ",
movie.title,
"- rating:",
movie.info.rating
);
});
// continue scanning if we have more movies, because
// scan can retrieve a maximum of 1MB of data
if (typeof data.LastEvaluatedKey != "undefined") {
console.log("Scanning for more...");
params.ExclusiveStartKey = data.LastEvaluatedKey;
docClient.scan(params, onScan);
}
}
}
node Scan.js
ProjectionExpression은 스캔 결과에서 원하는 속성만 지정
FilterExpression은 조건을 만족하는 항목만 반환하도록 조건을 지정. 다른 항목들은 모두 무시됨
// DeleteTable.js
var AWS = require("aws-sdk");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var dynamodb = new AWS.DynamoDB();
var params = {
TableName: "Movies"
};
dynamodb.deleteTable(params, function(err, data) {
if (err) {
console.error(
"Unable to delete table. Error JSON:",
JSON.stringify(err, null, 2)
);
} else {
console.log(
"Deleted table. Table description JSON:",
JSON.stringify(data, null, 2)
);
}
});
node DeleteTable.js
#데이터베이스 #DB #개발 #AWS #아마존 #NoSQL