Одним из основных отличий для пользователей, которые только знакомятся с DynamoDB и NoSQL, является процесс моделирования данных для фильтрации по целому набору данных. Например, нужно найти сеансы игры с открытыми местами, чтобы показать пользователям, к каким сеансам можно присоединиться.
При использовании реляционной базы данных нужно было бы написать код SQL, чтобы запросить данные.

SELECT * FROM games
	WHERE status = “OPEN”

DynamoDB может фильтровать результаты при выполнении операции Query или Scan, но DynamoDB работает не так, как реляционная база данных. Фильтр DynamoDB применяется после получения исходных объектов, которые отвечают параметрам операции Query или Scan. Фильтр уменьшает объем полезных данных, отправляемых из сервиса DynamoDB, но количество объектов, полученных изначально, зависит от лимитов размеров DynamoDB.

К счастью, существуют способы разрешить отправку запросов с фильтрацией к набору данных в DynamoDB. Чтобы создать эффективные фильтры для таблицы DynamoDB, необходимо с самого начала спланировать их использование в модели данных таблицы. Вспомните урок, который мы извлекли из второго модуля этого курса: сначала рассмотрите шаблоны доступа, а потом проектируйте таблицу.

На следующих этапах мы воспользуемся глобальным вторичным индексом, чтобы найти открытые игры. Точнее, мы применим разреженный индекс для реализации этого шаблона доступа.

