2단계: 데이터 모델 및 구현 세부 정보 검사 - Amazon DynamoDB

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

2단계: 데이터 모델 및 구현 세부 정보 검사

2.1: 기본 데이터 모델

이 예제 애플리케이션에서는 다음 DynamoDB 데이터 모델 개념을 중심으로 설명합니다.

  • 테이블 - DynamoDB에서 테이블은 항목 모음(즉, 레코드)이고 각 항목은 속성이라고 하는 이름-값 쌍의 모음입니다.

    이 Tic-Tac-Toe 예제에서 애플리케이션은 모든 게임 데이터를 Games라고 하는 테이블에 저장합니다. 이 애플리케이션은 테이블에 게임당 한 항목씩 생성하고 모든 게임 데이터를 속성으로 저장합니다. Tic-Tac-Toe 게임은 최대 9회까지 동작이 가능합니다. 기본 키만 필수 속성인 경우 DynamoDB 테이블은 스키마가 없으므로 애플리케이션에서 게임 항목당 다양한 개수의 속성을 저장할 수 있습니다.

    Games 테이블에는 문자열 형식의 GameId 속성 한 가지로 구성된 단순 기본 키가 있습니다. 애플리케이션이 각 게임에 고유한 ID를 할당합니다. DynamoDB 기본 키에 대한 자세한 내용은 프라이머리 키 단원을 참조하세요.

    사용자가 다른 사용자를 초대하여 Tic-Tac-Toe 게임을 시작할 경우 애플리케이션은 다음과 같은 게임 메타데이터를 저장하는 속성을 사용하여 Games 테이블에 새 항목을 만듭니다.

    • HostId는 게임을 시작한 사용자입니다.

    • Opponent는 초대를 받은 사용자입니다.

    • 플레이 차례가 된 사용자로, 게임을 시작한 사용자가 먼저 플레이합니다.

    • 보드에서 O 기호를 사용하는 사용자로, 게임을 시작한 사용자가 O 기호를 사용합니다.

    또한 이 애플리케이션은 StatusDate 연결된 속성을 생성하여 초기 게임 상태를 PENDING으로 표시합니다. 다음 스크린샷은 DynamoDB 콘솔에 나타난 예제 항목을 보여 줍니다.

    
                            속성 테이블의 콘솔 스크린샷입니다.

    게임이 진행되면 애플리케이션이 각 게임 동작에 대한 하나의 속성을 테이블에 추가합니다. 속성 이름은 보드상의 위치(예: TopLeft 또는 BottomRight)입니다. 예를 들어 동작은 O 값의 TopLeft 속성, O 값의 TopRight 속성, X 값의 BottomRight 속성을 가질 수 있습니다. 사용자에 따라 속성 값이 O 또는 X가 됩니다. 예를 들어 다음 보드를 가정해 보세요.

    
                            동점으로 끝나는 tic-tac-toe 게임을 보여 주는 스크린샷입니다.
  • 연결된 값 속성 - StatusDate 속성은 연결된 값 속성을 나타냅니다. 이 방식에서는 게임 상태(PENDING, IN_PROGRESS, FINISHED) 및 날짜(동작을 한 시간)를 저장하기 위한 별도의 속성을 만드는 대신 둘을 하나의 속성으로 결합합니다(예: IN_PROGRESS_2014-04-30 10:20:32).

    그런 다음 애플리케이션은 StatusDate를 인덱스의 정렬 키로 지정함으로써 StatusDate 속성을 사용하여 보조 인덱스를 생성합니다. StatusDate 연결된 값 속성을 사용하는 이점에 대해서는 다음 단원의 인덱스 설명에서 자세히 다룹니다.

  • 글로벌 보조 인덱스 - 테이블의 기본 키인 GameId를 사용하여 테이블을 효율적으로 쿼리하고 게임 항목을 찾을 수 있습니다. 기본 키 속성이 아닌 속성으로 테이블을 쿼리하기 위해 DynamoDB는 보조 인덱스 생성을 지원합니다. 이 예제 애플리케이션에서는 다음 두 가지 인덱스를 만듭니다.

    
                            예제 애플리케이션에서 생성된 hostStatusDate 및 OpppStatusDate 글로벌 보조 인덱스를 보여 주는 스크린샷입니다.
    • [HostId-StatusDate-index]. 이 인덱스는 파티션 키가 HostId이고 정렬 키가 StatusDate입니다. 이 인덱스를 사용하면, 예를 들어 HostId를 쿼리하여 특정 사용자가 호스팅한 게임을 찾을 수 있습니다.

    • [OpponentId-StatusDate-index]. 이 인덱스는 파티션 키가 OpponentId이고 정렬 키가 StatusDate입니다. 이 인덱스를 사용하면, 예를 들어 Opponent를 쿼리하여 특정 사용자가 상대방인 게임을 찾을 수 있습니다.

    이러한 인덱스는 이러한 인덱스의 파티션 키가 테이블의 기본 키에 사용된 파티션 키 속성(GameId)과 동일하지 않기 때문에 글로벌 보조 인덱스라고 불립니다.

    두 인덱스 모두 정렬 키로 StatusDate를 지정합니다. 이렇게 하면 다음과 같은 작업을 할 수 있습니다.

    • BEGINS_WITH 비교 연산자를 사용하여 쿼리할 수 있습니다. 예를 들어 특정 사용자가 호스팅한 게임 중 IN_PROGRESS 속성을 가진 모든 게임을 검색할 수 있습니다. 이 경우 BEGINS_WITH 연산자가 IN_PROGRESS로 시작하는 StatusDate 값을 확인합니다.

    • DynamoDB는 인덱스 항목을 정렬 키 값을 기준으로 정렬된 순서대로 저장합니다. 따라서 모든 상태 접두사가 동일할 경우(예: IN_PROGRESS) 날짜 부분에 ISO 형식을 사용해 가장 오래된 항목에서 최신 항목의 순서로 항목을 정렬합니다. 이 방식에서는 예를 들어 다음과 같은 특정 쿼리를 효율적으로 수행할 수 있습니다.

      • 로그인한 사용자가 호스팅한 최근 IN_PROGRESS 게임을 최대 10개까지 검색합니다. 이 쿼리를 수행하려면 HostId-StatusDate-index 인덱스를 지정합니다.

      • 로그인한 사용자가 상대방인 최근 IN_PROGRESS 게임을 최대 10개까지 검색합니다. 이 쿼리를 수행하려면 OpponentId-StatusDate-index 인덱스를 지정합니다.

