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

[AWS+Reactアプリ作成入門(Cognito編)](https://qiita.com/sand/items/e1f4ec830d13569a7128 "AWS+Reactアプリ作成入門(Cognito編)")
[AWS+Reactアプリ作成入門(S3編）](https://qiita.com/sand/items/94cd85c3c0426dc768bd "AWS+Reactアプリ作成入門(S3編）")
[AWS+Reactアプリ作成入門(DynamoDB編）](https://qiita.com/sand/items/70018f322d6fe986fe60 "AWS+Reactアプリ作成入門(DynamoDB編）")
[AWS+Reactアプリ作成入門(IAM Role編）](https://qiita.com/sand/items/a07a08f7c1ccc2e8b967 "AWS+Reactアプリ作成入門(IAM Role編）")
[AWS+Reactアプリ作成入門(ログイン後のAdmin編)](https://qiita.com/sand/items/260a6a0b360e574642cf "AWS+Reactアプリ作成入門(ログイン後のAdmin編)")


今回作成したアプリ ==>[久喜SNS](http://kuki-app-bucket.s3-website-ap-northeast-1.amazonaws.com/ "久喜SNS")

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

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


# １．投稿のpost

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

```js: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はコメントで示した通りです。これは検索に使われますが、以下に説明します。



# ２．投稿の検索

　このテーブルは、トップ画面で最新投稿を検索するのと、管理画面で自分の最新投稿を検索する２つの種類の検索があります。どちらも最新順のリストを取得します。

**１. 管理画面で自分の最新順の投稿リストを検索**

```
primary indexを以下のキーで作成
partition key : identityId　（文字列） 
sort key : uploadTime　（数値）
```
　identityIdはユーザIDとして使っているもので、Cognitoでのログイン時にAWS.config.credentials.identityIdに値が設定されるものです。uploadTimeは投稿時間でunixtimeです。


**２. トップ画面で最新順の投稿リストを検索**

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


```js: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　を指定しています。


次に。「**２. トップ画面で最新順の投稿リストを検索**」をみてみます。
以下はIndexNameを指定しsecondary indexを使います。partition keyがpartitionYearなので、KeyConditionExpressionでEqual条件で絞り込みます。

 ```js:src/views/Home.jsの一部
 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);
          });
      }
  });
 ```


# ３．投稿削除

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

```js: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()で投稿を削除しています。


# ４．投稿編集

　編集は、古いものを削除して新しいものを挿入する、という考えで実装しています。上の削除のコードで、削除が成功した時にaddCallback()を呼んでいるのがそれに当たります。単に削除だけを行いたい場合はaddCallback=nullとしてこの関数を呼びます。

# ５．カウンター
　投稿のページにはそれぞれカウンターを設け参照数をカウントしています。投稿のpostで示したコードのrefCounterがそれに当たります。今回はReactのCounterコンポーネントを作成し、参照されるごとにアトミックにインクリメントするコードを書きました。

```js: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について述べたいと思います。