Итак, мы реализовали шаблоны доступа во время создания и извлечения базовых элементов в мобильном приложении, таких как Пользователи и Фотографии. Мы также научились использовать инвертированный индекс для включения дополнительных шаблонов запросов по объектам.

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

  • Реакция на фотографию (Запись)
  • Подписка на друзей (Запись)

Обратите внимание, что оба эти шаблона доступа записывают данные в базу DynamoDB в отличие от шаблонов, предназначенных в основном для чтения, которые мы делали до сих пор.

Чтобы реализовать оба шаблона доступа в шагах, приведенных ниже, мы используем транзакции ACID базы данных DynamoDB. Транзакции DynamoDB выпущены в ноябре 2018 г. Давайте быстро ознакомимся с принципами работы транзакций в базе данных DynamoDB.

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

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

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

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

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

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


  • Шаг 1. Реакция на фотографию

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

    При добавлении реакции пользователя на фотографию требуется выполнить несколько действий:

    • Подтвердите, что пользователь еще не использовал этот тип реакции для этой фотографии.
    • Создайте новый объект Реакция , чтобы сохранить реакцию.
    • Увеличьте значение необходимого типа реакции в свойстве реакций объекта Фотография , чтобы отобразить подробные сведения о реакции на фотографии.

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

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

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

    import datetime
    
    import boto3
    
    dynamodb = boto3.client('dynamodb')
    
    REACTING_USER = 'kennedyheather'
    REACTION_TYPE = 'sunglasses'
    PHOTO_USER = 'ppierce'
    PHOTO_TIMESTAMP = '2019-04-14T08:09:34'
    
    
    def add_reaction_to_photo(reacting_user, reaction_type, photo_user, photo_timestamp):
        reaction = "REACTION#{}#{}".format(reacting_user, reaction_type)
        photo = "PHOTO#{}#{}".format(photo_user, photo_timestamp)
        user = "USER#{}".format(photo_user)
        try:
            resp = dynamodb.transact_write_items(
                TransactItems=[
                    {
                        "Put": {
                            "TableName": "quick-photos",
                            "Item": {
                                "PK": {"S": reaction},
                                "SK": {"S": photo},
                                "reactingUser": {"S": reacting_user},
                                "reactionType": {"S": reaction_type},
                                "photo": {"S": photo},
                                "timestamp": {"S": datetime.datetime.now().isoformat() }
                            },
                            "ConditionExpression": "attribute_not_exists(SK)",
                            "ReturnValuesOnConditionCheckFailure": "ALL_OLD"
                        },
                    },
                    {
                        "Update": {
                            "TableName": "quick-photos",
                            "Key": {"PK": {"S": user}, "SK": {"S": photo}},
                            "UpdateExpression": "SET reactions.#t = reactions.#t + :i",
                            "ExpressionAttributeNames": {
                                "#t": reaction_type
                            },
                            "ExpressionAttributeValues": {
                                ":i": { "N": "1" },
                            },
                            "ReturnValuesOnConditionCheckFailure": "ALL_OLD"
                        }
                    }
                ]
            )
            print("Added {} reaction from {}".format(reaction_type, reacting_user))
            return True
        except Exception as e:
            print("Could not add reaction to photo")
    
    add_reaction_to_photo(REACTING_USER, REACTION_TYPE, PHOTO_USER, PHOTO_TIMESTAMP)
    

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

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

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

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

    python application/add_reaction.py

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

    Added sunglasses reaction from kennedyheather

    Обратите внимание, что если вы попытаетесь запустить скрипт снова, функция не сработает. Пользователь kennedyheather уже добавил эту реакцию к этой фотографии, поэтому попытка сделать это повторно нарушит выражение условия в операции создания объекта Реакция. Другими словами, функция идемпотентна, и ее повторные вызовы с одними и теми же входами не будут иметь непредвиденных последствий.

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

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

  • Шаг 2. Подписка на пользователя

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

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

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

    import datetime
    
    import boto3
    
    dynamodb = boto3.client('dynamodb')
    
    FOLLOWED_USER = 'tmartinez'
    FOLLOWING_USER = 'john42'
    
    
    def follow_user(followed_user, following_user):
        user = "USER#{}".format(followed_user)
        friend = "#FRIEND#{}".format(following_user)
        user_metadata = "#METADATA#{}".format(followed_user)
        friend_user = "USER#{}".format(following_user)
        friend_metadata = "#METADATA#{}".format(following_user)
        try:
            resp = dynamodb.transact_write_items(
                TransactItems=[
                    {
                        "Put": {
                            "TableName": "quick-photos",
                            "Item": {
                                "PK": {"S": user},
                                "SK": {"S": friend},
                                "followedUser": {"S": followed_user},
                                "followingUser": {"S": following_user},
                                "timestamp": {"S": datetime.datetime.now().isoformat()},
                            },
                            "ConditionExpression": "attribute_not_exists(SK)",
                            "ReturnValuesOnConditionCheckFailure": "ALL_OLD",
                        }
                    },
                    {
                        "Update": {
                            "TableName": "quick-photos",
                            "Key": {"PK": {"S": user}, "SK": {"S": user_metadata}},
                            "UpdateExpression": "SET followers = followers + :i",
                            "ExpressionAttributeValues": {":i": {"N": "1"}},
                            "ReturnValuesOnConditionCheckFailure": "ALL_OLD",
                        }
                    },
                    {
                        "Update": {
                            "TableName": "quick-photos",
                            "Key": {"PK": {"S": friend_user}, "SK": {"S": friend_metadata}},
                            "UpdateExpression": "SET following = following + :i",
                            "ExpressionAttributeValues": {":i": {"N": "1"}},
                            "ReturnValuesOnConditionCheckFailure": "ALL_OLD",
                        }
                    },
                ]
            )
            print("User {} is now following user {}".format(following_user, followed_user))
            return True
        except Exception as e:
            print(e)
            print("Could not add follow relationship")
    
    follow_user(FOLLOWED_USER, FOLLOWING_USER)

    Функция follow_user в файле аналогична функции, которая содержалась бы в приложении. Она использует два имени – для подписчика и объекта подписки соответственно – и выполняет запрос на создание объекта Друзья и обновление двух объектов Пользователь.

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

    python application/follow_user.py

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

    User john42 is now following user tmartinez

    Попробуйте еще раз запустить игру в терминале. Отобразится сообщение об ошибке и невозможности добавить отношение подписки. Причина заключается в том, что этот пользователь уже подписан на запрашиваемого пользователя и запрос не прошел проверку условия на объекте.

  • Выводы

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

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

    В следующем модуле мы удалим созданные ресурсы и рассмотрим другие темы схемы обучения DynamoDB.