보조 인덱스에 대한 자세한 내용은 보조 인덱스를 사용하여 데이터 액세스 향상 단원을 참조하세요.

2.2: 애플리케이션 실행(코드 단계별 안내)

이 애플리케이션에는 두 개의 메인 페이지가 있습니다.

  • 홈 페이지 - 이 페이지는 사용자에게 간단한 로그인, 새 Tic-Tac-Toe 게임을 만들 수 있는 CREATE(생성) 버튼, 진행 중인 게임 목록, 게임 기록 및 활성 상태로 보류 중인 게임 초대를 제공합니다.

    홈 페이지는 자동으로 새로 고쳐지지 않으며 페이지를 새로 고쳐 목록을 새로 고쳐야 합니다.

  • 게임 페이지 - 이 페이지에는 사용자가 플레이하는 Tic-Tac-Toe 그리드가 표시됩니다.

    애플리케이션은 매초 자동으로 게임 페이지를 업데이트합니다. 브라우저의 JavaScript가 매초 Python 웹 서버를 호출하여 Games 테이블을 쿼리하고 테이블의 게임 항목이 변경되었는지 여부를 확인합니다. 변경된 경우 JavaScript에서 사용자가 업데이트된 보드를 볼 수 있도록 페이지 새로 고침을 트리거합니다.

