AWS+Reactアプリ作成入門(Cognito編)
AWS+Reactアプリ作成入門(S3編)
AWS+Reactアプリ作成入門(DynamoDB編)
AWS+Reactアプリ作成入門(IAM Role編)
AWS+Reactアプリ作成入門(ログイン後のAdmin編)
今回作成したアプリ ==>久喜SNS
DynamoDBはAWSが提供するDBサービスです。MongoDBと比べるとちょっと癖がありますし、私も全体的な理解が十分ではないので、説明は今回アプリの利用に限定することとして、断定的に記述していきたいと思います。偏見や偏りがあるかもしれないという事ですが、ご容赦ください。
- テーブルの検索はqueryとscanがあります。
- scanは無条件に全テーブルをサーチしコストが高いので使わないことにします。<== 7. の制限があるからindexキーの設計によってはscanも必要かな?
- queryはindexに対して検索条件を与えて検索するものです。
- テーブル作成時にprimary indexを設定するのが必須です
- indexキーは2つの項目を指定します。partition key とsort keyと呼びます(partition keyのみでもok)
- partition keyはテーブルを分割して、検索時に分割された領域だけをサーチするようにする、イメージですかね。
- query検索時にはpartition keyを指定して(equal条件)、sort keyでフィルタします。 <==partition keyはequal条件でないとダメ、それ以外を指定するとエラーで嵌ります。
- query検索時は自動的にsort keyでソートされます。
- primary keyでないものを条件として検索したい時は、secondary indexを明示的に作成します(課金される)
- secondary indexでも検索したい条件に合わせて、partition key とsort keyを定義します。
1.投稿のpost
以下のようなコードで、画像掲示板のテーブルへを投稿します。
var docClient = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: tablename,
Item:{
identityId: identityId, // ★prime partition key
email: _self.state.email,
username: _self.state.username,
filename: filepath,
thumbnail: thumbnail,
type: fileType,
title: title,
story: story,
imageOverwrite: _self.state.imageOverwrite,
mapUse: _self.state.mapUse,
position: _self.state.position,
uploadTime: uploadTime, // ★prime & secondary sort key
uploadDate: uploadDate,
partitionYear: partitionYear, //★secondary partition key
refCounter: 0
}
};
docClient.put(params, function(err, data) {
if(err) {
console.log("Err: table put :" +err);
} else {
console.log("Success: table put ok");
}
});
docClient.put()で投稿をテーブルに挿入します。primary keyとsecondary keyはコメントで示した通りです。これは検索に使われますが、以下に説明します。
2.投稿の検索
このテーブルは、トップ画面で最新投稿を検索するのと、管理画面で自分の最新投稿を検索する2つの種類の検索があります。どちらも最新順のリストを取得します。
1. 管理画面で自分の最新順の投稿リストを検索
primary indexを以下のキーで作成
partition key : identityId (文字列)
sort key : uploadTime (数値)
identityIdはユーザIDとして使っているもので、Cognitoでのログイン時にAWS.config.credentials.identityIdに値が設定されるものです。uploadTimeは投稿時間でunixtimeです。
2. トップ画面で最新順の投稿リストを検索
secondary indexを以下のキーで作成
partition key: partitionYear (数値)
sort key : uploadTime (数値)
まず「1. 管理画面で自分の最新順の投稿リストを検索」をみてみます。partitionYearはuploadTimeを年数字に変換したものです。2017とかの数字です。uploadTimeは投稿時間です。このpartitionYearというpartition keyは年によってテーブルを分割するものです。最初はテーブルは2017しかありませんが、来年以降2018,2019,2020..と分割されていきます。検索時にpartitionYear=2018と指定したら、2018の部分だけを検索してくれます。大雑把すぎる場合は年月を指定して201805とか指定するようにpartition keyの定義を変更すれば良いと思われます。
ちなみに昔はAWS.DynamoDB()が使われていたようですが、検索結果に不要の型(SとかNとか)が含まれとても使いにくいので、ここではAWS.DynamoDB.DocumentClient()を使っています。
以下にAdminページでユーザID(identityId)毎の最新記事をテーブルから取得するコードを示します。
var dynamo = new AWS.DynamoDB.DocumentClient();
var param = {
TableName : tablename,
ScanIndexForward: false, //queryには効くが、scanには効かない
KeyConditionExpression : "identityId = :identityId",
ExpressionAttributeValues : {":identityId" : identityId}
};
dynamo.query(param, function(err, data) {
if (err) {
console.log("### Error="+err);
} else {
//console.log("### data="+JSON.stringify(data.Items));
_self.setState({items: data.Items});
}
});
KeyConditionExpressionではpartition keyを指定しているだけですが、sort keyの条件を加えて、検索結果を絞り込むことが可能です。また取得は暗黙的にsort keyでソートされますが、デフォルトで昇順になってしまいます。ここでは最新順で降順なのです、ScanIndexForward: false を指定しています。
次に。「2. トップ画面で最新順の投稿リストを検索」をみてみます。
以下はIndexNameを指定しsecondary indexを使います。partition keyがpartitionYearなので、KeyConditionExpressionでEqual条件で絞り込みます。
var param = {
TableName : tablename,
IndexName: "partitionYear-uploadTime-index",
ScanIndexForward: false, //queryには効くが、scanには効かない
ExpressionAttributeNames : {'#k' : 'partitionYear'},
ExpressionAttributeValues : {':partitionYear' : 2017},
KeyConditionExpression : '#k = :partitionYear',
Limit: 20
};
dynamo.query(param, function(err, data) {
if (err) {
console.log("### Error="+err);
} else {
_self.setState({items: data.Items});
//--- cache update
var params = {
Bucket: bucketname,
Key: cachepath,
ContentType: 'application/json',
Body: JSON.stringify(data.Items)
};
s3.putObject(params, function(err, data) {
if (err) console.log("### cache upload error",err, err.stack);
else console.log("### cache upload successful"+data);
});
}
});
3.投稿削除
以下のようなコードで、画像掲示板のテーブルから投稿を削除します。
const docClient = new AWS.DynamoDB.DocumentClient();
const params3 = {
TableName: tablename,
Key: {
identityId: item.identityId, // ★partition key
uploadTime: item.uploadTime // ★sort key
}
};
docClient.delete(params3, function (err, res) {
if (err) {
console.log("### delete table err:"+err); // an error occurred
} else{
console.log("### delete table ok"); // successful response
if(addCallback) { //編集 => 削除 then 追加
addCallback();
}
}
});
partition keyとsort keyを指定し、docClient.delete()で投稿を削除しています。
4.投稿編集
編集は、古いものを削除して新しいものを挿入する、という考えで実装しています。上の削除のコードで、削除が成功した時にaddCallback()を呼んでいるのがそれに当たります。単に削除だけを行いたい場合はaddCallback=nullとしてこの関数を呼びます。
5.カウンター
投稿のページにはそれぞれカウンターを設け参照数をカウントしています。投稿のpostで示したコードのrefCounterがそれに当たります。今回はReactのCounterコンポーネントを作成し、参照されるごとにアトミックにインクリメントするコードを書きました。
import React from 'react';
import AWS from "aws-sdk";
import appConfig from '../appConfig';
import {bucketname,tablename} from '../appConfig';
//http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.NodeJs.03.html
export default class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
}
componentWillMount() {
const _self=this;
const dynamo = new AWS.DynamoDB.DocumentClient();
var params = {
TableName:tablename,
Key:{
"identityId": this.props.identityId,
"uploadTime": this.props.uploadTime
},
UpdateExpression: "set refCounter = refCounter + :val",
ExpressionAttributeValues:{
":val":1
},
ReturnValues:"UPDATED_NEW"
};
console.log("Updating the item...");
dynamo.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));
_self.setState( {counter: data.Attributes.refCounter} );
}
});
}
render () {
return (
<div>{this.state.counter}</div>
);
}
}
Counterコンポーネントは親コンポーネントからidentityIdとuploadTimeを渡され、this.propsで参照しています。カウンターは dynamo.update()でアトミックにインクリメントされます。
今回はこれで終わりです。次回以降にIAMのRoleについて述べたいと思います。