LoginSignup
46
59

More than 3 years have passed since last update.

Vue.js + AWS Lambda + dynamo DBでチャット機能を作ってみる【AWSの設定編】

Posted at

今回はチャット機能を実装したので、備忘録として記事を残していきます。

AWS Lambdaの作成を行った後にフロント側の設定の記事を上げていこうかと思います。
AWS側はAPI Gateway + Lambda + DynamoDBの構成で作成していきます。
※ 2020/08/29時点での作成画面

API Gatewayの設定

①API GatewayからAPI作成

API Gatewayから APIの作成を押下し、
WebSocket APIを作成していきます。
1_API_gateway.png
2_API_gateway.png

②APIとルート選択式を設定

下記の内容でプロトコルを設定していきます。

プロトコル:WebSocket

項目 内容
API名 vue_chat_test
ルート選択式 $request.body.action
説明 vue_chat_test

3_プロトコル-2.png

③ルートキーを設定内容

API作成後、ルートキーを作成する画面に遷移します。
今回は各ルートキーを下記で設定していきます。

キー名 用途
$connect クライアントがWebSocket通信を開始する時に利用
$disconnect クライアントがWebSocket通信を終了する時に利用
$default 該当するルートキーが無い場合に利用
sendmessage メッセージ送信時に利用
getmessages メッセージ取得時に利用

ただ、Lambda関数を作成していないとルートキーの作成ができないので、
先にdynamoDBのテーブル作成とLambda関数の作成を行っていきます。

dynamoDBでテーブル作成

dynamoDBは下記の内容で作成していきます。
構成は、自分宛てに届いたメッセージを見れるチャットの想定です。

  • connections
項目 内容 キー設定
connection_id コネクションID パーテーションキー
user_id ユーザーID
  • messages
項目 内容 キー設定
user_id コネクションID パーテーションキー
created_at 作成日付 ソートキー
message メッセージ

※ 実際にチャット機能として作成する場合は
 ルームID、誰から誰に送ったのか、既読情報などの情報も必要かと思いますが、
 今回は簡易的なチャット機能なので、こちらの情報だけで作っていきます。

Lambda関数を作成

ランタイムは Python3.8 で下記のLambda関数を作成していきます。

関数 内容 アクセス権限設定
OnConnect コネクションテーブルに
コネクションID・ユーザーIDを保存
dynamoDB
OnDisconnect コネクションテーブルのレコード削除 dynamoDB
SendMessage メッセージを保存 dynamoDB,ExecuteAPI
GetMessages メッセージを取得 dynamoDB,ExecuteAPI

※ アクセス権限は一旦フルアクセス許可で設定

  • OnConnect
import json
import boto3

# dynamoDBのテーブル取得
dynamodb = boto3.resource('dynamodb')
connections = dynamodb.Table('connections')

def lambda_handler(event, context):

    # 「connectionId、userId」を取得
    connection_id = event.get('requestContext',{}).get('connectionId')
    user_id = event.get('queryStringParameters',{}).get('userId')

    # connectionsテーブルに追加
    result = connections.put_item(
        Item={
            'connection_id': connection_id,
            'user_id': str(user_id)
        }
    )
    return { 'statusCode': 200,'body': 'ok' }
  • OnDisconnect
import json
import boto3

dynamodb = boto3.resource('dynamodb')
connections = dynamodb.Table('connections')

def lambda_handler(event, context):
    connection_id = event.get('requestContext',{}).get('connectionId')
    result = connections.delete_item(Key={ 'connection_id': connection_id })
    return { 'statusCode': 200, 'body': 'ok' }
  • SendMessage
import json
import boto3
import json
from boto3.dynamodb.conditions import Key, Attr
from datetime import datetime, timedelta, timezone

# dynamoDBのテーブル取得
dynamodb = boto3.resource('dynamodb')
connections = dynamodb.Table('connections')
messages = dynamodb.Table('messages')

# TimeZoneの設定
JST = timezone(timedelta(hours=+9), 'JST')

