C++初心者がMacでAWS SDK for C++を利用してAmazon DynamoDBにアクセスする

C++でAmazon DynamoDBへアクセスするのにはどうしたら良いのか、いろいろと調べて実行できるところまでできました。


## 前提条件

### Dockerがインストール済み

```sh
> docker --version
Docker version 18.09.1, build 4c52b90
```

### AWS-CLIがインストール済み

```sh
> aws --version
aws-cli/1.16.27 Python/3.6.6 Darwin/17.7.0 botocore/1.12.17
```

### その他環境

```sh
> sw_vers
ProductName:    Mac OS X
ProductVersion: 10.13.6
BuildVersion:   17G4015

> g++ -v
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 10.0.0 (clang-1000.10.44.4)
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

> gcc -v
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 10.0.0 (clang-1000.10.44.4)
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
```

## DynamoDBの準備(DynamoDB Localを利用)

下記を参考にさせていただき、DynamoDB Localを利用することにしました。
AWS SDK for C++も利用されていたのですが、SDKのビルドも試してみたかったので、今回はそちらは見送りました。

dockerでDynamoDB LocalとAWS SDK for C++の開発環境を動かす - Qiita
https://qiita.com/d9magai/items/8b5650656783ae8af1d5


### DynamoDB Localコンテナを立ち上げる

Dockerイメージからコンテナを立ち上げます。

```sh
> mkdir 任意のディレクトリ
> cd 任意のディレクトリ
> mkdir dynamodblocal
> docker run -d \
  -p 8000:8000 \
  -v dynamodblocal:/var/dynamodblocal \
  --name localdynamodb \
  -t tray/dynamodb-local  \
  -sharedDb  \
  -dbPath /var/dynamodblocal
```

#### `-sharedDb`オプションを指定しないとハマるそうです

気をつけましょう。
>丁度よさげなイメージがこちらにありましたので、これを使って動かします。その際に-sharedDbを指定し、リージョン区分のエミュレート機能を無効にします。個人的にはこの機能はハマりポイントだと思うので(というかハマりました)無効にして単一のDBが作られるようにします。


コンテナが動作しているか確認します。

```sh
> docker ps
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                 NAMES
efa64bb35f6d        tray/dynamodb-local   "/opt/jdk/bin/java -…"   9 seconds ago       Up 7 seconds        0.0.0.0:8000->8000/tcp   localdynamodb
```

### DynamoDB Localにテーブルを作成する

コンテナが立ち上がっていることが確認できたので、テーブルを作成します。
テーブル名などは任意でどうぞ。

```sh
> aws dynamodb create-table \
  --endpoint-url http://localhost:8000 \
  --table-name Music \
  --attribute-definitions AttributeName=Artist,AttributeType=S \
                          AttributeName=SongTitle,AttributeType=S \
  --key-schema AttributeName=Artist,KeyType=HASH AttributeName=SongTitle,KeyType=RANGE \
  --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1

{
    "TableDescription": {
        "AttributeDefinitions": [
            {
                "AttributeName": "Artist",
                "AttributeType": "S"
            },
            {
                "AttributeName": "SongTitle",
                "AttributeType": "S"
            }
        ],
        "TableName": "Music",
        "KeySchema": [
            {
                "AttributeName": "Artist",
                "KeyType": "HASH"
            },
            {
                "AttributeName": "SongTitle",
                "KeyType": "RANGE"
            }
        ],
        "TableStatus": "ACTIVE",
        "CreationDateTime": 1548140414.002,
        "ProvisionedThroughput": {
            "LastIncreaseDateTime": 0.0,
            "LastDecreaseDateTime": 0.0,
            "NumberOfDecreasesToday": 0,
            "ReadCapacityUnits": 1,
            "WriteCapacityUnits": 1
        },
        "TableSizeBytes": 32,
        "ItemCount": 1,
        "TableArn": "arn:aws:dynamodb:ddblocal:000000000000:table/Music"
    }
}
```

テーブルが作成できたかを確認します。

```sh
> aws dynamodb list-tables \
  --endpoint-url http://localhost:8000

{
    "TableNames": [
        "Music"
    ]
}
```

一応テーブルの中身も確認しておきます。

```sh
> aws dynamodb scan \
  --table-name Music \
  --endpoint-url http://localhost:8000

{
    "Items": [],
    "Count": 0,
    "ScannedCount": 0,
    "ConsumedCapacity": null
}
```

はい。まだ空っぽです。
これでDynamoDBの準備ができました。


## AWS SDK for C++のインストール

C++でDynamoDBへアクセスするのにはどうしたら良いのか調べたらすぐにSDKが存在していることがわかったのでそれを利用します。

