이 모듈에서는 Amazon DynamoDB 데이터베이스를 프로비저닝하고 DynamoDB를 사용하여 턴제 게임에 대한 정보를 저장하는 방법을 알아봅니다.

모듈 완료 시간: 30분


Amazon DynamoDB는 AWS가 제공하는 완전관리형 NoSQL 데이터베이스입니다. 이 데이터베이스는 10밀리초 미만의 응답 시간과 거의 무한한 확장성을 제공합니다. DynamoDB는 Amazon.com 쇼핑 카트부터 Lyft의 지리 위치 서비스와 광범위한 온라인 게임에 이르는 다양한 애플리케이션 및 업종에 사용됩니다.

DynamoDB와의 모든 상호 작용은 인증 및 권한 부여에 AWS Identity and Access Management(IAM)를 사용하여 HTTPS를 통해 수행됩니다. 일반적으로 원하는 언어의 AWS SDK를 사용하여 DynamoDB와 상호 작용합니다. Amazon Elastic Compute Cloud (Amazon EC2) 또는 AWS Lambda와 같은 AWS 컴퓨팅 옵션을 애플리케이션에 사용하는 경우 애플리케이션에서 컴퓨팅 환경의 AWS 자격 증명을 사용하여 DynamoDB에 요청을 제출할 수 있습니다.

