AWSのSNSとDynamoDBをDockerComposeとPython(boto3)を使ってローカルで動かしたことについての備忘録

#はじめに

- こんにちはRIN1208です。今回はAWSのSNSとDynamoDBを触れることになり、先ずはローカルでやってみようとのことでDockerComposeで動かしたのですがいろいろあったので(主にSNS)忘れないため、またこれから触る人の参考になればと思いこの記事を書きました。
- また間違っている箇所がありましたら指摘して頂けると幸いです。

#実行環境
- macos catalia .ver10.15  
  - Docker for Mac

##使用したコンテナ等
### DynamoDB local  
- [Docker Hub](https://hub.docker.com/r/amazon/dynamodb-local)  

### Fake SNS
- 今回はPublishするだけだったのでFake SNSというのを使用しました  
  - [Github](https://github.com/s12v/sns)  
  - [Docker Hub](https://hub.docker.com/r/s12v/sns/)     
 
### boto3 
- [公式ドキュメント(DynamoDB)](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html)
- [公式ドキュメント(SNS)](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sns.html)

# DynamoDBにデータを書き込むコード等  

- docker-compose.yml  
 
```docker-compose.yml
version: "3"
services:
  dynamodb:
    image: amazon/dynamodb-local
    ports:
      - 8000:8000
  app:
    build:
      context: .
    tty: true
    links:
      - dynamodb
```
- Dockerfile  
  - 今回は使うパッケージがboto3だけなのでrequirements.txtは使わずにそのままpip installしています  

```Dockerfile
FROM python:3.6
WORKDIR /
RUN pip install boto3
COPY . /
```
- pythonのコード
  - ここではテーブルの作成、データの書き込み、テーブル一覧の取得をしています  
  - aws_access_key_idとaws_secret_access_keyはdynamodb local
だと適当な文字列で大丈夫です。

```main.py
import boto3

resource = boto3.resource(
    'dynamodb',
    region_name='us-east-1',
    endpoint_url="http://dynamodb:8000/",
    aws_access_key_id='ACCESS_ID',
    aws_secret_access_key='ACCESS_KEY'
)

client = boto3.client(
    'dynamodb',
    region_name='us-east-1',
    endpoint_url="http://dynamodb:8000/",
    aws_access_key_id='ACCESS_ID',
    aws_secret_access_key='ACCESS_KEY'
)

#　以下の処理はテーブルの作成

tab = resource.create_table(
    TableName='atelier',
    KeySchema=[
        {
            'AttributeName': 'id',
            'KeyType': 'HASH'
        }
    ],
    AttributeDefinitions=[
        {
            'AttributeName': 'id',
            'AttributeType': 'N'
        }
    ],
    ProvisionedThroughput={
        'ReadCapacityUnits': 1,
        'WriteCapacityUnits': 1
    }
)


print('Table status:', tab.table_status)

#　以下の処理はデータの書き込み及び、書き込んだデータの取得

table = resource.Table('atelier')

table.put_item(
    Item={
        "id": 1,
        "name": "Ryza",
        "job": "Alchemist"
    }

)

res = table.get_item(Key={'id': 1})

print(res['Item'])

#　以下の処理はテーブル一覧の取得

tables = client.list_tables()

print('Tables List:', tables['TableNames'])
```

## 実行結果
- 実行して以下のように出力されていれば無事処理は成功  

実行コマンド
 
```
docker-compose up -d --build && docker-compose exec app  bash -c "python main.py"
```
以下のような感じで出力されていれば大丈夫です

```
Table status: ACTIVE
{'name': 'Ryza', 'job': 'Alchemist', 'id': Decimal('1')}
Tables List: ['test']
```
詳細は[こちら](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GettingStarted.Python.03.html)を参照してください。


# SNSにPublish
- これが一番苦戦しました

- docker-compose.yml  
 
```docker-compose.yml
version: "3"
services:
  sns:
    image: s12v/sns
    ports:
      - 9911:9911
  app:
    build:
      context: .
    tty: true
    links:
      - sns
```
- Dockerfile  
  - 今回も使うパッケージがboto3だけなのでrequirements.txtは使わずにそのままpip installしています  

```Dockerfile
FROM python:3.6
WORKDIR /
RUN pip install boto3
COPY . /
```
- pythonのコード
  - 今回もaws_access_key_idとaws_secret_access_keyは適当で大丈夫です


```main.py
import boto3

sns = boto3.client('sns', region_name='us-east-1',
                   endpoint_url="http://sns:9911",
                   aws_access_key_id='foo',
                   aws_secret_access_key='bar')
res = sns.create_topic(
    Name='atelier',
)
print(res['TopicArn'])

listTopic = sns.list_topics(
    NextToken=res['TopicArn']
)

print(listTopic)

##-------------------
sns.subscribe(
    TopicArn=res['TopicArn'],
    Protocol='http',
    Endpoint='http',
    ReturnSubscriptionArn=True
)
##-------------------

subscriptions = sns.list_subscriptions_by_topic(
    TopicArn=res['TopicArn']
)

print(subscriptions)

response = sns.publish(
    TopicArn=res['TopicArn'],
    Message='hutomomo yabai'
)

print(response)

```
#実行結果
実行コマンド

```
docker-compose up -d --build && docker-compose exec app  bash -c "python main.py"
```

以下のような感じで出力されていれば大丈夫です

```
{'Topics': [{'TopicArn': 'arn:aws:sns:us-east-1:123456789012:atelier'}], 'ResponseMetadata': {'RequestId': '3206fffa-b256-4100-b005-7d064c305f35', 'HTTPStatusCode': 200, 'HTTPHeaders': {'server': 'akka-http/10.0.10', 'date': 'Thu, 17 Oct 2019 15:25:50 GMT', 'content-type': 'text/xml; charset=UTF-8', 'content-length': '317'}, 'RetryAttempts': 0}}

{'Subscriptions': [{'SubscriptionArn': 'arn:aws:sns:us-east-1:123456789012:atelier:c2e93c42-e7b6-465b-bc71-01ac3b538e2c', 'Owner': '', 'Protocol': 'http', 'Endpoint': 'http', 'TopicArn': 'arn:aws:sns:us-east-1:123456789012:atelier'}], 'ResponseMetadata': {'RequestId': 'e872c714-7152-4233-9ba9-d930ed461ca6', 'HTTPStatusCode': 200, 'HTTPHeaders': {'server': 'akka-http/10.0.10', 'date': 'Thu, 17 Oct 2019 15:25:50 GMT', 'content-type': 'text/xml; charset=UTF-8', 'content-length': '563'}, 'RetryAttempts': 0}}

{'MessageId': 'd523ef9c-dca6-4164-8a75-6bf85b0d9c7a', 'ResponseMetadata': {'RequestId': '057296d4-b0b0-4f96-8153-b55224ca6d17', 'HTTPStatusCode': 200, 'HTTPHeaders': {'server': 'akka-http/10.0.10', 'date': 'Thu, 17 Oct 2019 15:25:50 GMT', 'content-type': 'text/xml; charset=UTF-8', 'content-length': '270'}, 'RetryAttempts': 0}}
```
##躓いたポイント
 上のコードの囲ってある部分で躓いていました  

 
```python
#　この部分
sns.subscribe(
    TopicArn=res['TopicArn'],
    Protocol='http',
    Endpoint='http',
    ReturnSubscriptionArn=True
)
```
 この部分で何をしているかというとsubscriptionを作成してるだけです。  
 ちなみに作成していないと以下のエラーがでます  
 
```
botocore.errorfactory.NotFoundException: An error occurred (NotFound) when calling the Publish operation: Topic not found: arn:aws:sns:us-east-1:123456789012:atelier
```
Topic上で作ってるのになんでないの!?ってなりました。
このエラーについてはgithubの[issue](https://github.com/s12v/sns/issues/44)にも上がっていてこれを見るまで気が付きませんでした.
fakesnsはguiもなく記事も全然なくて...
READMEにサンプルコードかsubscriptionの作成が必要と書いて欲しかった...

#最後に
- こんにちはRIN1208です。この記事でFakeSNSに触れる人の助けになれば幸いです。
- また、この記事を読んでくださりありがとうございます