aws/aws-sdk-cpp: AWS SDK for C++
https://github.com/aws/aws-sdk-cpp


ビルド方法は下記ドキュメントの[Building the SDK from Source]を参考にしました。

Setting Up the AWS SDK for C++ - AWS SDK for C++
https://docs.aws.amazon.com/ja_jp/sdk-for-cpp/v1/developer-guide/setup.html

リポジトリを取得します。

```sh
> cd 任意のディレクトリ
> git clone https://github.com/aws/aws-sdk-cpp.git
```

CMakeでビルド準備？(MakeFileを作成)します。
CMakeがインストールされていない場合、下記でインストールできます。

```sh:CMakeのインストール
> brew install cmake
```

`-DBUILD_ONLY`を指定しないとSDK全体がビルドされるので、とても時間がかかります(ましたorz)のでご注意ください。


複数サービスを指定する場合は、セミコロン`;`区切りとなります。CMakeで指定できるパラメータについては下記に詳しくあります。

CMake Parameters - AWS SDK for C++
https://docs.aws.amazon.com/ja_jp/sdk-for-cpp/v1/developer-guide/cmake-params.html

```sh
> cd 任意のディレクトリ
> mkdir aws-sdk-cpp-build
> cd aws-sdk-cpp-build
> cmake -DBUILD_ONLY="dynamodb" ../aws-sdk-cpp

-- Found Git: /usr/local/bin/git (found version "2.19.1")
-- TARGET_ARCH not specified; inferring host OS to be platform compilation target
-- Building AWS libraries as shared objects
-- Building project version: 1.7.39
-- The C compiler identification is AppleClang 10.0.0.10001044
-- The CXX compiler identification is AppleClang 10.0.0.10001044
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
(略)
-- Configuring done
-- Generating done
-- Build files have been written to: 任意のディレクトリ/aws-sdk-cpp-build
```

ソースファイルと別のディレクトリでビルドすることをout-of-sourceビルドというそうです。へぇ〜

CMake : out-of-sourceビルドで幸せになる - Qiita
https://qiita.com/osamu0329/items/7de2b190df3cfb4ad0ca


Makefileが作成されたら`make && make install`を実行します。
サービスを限定していてもそこそこ時間がかかりますので、休憩前にでもどうぞ。

```sh
> ls

AWSSDK                                 aws-cpp-sdk-dynamodb
CMakeCache.txt                         aws-cpp-sdk-dynamodb-integration-tests
CMakeFiles                             aws-sdk-cpp-config.cmake
Makefile                               cmake_install.cmake
aws-cpp-sdk-core                       testing-resources
aws-cpp-sdk-core-tests


> make

Scanning dependencies of target aws-cpp-sdk-core
[  0%] Building CXX object aws-cpp-sdk-core/CMakeFiles/aws-cpp-sdk-core.dir/source/AmazonSerializableWebServiceRequest.cpp.o
[  0%] Building CXX object aws-cpp-sdk-core/CMakeFiles/aws-cpp-sdk-core.dir/source/AmazonStreamingWebServiceRequest.cpp.o
(略)
[----------] Global test environment tear-down
[==========] 305 tests from 49 test cases ran. (2723 ms total)
[  PASSED  ] 305 tests.
[100%] Built target aws-cpp-sdk-core-tests


> make install

[ 28%] Built target aws-cpp-sdk-core
[ 84%] Built target aws-cpp-sdk-dynamodb
[ 86%] Built target testing-resources
[ 87%] Built target aws-cpp-sdk-dynamodb-integration-tests
[100%] Built target aws-cpp-sdk-core-tests
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/lib/cmake/AWSSDK/AWSSDKConfigVersion.cmake
-- Installing: /usr/local/lib/cmake/AWSSDK/platformDeps.cmake
-- Up-to-date: /usr/local/lib/cmake/AWSSDK
(略)
-- Installing: /usr/local/lib/cmake/testing-resources/testing-resources-config.cmake
-- Installing: /usr/local/lib/cmake/testing-resources/testing-resources-config-version.cmake
```

これでAWS SDK for C++が利用できるようになりました(なったはずです)。


## C++で実装

C++初心者なので、まだ自前で実装ができません。
ですので、公式にあった下記のサンプルを利用させてもらいます。

C++ Code Samples for Amazon DynamoDB - AWS Code Sample
https://docs.aws.amazon.com/ja_jp/code-samples/latest/catalog/code-catalog-cpp-example_code-dynamodb.html

```sh
> cd 任意のディレクトリ
> touch put_item.cpp
> touch CMakeLists.txt
```

### テーブルにデータをPUTする実装

put_item.cpp - AWS Code Sample
https://docs.aws.amazon.com/ja_jp/code-samples/latest/catalog/cpp-dynamodb-put_item.cpp.html