이제 애플리케이션의 작동 방식에 대해 자세히 설명하겠습니다.

홈 페이지

사용자가 로그인하면 애플리케이션에 다음과 같은 세 가지 정보 목록이 표시됩니다.


                        보류 중인 초대장, 진행 중인 게임 및 최근 기록의 세 가지 목록이 있는 애플리케이션 홈 페이지를 보여 주는 스크린샷입니다.
  • 초대 - 이 목록에는 로그인한 사용자가 가장 최근에 다른 사용자로부터 받은 수락 보류 중인 초대가 최대 10개까지 표시됩니다. 위 스크린샷에서 user1에게는 user5와 user2가 보낸 보류 중인 초대가 있습니다.

  • 진행 중인 게임 - 이 목록에는 진행 중인 최근 게임이 최대 10개까지 표시됩니다. 이러한 게임은 사용자가 현재 플레이하고 있고 상태가 IN_PROGRESS인 게임입니다. 스크린샷에서 user1은 user3 및 user4와 Tic-Tac-Toe 게임을 플레이하고 있습니다.

  • 최근 기록 - 이 목록에는 사용자가 가장 최근에 완료하고 상태가 FINISHED인 게임이 최대 10개까지 표시됩니다. 스크린샷의 게임에서 user1은 이전에 user2와 플레이한 적이 있습니다. 완료된 각 게임에 대해 게임 결과가 표시됩니다.

코드에서 application.pyindex 함수는 다음과 같은 세 번의 호출로 게임 상태 정보를 검색합니다.

inviteGames = controller.getGameInvites(session["username"]) inProgressGames = controller.getGamesWithStatus(session["username"], "IN_PROGRESS") finishedGames = controller.getGamesWithStatus(session["username"], "FINISHED")

각 호출에서 Game 객체로 래핑된 DynamoDB의 항목 목록이 반환됩니다. 보기에서 이러한 객체에서 쉽게 데이터를 추출할 수 있습니다. index 함수는 HTML을 렌더링하기 위해 이러한 객체 목록을 보기에 전달합니다.

return render_template("index.html", user=session["username"], invites=inviteGames, inprogress=inProgressGames, finished=finishedGames)

Tic-Tac-Toe 애플리케이션은 기본적으로 DynamoDB에서 가져온 게임 데이터를 저장하기 위해 Game 클래스를 정의합니다. 이러한 함수는 Amazon DynamoDB 항목과 관련된 코드에서 나머지 애플리케이션을 격리할 수 있는 Game 객체 목록을 반환합니다. 따라서 이러한 함수를 통해 데이터 스토어 계층의 세부 정보에서 애플리케이션 코드를 분리할 수 있습니다.

여기에서 설명하는 아키텍처 패턴은 MVC(모델-보기-컨트롤러) UI 패턴이라고도 합니다. 이 경우 데이터를 나타내는 Game 객체 인스턴스는 모델이며 HTML 페이지는 보기입니다. 컨트롤러는 두 개 파일로 나누어집니다. application.py 파일에는 Flask 프레임워크를 위한 컨트롤러 로직이 있으며 비즈니스 로직은 gameController.py 파일에 격리되어 있습니다. 즉, 애플리케이션은 DynamoDB SDK와 관련된 모든 항목을 dynamodb 폴더의 별도 파일에 저장합니다.

이제 세 가지 함수를 살펴보고 이러한 함수가 어떻게 글로벌 보조 인덱스를 사용하여 Games 테이블을 쿼리하고 관련 데이터를 검색하는지 알아보겠습니다.

getGameInvites를 사용하여 보류 중인 게임 초대 목록 불러오기

getGameInvites 함수는 가장 최근에 받은 보류 중인 초대 10개의 목록을 검색합니다. 이러한 게임은 사용자가 생성했지만 상대방이 초대를 수락하지 않은 게임입니다. 이러한 게임은 상대방이 초대를 수락할 때까지 상태가 PENDING으로 유지됩니다. 상대방이 초대를 거부할 경우, 애플리케이션이 테이블에서 해당 항목을 제거합니다.