def lambda_handler(event, context):

    # パラメータ取得
    body = json.loads(event["body"])
    user_id = body['user_id']
    message = body['message']

    # 作成日時
    now = "{0:%Y-%m-%d %H:%M:%S}".format(datetime.now(JST))

    # messageテーブルへ保存
    messages.put_item(
        Item={
            'user_id': user_id,
            'message': message,
            'created_at': now
        }
    )

    # ApiGatewayManagementApiの設定
    domain_name   = event.get('requestContext',{}).get('domainName')
    stage         = event.get('requestContext',{}).get('stage')
    apigw_management = boto3.client('apigatewaymanagementapi',
                                    endpoint_url=F"https://{domain_name}/{stage}")

    # 送信先userのconnection_idを取得
    connection_items = connections.scan(
        ProjectionExpression="connection_id",
        FilterExpression=Attr("user_id").eq(str(user_id))
    ).get('Items')    

    if len(connection_items) == 0:
        return { 'statusCode': 200,'body': 'ok' }

    # メッセージ情報を取得
    get_message_items = messages.query(
        KeyConditionExpression=Key("user_id").eq(str(user_id)) & Key("created_at").lte(now),
        ScanIndexForward=True
    ).get('Items')

    # メッセージがあればメッセージ、無ければ空配列を設定
    content_all = [] if len(get_message_items) == 0 else get_message_items

    # 送信先userにメッセージを返却
    for item in connection_items:
        _ = apigw_management.post_to_connection(ConnectionId=item['connection_id'],
                                                    Data=json.dumps(content_all))

    return { 'statusCode': 200,'body': 'ok' }
  • GetMessages
import boto3
import json
from boto3.dynamodb.conditions import Key, Attr
from datetime import datetime, timedelta, timezone


# テーブル定義
dynamodb_client = boto3.client('dynamodb')
dynamodb = boto3.resource('dynamodb')
connections = dynamodb.Table('connections')
messages = dynamodb.Table('messages')

# TimeZoneの設定
JST = timezone(timedelta(hours=+9), 'JST')

def lambda_handler(event, context):

    # パラメータ情報を変数に設定
    body = json.loads(event["body"])
    user_id = body['user_id']
    connection_id = event.get('requestContext',{}).get('connectionId')
    domain_name = event.get('requestContext',{}).get('domainName')
    stage       = event.get('requestContext',{}).get('stage')

    # コネクションIDが合っているか確認
    connection_items = connections.query(
        ProjectionExpression="connection_id",
        KeyConditionExpression=Key("connection_id").eq(connection_id),
        FilterExpression=Key('user_id').eq(str(user_id)),
    ).get('Items')

    if len(connection_items) == 0:
        return { 'statusCode': 500,'body': 'something went wrong' }

    # ApiGatewayManagementApiの設定
    apigw_management = boto3.client('apigatewaymanagementapi',
                                    endpoint_url=F"https://{domain_name}/{stage}")

    # 取得日時
    now = "{0:%Y-%m-%d %H:%M:%S}".format(datetime.now(JST))

    # メッセージ情報を取得
    get_message_items = messages.query(
        KeyConditionExpression=Key("user_id").eq(str(user_id)) & Key("created_at").lte(now),
        ScanIndexForward=True
    ).get('Items')

    # メッセージがあればメッセージ、無ければ空配列を設定
    content_all = [] if len(get_message_items) == 0 else get_message_items

    # コネクションにメッセージをPOST
    _ = apigw_management.post_to_connection(
        ConnectionId=connection_id,
        Data=json.dumps(content_all)
    )

    return {
        'statusCode': 200,
        'body': 'ok'
    }

ルートキーとLambdaの紐付け

作成したLambdaとAPI Gatewayのルートキーとを紐付けていきます。

ルートキー Lambda関数
$connect OnConnect
$disconnect OnDisconnect
$default OnConnect
sendmessage SendMessage
getmessages GetMessages

4_ルートキーとLambdaの紐付け.png

作成したWebSocket APIをデプロイ

ルートキーの紐付けが完了したら、WebSocket APIをデプロイしていきます。
今回はステージ名はTestで作成します。

5_デプロイ.png

デプロイが完了したら、 WebSocket URL接続 URLが発行されます。
フロントとの通信に必要なURLになります。

AWS側での設定は以上で、最後に動作確認だけ実施していきます。

wscatでのWebSocket APIを動作確認

wscatをインストール

$ npm install -g wscat

wscatを使って、動作を確認してdynamoDBにデータが保存されていることを確認する。

$ wscat -c [WebSocket URL]?userId=A

Connected (press CTRL+C to quit)
> {"action":"sendmessage","user_id": "A", "message": "Test"}
[{"message": "Test", "user_id": "A", "created_at": "2020-08-29 17:22:43"}]

> {"action":"getmessages", "user_id": "A"}
[{"message": "Test", "user_id": "A", "created_at": "2020-08-29 17:22:43"}]

dynamoDBの状態はこちら!

スクリーンショット 2020-08-29 17.29.17.png
スクリーンショット 2020-08-29 17.29.21.png

まとめ

Vue.js + AWS Lambda + dynamo DBでチャット機能のAWSの設定を作成してみました。
今回は動作することをメインに作成したので、エラー時の処理はあまり入れれていないのと、実際に実装するには他にも考えないといけないことも多いかと思います。

次はVue.jsを使ってフロント画面を作成して、APIとの通信を実施していきます。
※ 投稿日時は未定

ご指摘などがあれば是非コメントお願いいたします!

46
59
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
59