JavaScript
AWS
DynamoDB
クラウド
React

AWS+Reactアプリ作成入門(DynamoDB編)

AWS+Reactアプリ作成入門(Cognito編)
AWS+Reactアプリ作成入門(S3編)
AWS+Reactアプリ作成入門(DynamoDB編)
AWS+Reactアプリ作成入門(IAM Role編)
AWS+Reactアプリ作成入門(ログイン後のAdmin編)

今回作成したアプリ ==>久喜SNS

 DynamoDBはAWSが提供するDBサービスです。MongoDBと比べるとちょっと癖がありますし、私も全体的な理解が十分ではないので、説明は今回アプリの利用に限定することとして、断定的に記述していきたいと思います。偏見や偏りがあるかもしれないという事ですが、ご容赦ください。

  1. テーブルの検索はqueryとscanがあります。
  2. scanは無条件に全テーブルをサーチしコストが高いので使わないことにします。
  3. queryはindexに対して検索条件を与えて検索するものです。
  4. テーブル作成時にprimary indexを設定するのが必須です
  5. indexキーは2つの項目を指定します。partition key とsort keyと呼びます(partition keyのみでもok)
  6. partition keyはテーブルを分割して、検索時に分割された領域だけをサーチするようにする、イメージですかね。
  7. query検索時にはpartition keyを指定して(equal条件)、sort keyでフィルタします。
  8. query検索時は自動的にsort keyでソートされます。
  9. primary keyでないものを条件として検索したい時は、secondary indexを明示的に作成します(課金される)
  10. secondary indexでも検索したい条件に合わせて、partition key とsort keyを定義します。

1.投稿のpost

 以下のようなコードで、画像掲示板のテーブルへを投稿します。

src/views/Admin.jsの一部
    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 (数値)

 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)毎の最新記事をテーブルから取得するコードを示します。

src/views/Admin.jsの一部
    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 を指定しています。

3.投稿削除

 以下のようなコードで、画像掲示板のテーブルから投稿を削除します。

src/views/Admin.jsの一部
    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コンポーネントを作成し、参照されるごとにアトミックにインクリメントするコードを書きました。

src/views/Counter.js
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について述べたいと思います。