이 함수는 다음과 같이 쿼리를 지정합니다.

  • 다음 인덱스 키 값 및 비교 연산자에 사용할 OpponentId-StatusDate-index 인덱스를 지정합니다.

    • 파티션 키는 OpponentId이고 인덱스 키로 user ID를 사용합니다.

    • 정렬 키는 StatusDate이고 비교 연산자 및 인덱스 키 값으로 beginswith="PENDING_"을 사용합니다.

    OpponentId-StatusDate-index 인덱스를 사용하여 로그인한 사용자가 초대받은 게임, 즉 로그인한 사용자가 상대방인 게임을 검색합니다.

  • 쿼리는 결과를 10개 항목으로 제한합니다.

gameInvitesIndex = self.cm.getGamesTable().query( Opponent__eq=user, StatusDate__beginswith="PENDING_", index="OpponentId-StatusDate-index", limit=10)

인덱스에서 각 OpponentId(파티션 키)에 대해 DynamoDB는 StatusDate(정렬 키)를 기준으로 항목을 정렬합니다. 따라서 쿼리가 반환하는 게임은 최근 10개 게임이 됩니다.

getGamesWithStatus를 사용하여 특정 상태의 게임 목록 불러오기

상대방이 게임 초대를 수락하면 게임 상태가 IN_PROGRESS로 변경됩니다. 게임이 완료된 다음에는 상태가 FINISHED로 변경됩니다.

진행 중이거나 완료된 게임을 검색하는 쿼리는 상태 값이 다른 것을 제외하고 동일합니다. 따라서 애플리케이션은 상태 값을 파라미터로 사용하는 getGamesWithStatus 함수를 정의합니다.

inProgressGames = controller.getGamesWithStatus(session["username"], "IN_PROGRESS") finishedGames = controller.getGamesWithStatus(session["username"], "FINISHED")

다음 단원은 진행 중인 게임에 대한 설명이지만 완료된 게임에도 동일하게 적용됩니다.

특정 사용자가 진행 중인 게임 목록에는 다음이 모두 포함됩니다.

  • 사용자가 호스팅하여 진행 중인 게임

  • 사용자가 상대방으로 진행 중인 게임

getGamesWithStatus 함수는 다음 두 쿼리를 실행하며 매번 적절한 보조 인덱스를 사용합니다.

  • 이 함수는 HostId-StatusDate-index 인덱스를 사용하여 Games 테이블을 쿼리합니다. 인덱스의 경우 쿼리는 기본 키 값(파티션 키(HostId) 및 정렬 키(StatusDate) 값 모두)을 비교 연산자와 함께 지정합니다.

    hostGamesInProgress = self.cm.getGamesTable ().query(HostId__eq=user, StatusDate__beginswith=status, index="HostId-StatusDate-index", limit=10)

    비교 연산자의 Python 구문은 다음과 같습니다.

    • HostId__eq=user는 같음 비교 연산자를 지정하는 구문입니다.

    • StatusDate__beginswith=statusBEGINS_WITH 비교 연산자를 지정하는 구문입니다.

  • 이 함수는 OpponentId-StatusDate-index 인덱스를 사용하여 Games 테이블을 쿼리합니다.

    oppGamesInProgress = self.cm.getGamesTable().query(Opponent__eq=user, StatusDate__beginswith=status, index="OpponentId-StatusDate-index", limit=10)
  • 그런 다음 이 함수는 두 목록을 결합하고 정렬하여 첫 0번 ~ 10번 항목에 대해 Game 객체 목록을 만들고 호출 함수(인덱스)에 목록을 반환합니다.

    games = self.mergeQueries(hostGamesInProgress, oppGamesInProgress) return games

게임 페이지