Время, необходимое для прохождения модуля: 40 минут


  • Этап 1. Моделирование разреженного вторичного индекса

    Вторичные индексы – ключевые инструменты моделирования данных в DynamoDB. С их помощью можно переформировать данные, чтобы воспользоваться альтернативными шаблонами запросов. Чтобы создать вторичный индекс, нужно указать первичный ключ индекса, как это делалось раньше во время создания таблицы. Обратите внимание, что первичный ключ глобального вторичного индекса необязательно должен быть уникальным для каждого элемента. Затем DynamoDB копирует объекты в индекс в зависимости от указанных атрибутов, так что запросы к нему можно выполнять так же, как и к таблице.

    Использование разреженных вторичных индексов – это продвинутая стратегия в DynamoDB. При использовании вторичных индексов DynamoDB копирует объекты из исходной таблицы, только если они содержат элементы первичного ключа во вторичном индексе. Элементы без первичного ключа не копируются, поэтому такой вторичный индекс называется «разреженным».

    Давайте посмотрим, как это может нам помочь. Вы, наверное, помните, что есть два шаблона доступа для поиска открытых игр:

    • найти открытые игры (чтение);
    • найти открытые игры по карте (чтение).

    Создать вторичный индекс можно с использованием составного первичного ключа, в котором ключ HASH – это атрибут map игры, а ключ RANGE – это атрибут open_timestamp игры, в котором указано время ее открытия.

    Для нас важно то, что атрибут open_timestamp удаляется, если игра заполнена. Когда удаляется этот атрибут, заполненная игра изымается из вторичного индекса, потому что она не имеет значения для атрибута ключа RANGE. Таким образом индекс становится разреженным: в нем находятся только открытые игры с атрибутом open_timestamp.

    На следующем этапе мы создадим вторичный индекс.

  • Этап 2. Создание разреженного вторичного индекса

    На этом этапе мы создадим разреженный вторичный индекс для открытых игр (игр, которые еще не заполнены).

    Создание вторичного индекса напоминает создание таблицы. В загруженном коде в каталоге scripts/ вы найдете файл скрипта с именем add_secondary_index.py. Этот файл содержит следующий код:

    import boto3
    
    dynamodb = boto3.client('dynamodb')
    
    try:
        dynamodb.update_table(
            TableName='battle-royale',
            AttributeDefinitions=[
                {
                    "AttributeName": "map",
                    "AttributeType": "S"
                },
                {
                    "AttributeName": "open_timestamp",
                    "AttributeType": "S"
                }
            ],
            GlobalSecondaryIndexUpdates=[
                {
                    "Create": {
                        "IndexName": "OpenGamesIndex",
                        "KeySchema": [
                            {
                                "AttributeName": "map",
                                "KeyType": "HASH"
                            },
                            {
                                "AttributeName": "open_timestamp",
                                "KeyType": "RANGE"
                            }
                        ],
                        "Projection": {
                            "ProjectionType": "ALL"
                        },
                        "ProvisionedThroughput": {
                            "ReadCapacityUnits": 1,
                            "WriteCapacityUnits": 1
                        }
                    }
                }
            ],
        )
        print("Table updated successfully.")
    except Exception as e:
        print("Could not update table. Error:")
        print(e)

    Когда в первичном ключе таблицы или вторичного индекса используются атрибуты, их необходимо определить в AttributeDefinitions. Затем мы создаем вторичный индекс операцией Create в свойстве GlobalSecondaryIndexUpdates. Для этого вторичного индекса мы указываем имя индекса, схему первичного ключа, предоставляемую пропускную способность и атрибуты, для которых необходимо создать проекцию.

    Обратите внимание, что нам не понадобилось указывать, что вторичный индекс предполагается использовать как разреженный. Это делается только за счет данных, которыми его наполняют. Если в таблицу записываются данные, не содержащие атрибутов для вторичных индексов, то они не будут включены во вторичный индекс.

    Создайте вторичный индекс с помощью приведенной ниже команды.

    python scripts/add_secondary_index.py

    В консоли вы увидите сообщение «Table updated successfully» («Таблица обновлена»).

    На следующем этапе мы будем использовать разреженный индекс для поиска открытых игр по картам.

  • Этап 3. Опрос разреженного вторичного индекса

    Теперь, после настройки вторичного индекса, воспользуемся им, чтобы реализовать некоторые шаблоны доступа.

    Для использования вторичного индекса можно воспользоваться двумя вызовами API: Query и Scan. Для Query требуется указать ключ HASH. Он возвращает целевой результат. Для Scan не требуется указывать ключ HASH. Эта операция выполняется над всей таблицей. Операции Scan не рекомендуется использовать в DynamoDB, кроме определенных случаев, потому что они обращаются к каждому объекту базы данных. Если в таблице очень много данных, то сканирование может длиться долго. На следующем этапе мы покажем, почему операции Scan могут быть эффективным инструментом при использовании с разреженными индексами.

    API Query можно использовать для поиска всех открытых игр по картам в индексе, созданном на предыдущем этапе. Вторичный индекс разбивается на разделы по именам карт, что позволяет выполнять целевые запросы для поиска открытых игр.

    В загруженном коде в каталоге application/ находится файл find_open_games_by_map.py. Этот скрипт содержит следующий код:

    import boto3
    
    from entities import Game
    
    dynamodb = boto3.client('dynamodb')
    
    def find_open_games_by_map(map_name):
        resp = dynamodb.query(
            TableName='battle-royale',
            IndexName="OpenGamesIndex",
            KeyConditionExpression="#map = :map",
            ExpressionAttributeNames={
                "#map": "map"
            },
            ExpressionAttributeValues={
                ":map": { "S": map_name },
            },
            ScanIndexForward=True
        )
    
        games = [Game(item) for item in resp['Items']]
    
        return games
    
    games = find_open_games_by_map("Green Grasslands")
    for game in games:
        print(game)

    В вашем приложении может использоваться функция, подобная функции find_open_games_by_map из приведенного выше скрипта. Она принимает имя карты и выполняет запрос к индексу OpenGamesIndex, чтобы найти все открытые игры по карте. Затем она собирает возвращенные сущности в объекты Game, которые можно использовать в приложении.

    Выполните скрипт, запустив указанную ниже команду в терминале.

    python application/find_open_games_by_map.py

    В терминале будет выведена следующая информация о четырех открытых играх на карте Green Grasslands.

    Open games for Green Grasslands:
    Game<14c7f97e-8354-4ddf-985f-074970818215 -- Green Grasslands>
    Game<3d4285f0-e52b-401a-a59b-112b38c4a26b -- Green Grasslands>
    Game<683680f0-02b0-4e5e-a36a-be4e00fc93f3 -- Green Grasslands>
    Game<0ab37cf1-fc60-4d93-b72b-89335f759581 -- Green Grasslands>
    sudo cp -r wordpress/* /var/www/html/

    На следующем этапе мы воспользуемся API Scan, чтобы просканировать разреженный вторичный индекс.

  • Этап 4. Сканирование разреженного вторичного индекса

    На предыдущем этапе мы узнали, как находить игры для определенной карты. Это полезно, потому что некоторые игроки предпочитают играть на конкретной карте. Другие же могут проводить игру на любой. В этом разделе мы расскажем, как найти любую открытую игру в приложении, независимо от типа карты. Для этого воспользуемся API Scan.

    В целом, нет необходимости проектировать таблицу для использования операции DynamoDB Scan, потому что DynamoDB предназначена для точечных запросов, отбирающих именно те сущности, которые вам нужны. Операция Scan выбирает в таблице случайную коллекцию сущностей, поэтому для поиска нужных сущностей может потребоваться несколько обращений к базе данных.

    Но иногда операция Scan может оказаться полезной. В нашем случае имеется разреженный вторичный индекс, поэтому в нем не будет слишком много сущностей. Кроме того, в индексе находятся только открытые игры, а именно это нам и нужно.

    В этом сценарии использования операция Scan действует очень эффективно. Давайте посмотрим, как она работает. В загруженном коде в каталоге application/ находится файл find_open_games.py. Этот файл содержит следующий код:

    import boto3
    
    from entities import Game
    
    dynamodb = boto3.client('dynamodb')
    
    def find_open_games():
        resp = dynamodb.scan(
            TableName='battle-royale',
            IndexName="OpenGamesIndex",
        )
    
        games = [Game(item) for item in resp['Items']]
    
        return games
    
    games = find_open_games()
    print("Open games:")
    for game in games:
        print(game)

    Этот код подобен коду, описанному на предыдущем этапе. Но вместо метода query() клиента DynamoDB мы используем метод scan(). Поскольку мы используем scan(), не требуется указывать данные об условиях ключа, как это делалось в случае query(). DynamoDB просто возвращает нам набор результатов без определенного порядка.

    Запустите скрипт следующей командой в терминале.

    python application/find_open_games.py

    В терминале будет выведен список из девяти открытых игр на различных картах.

    Open games:
    Game<c6f38a6a-d1c5-4bdf-8468-24692ccc4646 -- Urban Underground>
    Game<d06af94a-2363-441d-a69b-49e3f85e748a -- Dirty Desert>
    Game<873aaf13-0847-4661-ba26-21e0c66ebe64 -- Dirty Desert>
    Game<fe89e561-8a93-4e08-84d8-efa88bef383d -- Dirty Desert>
    Game<248dd9ef-6b17-42f0-9567-2cbd3dd63174 -- Juicy Jungle>
    Game<14c7f97e-8354-4ddf-985f-074970818215 -- Green Grasslands>
    Game<3d4285f0-e52b-401a-a59b-112b38c4a26b -- Green Grasslands>
    Game<683680f0-02b0-4e5e-a36a-be4e00fc93f3 -- Green Grasslands>
    Game<0ab37cf1-fc60-4d93-b72b-89335f759581 -- Green Grasslands>
    

    На этом этапе мы убедились, что в определенных случаях операция Scan может оказаться правильным выбором. Мы воспользовались операцией Scan для получения набора сущностей из разреженного вторичного индекса, чтобы показать игрокам открытые игры.

    На следующих этапах мы реализуем два шаблона доступа:

    • присоединить пользователя к игре (запись)
    • начать игру (запись)

    Чтобы реализовать шаблон доступа «Присоединить пользователя к игре» на следующих этапах, мы собираемся воспользоваться транзакциями DynamoDB. Транзакции часто используются в реляционных системах для проведения операций, которые одновременно затрагивают множество элементов данных. Допустим, вы управляете банком. Один клиент, Алехандра, переводит 100 USD другому клиенту, Ане. Для записи этой банковской транзакции вы использовали бы транзакцию базы данных, чтобы применить изменения к обоим счетам клиентов, а не только к одному.

    Транзакции DynamoDB упрощают построение приложений, которые изменяют несколько объектов за одну операцию. С помощью транзакций можно обработать до 10 объектов в одном запросе.

    В вызове API TransactWriteItem можно использовать указанные ниже операции.

    • Put – для вставки или перезаписи объекта.
    • Update – для обновления существующего объекта.
    • Delete – для удаления объекта.
    • ConditionCheck – для проверки условия относительно существующего объекта без изменения этого объекта.

     

    На следующем этапе мы воспользуемся транзакцией DynamoDB, чтобы добавить новых пользователей в игру, при этом предотвращая ее переполнение.

  • Этап 5. Добавление пользователей в игру

    Первый шаблон доступа, который мы реализуем в этом модуле, – добавление новых пользователей в игру.

    При добавлении новых пользователей в игру нам необходимо:

    • убедиться, что в игре еще не набралось 50 игроков (в каждой игре может быть максимум 50 игроков);
    • убедиться, что пользователь еще не добавлен в игру;
    • создать сущность UserGameMapping, чтобы добавить пользователя в игру;
    • увеличить на единицу атрибут people в сущности Game, чтобы отслеживать количество игроков в игре.

    Обратите внимание, что для выполнения этих задач необходимо провести операции записи в существующую сущность Game и новую сущность UserGameMapping, а также задать условную логику для каждой сущности. Это оптимальная операция для транзакций DynamoDB, потому что одним запросом требуется обработать несколько сущностей и нужно, чтобы запрос был либо выполнен, либо не выполнен целиком.

    В загруженном коде в каталоге application/ находится скрипт join_game.py. Функция в этом скрипте использует транзакцию DynamoDB, чтобы добавить нового пользователя в игру.

    Этот скрипт содержит следующий код:

    import boto3
    
    from entities import Game, UserGameMapping
    
    dynamodb = boto3.client('dynamodb')
    
    GAME_ID = "c6f38a6a-d1c5-4bdf-8468-24692ccc4646"
    USERNAME = 'vlopez'
    
    
    def join_game_for_user(game_id, username):
        try:
            resp = dynamodb.transact_write_items(
                TransactItems=[
                    {
                        "Put": {
                            "TableName": "battle-royale",
                            "Item": {
                                "PK": {"S": "GAME#{}".format(game_id) },
                                "SK": {"S": "USER#{}".format(username) },
                                "game_id": {"S": game_id },
                                "username": {"S": username }
                            },
                            "ConditionExpression": "attribute_not_exists(SK)",
                            "ReturnValuesOnConditionCheckFailure": "ALL_OLD"
                        },
                    },
                    {
                        "Update": {
                            "TableName": "battle-royale",
                            "Key": {
                                "PK": { "S": "GAME#{}".format(game_id) },
                                "SK": { "S": "#METADATA#{}".format(game_id) },
                            },
                            "UpdateExpression": "SET people = people + :p",
                            "ConditionExpression": "people <= :limit",
                            "ExpressionAttributeValues": {
                                ":p": { "N": "1" },
                                ":limit": { "N": "50" }
                            },
                            "ReturnValuesOnConditionCheckFailure": "ALL_OLD"
                        }
                    }
                ]
            )
            print("Added {} to game {}".format(username, game_id))
            return True
        except Exception as e:
            print("Could not add user to game")
    
    join_game_for_user(GAME_ID, USERNAME)

    В функции join_game_for_user этого скрипта метод transact_write_items() выполняет транзакцию записи. В этой транзакции – две операции.

    В первой операции транзакции используется операция Put для вставки новой сущности UserGameMapping. В этой операции указано условие: для данной сущности не должен существовать атрибут SK. Это гарантирует, что сущности с данным PK и SK еще нет. Если же такая сущность уже существует, это значит, что данный пользователь уже присоединился к игре.

    Вторая операция – это операция Update над сущностью Game, которая увеличивает значение атрибута people на единицу. В ходе этой операции мы добавляем проверку условия: не превышает ли текущее значение атрибута people ограничение в 50 человек. Когда к игре присоединятся 50 человек, она будет заполнена и готова к началу.

    Запустите скрипт следующей командой в терминале.

    python application/join_game.py

    Вывод в терминале должен указывать, что в игру добавлен пользователь.

    Added vlopez to game c6f38a6a-d1c5-4bdf-8468-24692ccc4646

    Обратите внимание, что если вы попытаетесь запустить скрипт снова, функция не сработает. Пользователь vlopez уже добавлен в игру, поэтому еще одна попытка добавить пользователя не удовлетворяет указанным нами условиям.

    Добавление транзакций DynamoDB очень упрощает рабочий процесс выполнения сложных операций, подобных этой. Без транзакций потребовалось бы несколько вызовов API со сложными условиями, а в случае конфликтов пришлось бы вручную выполнять откаты. Теперь, чтобы реализовать такие сложные операции, нам потребуется не более 50 строк кода.

    На следующем этапе мы реализуем шаблон доступа «Начать игру (запись)».

  • Этап 6. Начало игры

    Когда в игре наберется 50 человек, ее создатель может начать игру, чтобы инициировать игровой процесс. На этом этапе мы покажем, как реализовать данный шаблон доступа.

    Когда серверная часть нашего приложения получает запрос на начало игры, мы проверяем три условия:

    • в игру добавлено 50 человек;
    • запрос подан создателем игры;
    • игра еще не начата.

    Каждую проверку можно обработать в условном выражении в запросе на обновление игры. Если все проверки пройдены, нам нужно обновить сущность следующим образом:

    • удалить атрибут open_timestamp, чтобы исключить игру из разреженного вторичного индекса из предыдущего модуля, поскольку она больше не открыта;
    • добавить атрибут start_time, чтобы указать, когда началась игра.

    В загруженном коде в каталоге application/ находится скрипт start_game.py. Этот файл содержит следующий код:

    import datetime
    
    import boto3
    
    from entities import Game
    
    dynamodb = boto3.client('dynamodb')
    
    GAME_ID = "c6f38a6a-d1c5-4bdf-8468-24692ccc4646"
    CREATOR = "gstanley"
    
    def start_game(game_id, requesting_user, start_time):
        try:
            resp = dynamodb.update_item(
                TableName='battle-royale',
                Key={
                    "PK": { "S": "GAME#{}".format(game_id) },
                    "SK": { "S": "#METADATA#{}".format(game_id) }
                },
                UpdateExpression="REMOVE open_timestamp SET start_time = :time",
                ConditionExpression="people = :limit AND creator = :requesting_user AND attribute_not_exists(start_time)",
                ExpressionAttributeValues={
                    ":time": { "S": start_time.isoformat() },
                    ":limit": { "N": "50" },
                    ":requesting_user": { "S": requesting_user }
                },
                ReturnValues="ALL_NEW"
            )
            return Game(resp['Attributes'])
        except Exception as e:
            print('Could not start game')
            return False
    
    game = start_game(GAME_ID, CREATOR, datetime.datetime(2019, 4, 16, 10, 15, 35))
    
    if game:
        print("Started game: {}".format(game))

    В вашем приложении может использоваться функция, подобная функции start_game из этого скрипта. Она принимает аргументы game_id, requesting_user и start_time и выполняет запрос на обновление сущности Game, чтобы начать игру.

    В параметре ConditionExpression в вызове update_item() указаны все три проверки, перечисленные выше на этом этапе: в игре должно участвовать 50 человек, запросить ее начало должен создатель и игра не может иметь атрибута start_time, который указывал бы, что она уже началась.

    В параметре UpdateExpression вы можете увидеть изменения, которые требуется внести в сущность. Сначала мы удаляем из сущности атрибут open_timestamp, а потом указываем в атрибуте start_time время начала игры.

    Запустите скрипт следующей командой в терминале.

    python application/start_game.py

    В терминале вы увидите вывод, указывающий, что игра начата успешно.

    Started game: Game<c6f38a6a-d1c5-4bdf-8468-24692ccc4646 -- Urban Underground>

    Попробуйте еще раз запустить игру терминале. На этот раз будет выдано сообщение об ошибке, в котором указано, что начать игру не удалось. Так произошло потому, что игра уже началась и атрибут start_time существует. В результате запрос не прошел проверку условия сущности.

    Вы, наверное, помните что сущности Game и соответствующие сущности User связаны отношением «многие ко многим», которое представлено сущностью UserGameMapping.

    Часто требуется опрашивать обе стороны отношения. С помощью текущей настройки первичного ключа можно найти все сущности User в сущности Game. Для поиска всех сущностей Game для сущности User можно создать инвертированный индекс.

    В DynamoDB инвертированный индекс – это вторичный индекс, который является инверсией первичного ключа. Ключ RANGE становится ключом HASH и наоборот. Этот шаблон переворачивает таблицу и дает возможность отправлять запросы с другой стороны отношения «многие ко многим».

    На следующих этапах мы добавим в таблицу инвертированный индекс и покажем, как с его помощью получить все сущности Game для определенной сущности User. 

  • Этап 7. Добавление инвертированного индекса

    На этом этапе мы добавляем в таблицу инвертированный индекс. Инвертированный индекс создается так же, как и любой другой вторичный индекс.

    В загруженном коде скрипт add_inverted_index.py находится в каталоге scripts/. Этот скрипт на языке Python добавляет в таблицу инвертированный индекс.

    Файл содержит следующий код:

    import boto3
    
    dynamodb = boto3.client('dynamodb')
    
    try:
        dynamodb.update_table(
            TableName='battle-royale',
            AttributeDefinitions=[
                {
                    "AttributeName": "PK",
                    "AttributeType": "S"
                },
                {
                    "AttributeName": "SK",
                    "AttributeType": "S"
                }
            ],
            GlobalSecondaryIndexUpdates=[
                {
                    "Create": {
                        "IndexName": "InvertedIndex",
                        "KeySchema": [
                            {
                                "AttributeName": "SK",
                                "KeyType": "HASH"
                            },
                            {
                                "AttributeName": "PK",
                                "KeyType": "RANGE"
                            }
                        ],
                        "Projection": {
                            "ProjectionType": "ALL"
                        },
                        "ProvisionedThroughput": {
                            "ReadCapacityUnits": 1,
                            "WriteCapacityUnits": 1
                        }
                    }
                }
            ],
        )
        print("Table updated successfully.")
    except Exception as e:
        print("Could not update table. Error:")
        print(e)
    

    В этом скрипте мы вызываем метод update_table() клиента DynamoDB. В данном методе мы передаем сведения о вторичном индексе, который требуется создать, в том числе схему ключа индекса, предоставляемую пропускную способность и атрибуты, проекции которых требуется создать в индексе.

    Запустите скрипт, введя в терминале следующую команду.

    python scripts/add_inverted_index.py

    В терминале будет выведено сообщение об успешном создании индекса.

    Table updated successfully.

    На следующем этапе мы воспользуемся индексом, чтобы получить все сущности Game для отдельной сущности User.

  • Этап 8. Получение игр для пользователя

    Создав инвертированный индекс, воспользуемся им, чтобы получить сущности Game для игр, в которые играет User. Для этого необходимо отправить инвертированному индексу запрос, указав сущность User, для которой требуется просмотреть сущности Game.

    В загруженном коде в каталоге application/ находится скрипт find_games_for_user.py. Этот файл содержит следующий код:

    import boto3
    
    from entities import UserGameMapping
    
    dynamodb = boto3.client('dynamodb')
    
    USERNAME = "carrpatrick"
    
    
    def find_games_for_user(username):
        try:
            resp = dynamodb.query(
                TableName='battle-royale',
                IndexName='InvertedIndex',
                KeyConditionExpression="SK = :sk",
                ExpressionAttributeValues={
                    ":sk": { "S": "USER#{}".format(username) }
                },
                ScanIndexForward=True
            )
        except Exception as e:
            print('Index is still backfilling. Please try again in a moment.')
            return None
    
        return [UserGameMapping(item) for item in resp['Items']]
    
    games = find_games_for_user(USERNAME)
    
    if games:
        print("Games played by {}:".format(USERNAME))
        for game in games:
            print(game)
    

    В вашей игре может использоваться функция, подобная функции find_games_for_user() из этого скрипта. Эта функция принимает имя пользователя и возвращает все игры, в которые играет указанный пользователь.

    Запустите скрипт следующей командой в терминале.

    python application/find_games_for_user.py

    Этот скрипт должен вывести все игры, в которые играет пользователь carrpatrick.

    Games played by carrpatrick:
    UserGameMapping<25cec5bf-e498-483e-9a00-a5f93b9ea7c7 -- carrpatrick -- SILVER>
    UserGameMapping<c6f38a6a-d1c5-4bdf-8468-24692ccc4646 -- carrpatrick>
    UserGameMapping<c9c3917e-30f3-4ba4-82c4-2e9a0e4d1cfd -- carrpatrick>
    

    В этом модуле мы добавили в таблицу вторичный индекс. Таким образом мы реализовали два дополнительных шаблона доступа:

    • найти открытые игры по карте (чтение);
    • найти открытые игры (чтение).

    Для этого мы использовали разреженный индекс, в который были включены только те игры, которые еще открыты для добавления игроков. Затем мы использовали API Query и Scan, чтобы найти в индексе открытые игры.

    Также мы увидели, как реализовать в приложении две расширенных операции записи. Во-первых, мы воспользовались транзакциями DynamoDB, когда пользователь присоединился к игре. С помощью транзакций мы осуществили запись с составным условием в несколько сущностей за один запрос.

    Во-вторых, мы реализовали функцию, с помощью которой создатель игры может начать ее, когда она к этому готова. В этом шаблоне доступа мы использовали операцию обновления, в которой требовалось проверить значение трех атрибутов и обновить значения двух атрибутов. Такую сложную логику можно выразить в одном запросе благодаря эффективности условных выражений и выражений обновления.

    В-третьих, мы реализовали последний шаблон доступа, получив все сущности Game для сущности User. Для реализации этого шаблона мы создали вторичный индекс по шаблону инвертированного индекса, чтобы можно было опрашивать отношение «многие ко многим» между сущностями User и сущностями Game с другой стороны.

    В следующем модуле мы очищаем созданные ресурсы.