サンプルソースをほぼそのまま利用しますが、DynamoDB localへアクセスするように`clientConfig.endpointOverride = "http://localhost:8000";`を追加しています。

```cpp:put_item.cpp
/*
Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.

This file is licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License. A copy of
the License is located at

http://aws.amazon.com/apache2.0/

This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#include <aws/core/Aws.h>
#include <aws/core/utils/Outcome.h>
#include <aws/dynamodb/DynamoDBClient.h>
#include <aws/dynamodb/model/AttributeDefinition.h>
#include <aws/dynamodb/model/PutItemRequest.h>
#include <aws/dynamodb/model/PutItemResult.h>
#include <iostream>


/**
* Put an item in a DynamoDB table.
*
* Takes the name of the table, a name (primary key value) and a greeting
* (associated with the key value).
*
* This code expects that you have AWS credentials set up per:
* http://docs.aws.amazon.com/sdk-for-cpp/v1/developer-guide/credentials.html
*/
int main(int argc, char** argv)
{
    const std::string USAGE = \
        "Usage:\n"
        "    put_item <table> <name> [field=value ...]\n\n"
        "Where:\n"
        "    table    - the table to put the item in.\n"
        "    name     - a name to add to the table. If the name already\n"
        "               exists, its entry will be updated.\n\n"
        "Additional fields can be added by appending them to the end of the\n"
        "input.\n\n"
        "Example:\n"
        "    put_item Cellists Pau Language=ca Born=1876\n";

    if (argc < 2)
    {
        std::cout << USAGE;
        return 1;
    }

    Aws::SDKOptions options;

    Aws::InitAPI(options);
    {
        const Aws::String table(argv[1]);
        const Aws::String name(argv[2]);

        Aws::Client::ClientConfiguration clientConfig;
        clientConfig.endpointOverride = "http://localhost:8000";
        Aws::DynamoDB::DynamoDBClient dynamoClient(clientConfig);

        Aws::DynamoDB::Model::PutItemRequest pir;
        pir.SetTableName(table);

        Aws::DynamoDB::Model::AttributeValue av;
        av.SetS(name);
        pir.AddItem("Name", av);

        for (int x = 3; x < argc; x++)
        {
            const Aws::String arg(argv[x]);
            const Aws::Vector<Aws::String>& flds = Aws::Utils::StringUtils::Split(arg, ':');
            if (flds.size() == 2)
            {
                Aws::DynamoDB::Model::AttributeValue val;
                val.SetS(flds[1]);
                pir.AddItem(flds[0], val);
            }
            else
            {
                std::cout << "Invalid argument: " << arg << std::endl << USAGE;
                return 1;
            }
        }

        const Aws::DynamoDB::Model::PutItemOutcome result = dynamoClient.PutItem(pir);
        if (!result.IsSuccess())
        {
            std::cout << result.GetError().GetMessage() << std::endl;
            return 1;
        }
        std::cout << "Done!" << std::endl;
    }
    Aws::ShutdownAPI(options);
    return 0;
}
```

### CMakeListsの定義

CMakeLists.txt - AWS Code Sample
https://docs.aws.amazon.com/ja_jp/code-samples/latest/catalog/cpp-dynamodb-CMakeLists.txt.html

とりあえずPUTのサンプル実装のみ含めたいので、余分なコードをコメントアウトしています。

```txt:CMakeLists.txt
# Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# This file is licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License. A copy of
# the License is located at
# http://aws.amazon.com/apache2.0/
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.

cmake_minimum_required(VERSION 3.2)
project(dynamodb-examples)
set (CMAKE_CXX_STANDARD 11)

# Locate the aws sdk for c++ package.
find_package(AWSSDK REQUIRED COMPONENTS dynamodb)


set(EXAMPLES "")
#list(APPEND EXAMPLES "batch_get_item")
#list(APPEND EXAMPLES "create_table")
#list(APPEND EXAMPLES "create_table_composite_key")
#list(APPEND EXAMPLES "delete_item")
#list(APPEND EXAMPLES "delete_table")
#list(APPEND EXAMPLES "describe_table")
#list(APPEND EXAMPLES "get_item")
#list(APPEND EXAMPLES "list_tables")
list(APPEND EXAMPLES "put_item")
#list(APPEND EXAMPLES "update_item")
#list(APPEND EXAMPLES "update_table")


# The executables to build.
foreach(EXAMPLE IN LISTS EXAMPLES)
    add_executable(${EXAMPLE} ${EXAMPLE}.cpp)
    target_link_libraries(${EXAMPLE} ${AWSSDK_LINK_LIBRARIES})
endforeach()
```

## ビルドして実行する

実装の準備ができたら、ビルドして実行してみます。