게임 페이지는 사용자가 Tic-Tac-Toe 게임을 플레이하는 곳입니다. 여기에는 게임 그리드와 함께 게임 관련 정보가 표시됩니다. 다음 스크린샷은 진행 중인 예제 게임을 보여줍니다.


                        진행 중인 tic-tac-toe 게임을 보여 주는 스크린샷입니다.

애플리케이션은 다음 상황에서 게임 페이지를 표시합니다.

  • 사용자가 게임을 만들고 플레이할 다른 사용자를 초대합니다.

    이 경우 페이지에 호스트로 사용자가 표시되고 게임 상태는 상대방의 수락을 기다리는 PENDING으로 표시됩니다.

  • 사용자가 홈 페이지에서 대기 중인 초대 중 하나를 수락합니다.

    이 경우 페이지에 사용자가 상대방으로 표시되고 게임 상태는 IN_PROGRESS로 표시됩니다.

사용자가 보드에서 선택하면 애플리케이션에 대해 양식 POST 요청이 생성됩니다. 즉, Flask가 application.py에서 HTML 양식 데이터로 selectSquare를 호출합니다. 그 다음 이 함수가 gameController.py에서 updateBoardAndTurn 함수를 호출하여 다음과 같이 게임 항목을 업데이트합니다.

  • 동작에 해당하는 새 속성을 추가합니다.

  • Turn 속성 값을 다음 차례가 돌아온 사용자로 업데이트합니다.

controller.updateBoardAndTurn(item, value, session["username"])

이 함수는 항목 업데이트가 성공적일 경우 true를 반환하고 그렇지 않으면 false를 반환합니다. updateBoardAndTurn 함수에 대한 다음 내용을 참조하세요.

  • 함수가 SDK for Python의 update_item 함수를 호출하여 기존 항목에 대해 유한한 업데이트 집합을 만듭니다. 이 함수는 DynamoDB의 UpdateItem 작업에 매핑됩니다. 자세한 내용은 UpdateItem 단원을 참조하세요.

    참고

    UpdateItemPutItem 작업의 차이는 PutItem의 경우 전체 항목을 대체한다는 점입니다. 자세한 내용은 PutItem 단원을 참조하세요.

update_item 호출의 경우 코드가 다음을 식별합니다.

  • Games 테이블의 기본 키(즉, ItemId)입니다.

    key = { "GameId" : { "S" : gameId } }
  • 현재 사용자 동작에 해당하는 새로 추가할 속성 및 해당 값(예: TopLeft="X")

    attributeUpdates = { position : { "Action" : "PUT", "Value" : { "S" : representation } } }
  • 업데이트되기 위해 충족되어야 하는 조건은 다음과 같습니다.

    • 게임이 진행 중이어야 합니다. 즉, StatusDate 속성이 IN_PROGRESS로 시작되어야 합니다.

    • 현재 차례가 Turn 속성에 지정된 대로 유효한 사용자 차례여야 합니다.

    • 사용자가 선택한 사각형이 사용 가능해야 합니다. 즉, 사각형에 해당하는 속성이 없어야 합니다.

    expectations = {"StatusDate" : {"AttributeValueList": [{"S" : "IN_PROGRESS_"}], "ComparisonOperator": "BEGINS_WITH"}, "Turn" : {"Value" : {"S" : current_player}}, position : {"Exists" : False}}

이제 함수가 update_item을 호출하여 항목을 업데이트합니다.

self.cm.db.update_item("Games", key=key, attribute_updates=attributeUpdates, expected=expectations)

함수가 반환되면 다음 예제와 같이 selectSquare 함수 호출이 리디렉션됩니다.

redirect("/game="+gameId)

이 호출에 따라 브라우저가 새로 고침 됩니다. 애플리케이션이 새로 고침 중에 게임이 승 또는 무승부로 종료되었는지 여부를 확인합니다. 확인을 마치면 애플리케이션에서는 그에 따라 게임 항목을 업데이트합니다.