다음 단계에서는 먼저 DynamoDB 데이터베이스를 프로비저닝합니다. 그런 다음 Node.js의 JavaScript용 AWS SDK를 사용하여 DynamoDB 데이터베이스와 상호 작용하는 방법을 알아봅니다.


  • 1단계. Amazon DynamoDB 데이터베이스 프로비저닝

    먼저, DynamoDB 데이터베이스를 프로비저닝합니다. DynamoDB의 데이터베이스를 테이블이라고도 합니다.

    DynamoDB 테이블을 생성할 때는 테이블의 기본 키를 구성하는 속성을 지정해야 합니다. DynamoDB 테이블에 기록되는 각 레코드를 항목이라고 하며 각 항목에는 테이블의 기본 키가 포함되어야 합니다.

    DynamoDB 데이터 모델링 및 기본 키 설계는 중요한 주제입니다. 그러나 게임의 액세스 패턴이 단순하므로 고급 데이터 모델링이 필요하지 않습니다. 따라서 이 자습서에서는 DynamoDB 데이터 모델링의 고급 주제를 다루지 않습니다. DynamoDB의 데이터 모델링에 대한 자세한 내용은 Amazon DynamoDB로 모바일 앱용 데이터베이스 설계 또는 Amazon DynamoDB로 게이밍 애플리케이션의 데이터 모델링의 자습서를 참조하십시오.

    애플리케이션은 DynamoDB를 단순한 키-값 스토어로 사용합니다. 각 게임에는 각 게임을 고유하게 식별하는 gameId 속성이 있습니다. gameId 속성은 테이블의 기본 키로 사용됩니다.

    scripts/ 디렉터리에 있는 create-table.sh 파일은 AWS Command Line Interface(AWS CLI)를 사용하여 DynamoDB 테이블을 생성합니다. 파일의 콘텐츠는 다음과 같습니다.
     

    aws dynamodb create-table \
      --table-name turn-based-game \
      --attribute-definitions '[
        {
          "AttributeName": "gameId",
          "AttributeType": "S"
        }
      ]' \
      --key-schema '[
        {
          "AttributeName": "gameId",
          "KeyType": "HASH"
        }
      ]' \
      --provisioned-throughput '{
        "ReadCapacityUnits": 5,
        "WriteCapacityUnits": 5
      }'

    먼저, 이 파일은 테이블에 turn-based-game이라는 이름을 지정합니다. 그런 다음 테이블의 기본 키에 사용되는 속성을 선언합니다. 이 예제에서는 단순 기본 키를 사용할 것이므로 문자열 데이터 유형의 속성인 gameId 속성 1개만 선언합니다. 다음으로, gameId 속성을 테이블의 해시 키로 사용할 것임을 명시하여 기본 키 스키마를 지정합니다.

    마지막으로, 테이블에 대한 읽기 및 쓰기 용량 단위 수를 지정합니다. DynamoDB는 테이블에 대한 각 읽기 및 쓰기 요청을 기준으로 요금을 지불하는 온디맨드 요금 모드를 제공합니다. 그러나 프로비저닝된 처리량 모드가 AWS 프리 티어 범위 내에 포함되므로 여기서 이 모드를 사용할 수 있습니다.

    터미널에서 다음 명령을 실행하여 테이블을 생성합니다.

    bash scripts/create-table.sh

    터미널에 다음 출력이 표시됩니다.

    {
        "TableDescription": {
            "AttributeDefinitions": [
                {
                    "AttributeName": "gameId",
                    "AttributeType": "S"
                }
            ],
            "TableName": "turn-based-game",
            "KeySchema": [
                {
                    "AttributeName": "gameId",
                    "KeyType": "HASH"
                }
            ],
            "TableStatus": "CREATING",
            "CreationDateTime": 1574086642.07,
            "ProvisionedThroughput": {
                "NumberOfDecreasesToday": 0,
                "ReadCapacityUnits": 5,
                "WriteCapacityUnits": 5
            },
            "TableSizeBytes": 0,
            "ItemCount": 0,
            "TableArn": "arn:aws:dynamodb:us-east-1:955617200811:table/turn-based-game",
            "TableId": "c62cb86a-211e-4a50-a160-4a616c8f3445"
        }
    }
  • 2단계. 테이블에 예제 게임 항목을 저장합니다.

    테이블을 생성했으니 이제 항목을 테이블에 추가할 수 있습니다. 각 게임은 테이블에서 단일 항목으로 표시됩니다.

    각 게임 항목의 스키마는 다음과 같습니다.

    각 게임에는 게임의 고유 식별자인 GameId가 포함됩니다. User1User2 속성에는 게임을 플레이 중인 사용자 2명의 사용자 이름이 저장됩니다. Heap1, Heap2Heap3 속성에는 각 3개 힙의 객체 수가 저장됩니다. 마지막으로 LastMoveBy 속성은 가장 최근에 동작을 취한 플레이어를 나타냅니다.

    scripts/ 디렉터리에 있는 createGame.js 파일은 테이블에 예제 게임을 추가합니다. 파일의 콘텐츠는 다음과 같습니다.

    const AWS = require('aws-sdk')
    const documentClient = new AWS.DynamoDB.DocumentClient()
    
    const params = {
      TableName: 'turn-based-game',
      Item: {
        gameId: '5b5ee7d8',
        user1: 'myfirstuser',
        user2: 'theseconduser',
        heap1: 5,
        heap2: 4,
        heap3: 5,
        lastMoveBy: 'myfirstuser'
      }
    }
    
    documentClient.put(params).promise()
      .then(() => console.log('Game added successfully!'))
      .catch((error) => console.log('Error adding game', error))

    AWS SDK를 가져온 다음 DynamoDB Document Client 인스턴스를 생성합니다. Document Client는 상세한 DynamoDB API를 간략하게 추상화하여 DynamoDB 항목을 사용한 작업을 간소화합니다. 이 클라이언트가 생성되면 스크립트가 테이블 이름 및 항목 속성을 포함하여 PutItem API 호출에 대한 파라미터를 어셈블링합니다. 그런 다음 Document Client에서 put() 메서드를 호출하고 성공 또는 실패에 대한 정보를 로깅합니다.

    이 스크립트를 실행할 때 터미널에서 다음 명령을 실행하여 테이블에 게임을 삽입할 수 있습니다.

    node scripts/createGame.js

    터미널에 다음 출력이 표시됩니다.

    Game added successfully!

    참고: 명령을 너무 빠르게 실행하면 테이블을 아직 사용할 수 없다는 내용의 오류가 표시될 수 있습니다. 이 경우 1분간 기다린 후 명령을 다시 실행합니다.

    좋습니다. 이제 게임 하나가 테이블에 추가되었습니다. 다음 단계에서는 게임 항목을 업데이트하여 동작 중인 사용자를 시뮬레이션하는 방법을 알아봅니다.

  • 3단계. 테이블의 게임 항목 업데이트

    이제 게임 항목이 테이블이 포함되었으니 진행 중인 게임에서 동작 중인 플레이어를 시뮬레이션하는 방법을 알아볼 수 있습니다.

    이 작업은 2가지 방법으로 처리될 수 있습니다. 첫 번째 방법에서는 GetItem API를 사용하여 항목을 검색합니다. 그런 다음 플레이어가 취한 동작에 따라 애플리케이션의 게임 항목을 업데이트할 수 있습니다. 마지막으로, PutItem API를 사용하여 DynamoDB의 기존 항목을 바꿉니다. 이 옵션은 작동하기는 하지만 DynamoDB 테이블을 여러 번 요청해야 하기 때문에 게임 항목을 가져오고 다시 작성하는 사이에 수행된 변경 사항을 덮어쓰게 될 위험이 있습니다.

    이 작업을 처리하는 두 번째 방법은 DynamoDB에서 UpdateItem API 호출을 사용하는 것입니다. UpdateItem API를 사용하면 단일 요청을 통해 기존의 DynamoDB 항목을 업데이트할 수 있습니다. 변경하려는 항목, 변경할 속성 및 항목의 어셜션 조건을 지정하면 됩니다. 이 방법은 데이터베이스를 여러 번 호출하지 않아도 되므로 항목을 변경할 때 선호되는 방법입니다.

    scripts/ 디렉터리에는 다음과 같은 콘텐츠가 포함된 performMove.js 파일이 있습니다.

    const AWS = require('aws-sdk')
    const documentClient = new AWS.DynamoDB.DocumentClient()
    
    const performMove = async ({ gameId, user, changedHeap, changedHeapValue }) => {
      if (changedHeapValue < 0 ) {
        throw new Error('Cannot set heap value below 0')
      }
      const params = {
        TableName: 'turn-based-game',
        Key: { 
          gameId: gameId
        },
        UpdateExpression: `SET lastMoveBy = :user, ${changedHeap} = :changedHeapValue`,
        ConditionExpression: `(user1 = :user OR user2 = :user) AND lastMoveBy <> :user AND ${changedHeap} > :changedHeapValue`,
        ExpressionAttributeValues: {
          ':user': user,
          ':changedHeapValue': changedHeapValue,
        },
        ReturnValues: 'ALL_NEW'
      }
      try {
        const resp = await documentClient.update(params).promise()
        console.log('Updated game: ', resp.Attributes)
      } catch (error) {
        console.log('Error updating item: ', error.message)
      }
    }
    
    performMove({ gameId: '5b5ee7d8', user: 'theseconduser', changedHeap: 'heap1', changedHeapValue: 3 })

    이 스크립트는 약간 복잡하므로 단계별로 살펴봅시다.

    이전 스크립트와 마찬가지로 AWS SDK를 가져오고 DynamoDB Document Client 인스턴스를 생성합니다.

    그런 다음 performMove라는 메서드를 정의합니다. 이 메서드는 사용자가 동작을 취하기 위한 요청을 제출할 때 애플리케이션에서 사용될 수 있는 내부 메서드와 유사합니다. 이 스크립트는 UpdateItem API 호출에 대한 파라미터를 어셈블링합니다. 먼저, 이 메서드는 게임의 2가지 속성, 즉 마지막으로 동작을 취한 사용자와 변경된 힙의 요소 수를 변경합니다.

    그러면 UpdateItem API 파라미터가 게임의 현재 상태에 대한 일부 어설션을 수행합니다. 항목을 업데이트하기 전에 항목이 원하는 상태에 있는지 확인하기 위해 ConditionExpression이 다시 평가됩니다. 조건 식에서는 다음 3가지 어설션을 수행합니다.

    1. 동작의 수행을 요청하는 사용자는 게임 사용자 2명 중 1명입니다.
    2. 현재 턴은 동작 수행을 요청하는 사용자에게 속합니다.
    3. 변경되는 힙의 현재 값은 변경할 값보다 큽니다.

    마지막으로, 파라미터는 ReturnValueALL_NEW로 명시합니다. 이렇게 하면 값이 업데이트된 후 DynamoDB가 전체 항목을 반환합니다. 이 작업이 완료되면 애플리케이션에서 게임을 평가하여 승자가 있는지 여부를 확인할 수 있습니다.

    파일의 맨 아래에 애플리케이션에서 이 메서드를 호출하는 방법에 대한 예제가 나와 있습니다. 다음 명령을 사용하여 스크립트를 실행할 수 있습니다.

    node scripts/performMove.js

    터미널에 다음 출력이 표시됩니다.

    Updated game:  { heap2: 4,
      heap1: 3,
      heap3: 5,
      gameId: '5b5ee7d8',
      user2: 'theseconduser',
      user1: 'myfirstuser',
      lastMoveBy: 'theseconduser' }

    쓰기 작업이 성공했고 게임이 업데이트되었음을 알 수 있습니다.

    터미널에서 스크립트를 다시 실행합니다. 다음 출력이 표시됩니다.

    Error updating item:  The conditional request failed

    이번에는 조건부 요청이 실패했습니다. 요청하는 사용자(-- theseconduser --)가 최근에 동작한 사용자였습니다. 또한 변경된 힙(-- heap1 --)의 값이 이미 3이므로 사용자가 변경한 것이 없습니다. 이로 인해 요청이 거부되었습니다.


이 모듈에서는 게임 데이터를 저장할 Amazon DynamoDB 데이터베이스를 프로비저닝했습니다. 데이터 모델링에서 DynamoDB의 기본 키에 대해 알아보았습니다. 테이블을 생성한 후에는 초기 게임 상태를 저장하기 위해 항목을 테이블에 삽입하는 방법을 학습했습니다. 마지막으로, 데이터의 항목을 업데이트하여 단일 요청에서 DynamoDB가 여러 번 호출되지 않도록 하는 방법을 배웠습니다.

다음 모듈에서는 Amazon Simple Notification Service(Amazon SNS)를 사용하여 SMS 메시지를 전송하여 게임 내의 중요한 이벤트를 사용자에게 알리는 방법을 알아봅니다.