```sh
> mkdir 任意のディレクトリ/build
> cd 任意のディレクトリ/build
> cmake ../

-- Found AWS SDK for C++, Version: 1.7.39, Install Root:/usr/local, Platform Prefix:, Platform Dependent Libraries: pthread;curl
-- Components specified for AWSSDK: dynamodb
-- Try finding aws-cpp-sdk-core
-- Found aws-cpp-sdk-core
-- Try finding aws-cpp-sdk-dynamodb
-- Found aws-cpp-sdk-dynamodb
-- Configuring done
-- Generating done
-- Build files have been written to: 任意のディレクトリ/build

> make

Scanning dependencies of target put_item
[ 50%] Building CXX object CMakeFiles/put_item.dir/put_item.cpp.o
[100%] Linking CXX executable put_item
ld: warning: text-based stub file /System/Library/Frameworks//CoreFoundation.framework/CoreFoundation.tbd and library file /System/Library/Frameworks//CoreFoundation.framework/CoreFoundation are out of sync. Falling back to library file for linking.
[100%] Built target put_item
```

ワーニングが出力されましたが、捨て置きます。
下記あたりが参考になるかもです。(未確認)

r - ld: warning: text-based stub file are out of sync. Falling back to library file for linking - Stack Overflow
https://stackoverflow.com/questions/51314888/ld-warning-text-based-stub-file-are-out-of-sync-falling-back-to-library-file


無事にビルドできたら実行してみます。

```sh
> ./put_item

Usage:
    put_item <table> <name> [field=value ...]

Where:
    table    - the table to put the item in.
    name     - a name to add to the table. If the name already
               exists, its entry will be updated.

Additional fields can be added by appending them to the end of the
input.

Example:
    put_item Cellists Pau Language=ca Born=1876
```

実行できたので、パラメータを指定してDynamoDB LocalのテーブルにPUTできるか確認します。

```sh
> ./put_item Music Hoge Artist=hoge SongTitle="hoge hoge"
Invalid argument: Artist=hoge
Usage:
(略)
```

`Invalid argument`でエラーとなりました。むむ。
実装をよくよく確認すると`const Aws::Vector<Aws::String>& flds = Aws::Utils::StringUtils::Split(arg, ':');`とあり、パラメータの区切り文字がコロン`:`という罠でした。うぉぉ〜ぃ


パラメータ指定を見直して実行しなおします。

```sh
> ./put_item Music Hoge Artist:hoge SongTitle:"hoge hoge"

Done!
```

はい。Done!
実際にPUTされたか確認します。

```sh
> aws dynamodb scan \
  --table-name Music \
  --endpoint-url http://localhost:8000

{
    "Items": [
        {
            "Artist": {
                "S": "hoge"
            },
            "Name": {
                "S": "Hoge"
            },
            "SongTitle": {
                "S": "hoge hoge"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}
```

やったぜ。
~~それにしてもやっぱりDynamoDBから返されるJSONそのままだとツラミ~~


## おまけ(まだ良くわかっていない点)

今回はAWS SDK for C++を`make install`して`/usr/local`配下へインストールして利用しました。そうするとAWS SDK for C++がインストールされていない環境では実行できません。

```sh
> cd 任意のディレクトリ/aws-sdk-cpp-build
> make uninstall
(略)
> cd ../build
> ./put_item Music Hoge Artist:hoge SongTitle:"hoge hoge"
dyld: Library not loaded: @rpath/libaws-cpp-sdk-dynamodb.dylib
  Referenced from: 任意のディレクトリ/build/./put_item
  Reason: image not found
fish: './put_item Music Hoge Artist:ho…' terminated by signal SIGABRT (Abort)
```

まだわかっていませんが、本来であれば、自前の実装にSDKが含まれるようにCMakeLists.txtで定義してやる必要がある？のでしょうか？


## まとめ

C++で外部ライブラリをどう利用するのが良いのかわからないままに試してみましたが、とりあえずAWS SDK for C++を利用してAmazon DynamoDB(Local)へアクセスすることができました。


## 参考

dockerでDynamoDB LocalとAWS SDK for C++の開発環境を動かす - Qiita
https://qiita.com/d9magai/items/8b5650656783ae8af1d5

aws/aws-sdk-cpp: AWS SDK for C++
https://github.com/aws/aws-sdk-cpp

Setting Up the AWS SDK for C++ - AWS SDK for C++
https://docs.aws.amazon.com/ja_jp/sdk-for-cpp/v1/developer-guide/setup.html

C++ Code Samples for Amazon DynamoDB - AWS Code Sample
https://docs.aws.amazon.com/ja_jp/code-samples/latest/catalog/code-catalog-cpp-example_code-dynamodb.html