AWS SDK for Python(Boto3)ではClient APIよりResource APIを使おう

# はじめに
この記事は[AWS初心者 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/aws_beginner)
の14日目の記事です。
もし誤りがあれば指摘してもらえると幸いです。

# 要約
Boto3の中ではサービスによってリソースAPI(boto3.reosurce('サービス名')で呼び出すもの)があります。リソースAPIの方がクライアントAPIより抽象化されていて不要な情報を書かずに実装できるので、それぞれのAPIで同じことができる場合はリソースAPIを優先して使用した方が良いです。

# 記事全体の構成
まずBoto3、クライアントAPI、リソースAPIとは何だったのかを再確認します。
その後に両方のAPIを使用した場合にSQS、S3、DynamoDBの使用パターンを１ケースずつとりあげて比較してどう違うのかを具体的に見てみます。
最後に現在リソースAPIが提供されているサービスの一覧と所感を書いてます。

# Boto3とは
Pythonに提供されているAWSのSDKで、コードからAWSの各種サービス（EC2やDynamoDB、S3など）に接続する場合に使用します。
APIのリファレンスは以下の資料に書かれています。
[Boto 3 Documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)

APIには主に低レベルなAPIであるクライアントAPIと高レベルなAPIであるリソースAPIがあります。具体的には以下のような記述で呼び出されます。

```python
# Client API
boto3.client('sqs')

# Resource API
boto3.resource('sqs')
```

次にそれぞれのAPIにどんな特性があるか確認します。

# クライアントAPI（低レベルAPI）
AWSのサービスで提供しているHTTP APIと１対１に対応するメソッドです。HTTP API
と完全にマッピングしているので、APIで可能な操作はすべてできるようになっています。ただ汎用的な設定になっているので、APIに設定するパラメータをメソッドに直接する必要があります。
以下はSQSのsend_messageを実行する例です。APIで使用するQueueUrlなどを指定しています。

```python
import boto3

# SQS クライアントAPI版
sqs = boto3.client('sqs')

response = sqs.send_message(QueueUrl='https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue', MessageBody='...')
```

参考元：Boto3のClient APIドキュメント(send_message)
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.send_message
※「QueueUrl='...'」の部分はURLの記述を追加

# リソースAPI（高レベルAPI）
クライアントAPIと比べて高レベルで抽象化したAPIです。APIとの直接マッピングではなく、抽象化したクラスを間に挟むことでより開発者にとって扱いやすくなっています。
ここでは例として、クライアントAPIと同じようにリソースAPIでsend_messageを行う例を記載します。

```python
queueName='target-queue-name'

# SQS リソースAPI版
sqs = boto3.resource('sqs')

response = sqs.get_queue_by_name
                QueueName = queueName
            ).send_message(
                MessageBody = '...'
            )
```

参考元：「Boto3(Python)で"Service Resource"を使ってみた(Lambda)」
https://cloudpack.media/16114

クライアントAPIと比較するとコード内にSQSのURLを記載せず、queue名の問い合わせからメッセージの送信が書けます。いちいちキューのURLを意識する必要がなくなるので開発者はキューの名前さえ記述すればよくなります。


# クライアントAPIとリソースAPIの比較（S3）
次にS3のクライアントAPIとリソースAPIを比較します。
例としてバケットからファイル名を取得するコードを記述します。

```python
# 共通定数(バケットとS3内ファイルのプレフィックス)
BUCKET_NAME= "xxx-bucket"
S3_PREFIX = "image-file-done/"
```
```python
# S3 クライアントAPI版
s3_client = boto3.client('s3')
# 戻り値の型：<class 'dict'>
s3_objects = s3_client.list_objects_v2(Bucket=BUCKET_NAME, Prefix=S3_PREFIX)

for filename in s3_objects['Contents']:
    print('client:'+filename['Key'])

```

```python
# S3 リソースAPI版
s3_resource = boto3.resource('s3')
# 戻り値の型：<class 'boto3.resources.collection.s3.Bucket.objectsCollection'>
s3_objects = s3_resource.Bucket(BUCKET_NAME).objects.filter(Prefix=S3_PREFIX)

for filename in s3_objects:
    print('resource:'+filename.key)
```

呼び出しまではほとんど変わりませんが、呼び出し後の型が異なります。
クライアントAPIで呼び出した場合は、辞書型で返ってくるので実装のたびに返却される辞書型の形式を意識したデータの取り出しが必要になります。リソースAPIであればboto3用のオブジェクトになっているので、他のサービスでリソースAPIを使用する場合でも似たような記述でコードが書けます。

# クライアントAPIとリソースAPIの比較（DynamoDB）
最後にDynamoDBのクライアントAPIとリソースAPIを比較します。
こちらは特にBoto3のドキュメントがよく分からなくて、他の記述を参考にして間違ってコピーしてしまうことが多そうです。（昔の自分のことです…）

```python
# 共通定数(テーブル名とハッシュキー名、ソートキー名)
TABLE_NAME='XXXXX_IFO'
HASH_KEY_NAME='XXXXX_CODE'
SORT_KEY_NAME='DATE_TIME'
```

```python
# DynamoDB クライアント版
dynamodb_client = boto3.client('dynamodb')
# 戻り値の型: <class 'dict'>
response = dynamodb_client.get_item(
    TableName=TABLE_NAME,
    Key={
        HASH_KEY_NAME:{
            'S': '54620100'  
        },
        SORT_KEY_NAME:{
            'S': '2019050621'
        }
    }
)
```

```python
# DynamoDB リソース API
dynamodb_resource = boto3.resource('dynamodb')
table = dynamodb_resource.Table(TABLE_NAME)
# 戻り値の型: <class 'dict'>
response = table.get_item(
    Key={
        HASH_KEY_NAME: '54620100',
        SORT_KEY_NAME: '2019050621'
    }
)
```

久々にクライアントAPIで書きましたがパラメータをCLIと同じ形式で書くので少ししんどいです。戻り値の型自体はどちらも辞書型ですが、テーブル内部の操作をTableオブジェクトからでき、Key項目の記述は型情報を逐一書かなくて済むのでかなり楽になってます。

# リソースAPIが提供されているサービス(2019/12/14時点)

リソースAPIは提供されているサービスが限られています。具体的には以下のサービスが対応しています。
 - CloudFormation
 - Cloud Watch
 - DynamoDB
 - EC2
 - Glacier
 - IAM
 - OpsWorks
 - S3
 - SNS
 - SQS

参考元：「Boto 3 Documentation」
https://boto3.amazonaws.com/v1/documentation/api/latest/index.html

上記サービスを利用する場合は、クライアントAPIの利用よりも先にまずリソースAPIの検討を行う必要がありそうです。Boto3のCodeExamples(サンプルドキュメント)でDynamoDBはresourceになっていますが、他のEC2やCloudWatchはclientなのでCodeExamplesだけでなくAvailable Servicesを見る必要がありそうです。

# 所感
今までクライアントAPIやリソースAPIなどをあまり意識せずに使用していたため反省もかねて色々調べた結果をまとめました。SQSの例でもわかるようにコード部分に不要な情報を書く必要がなくなるので、今後自分もまずはリソースAPIの提供があるサービスか確認し、提供されているならうまく使用できないか考えてからクライアントAPIを使いたいと思います。
QiitaやネットにはクライアントAPIで書かれた実装が多々あるようなので、今後自分が記事を書くことでリソースAPIの情報を増やしたいと思います。