到目前為止,我們已完成在行動應用程式中建立和擷取核心實體 (例如使用者相片) 的存取模式。此外,我們還了解了如何使用反向索引,以在實體上啟用其他查詢模式。

在此單元中,我們將完成兩種存取模式:

  • 對相片做出回應 (寫入)
  • 關注朋友 (寫入)

請注意,這兩種存取模式都在將資料寫入 DynamoDB,這與到目前為止我們已經完成的讀取繁重模式不同

為了完成以下步驟中的兩種存取方式,我們將使用 DynamoDB ACID 交易。DynamoDB 交易已於 2018 年 11 月發佈。 我們來快速了解 DynamoDB 中的交易如何運作。

交易在關聯式系統中很常見,因為操作會一次影響多個資料元素。例如,假設您正在經營一間銀行。一位客戶 Karen 將 100 USD 轉帳給另一位客戶 Sujith。在記錄此交易時,您將使用交易來確保將變更套用於兩位客戶的餘額,而不僅僅是一個。

透過新增 DynamoDB 交易,可以更輕鬆地建置需要將多個項目更改為單一操作一部份的應用程式。使用 DynamoDB 交易,您可以將多個項目作為交易請求的一部份,處理多達 10 個項目。

TransactWriteItem API 叫用中,可以使用以下幾種操作。

  • 放置:用於插入或覆寫項目;
  • 更新:用於更新現有項目;
  • 刪除:用於刪除項目;
  • 條件檢查:用於在不變更項目的情況下對現有項目聲明條件。

在下面的步驟中,我們將以兩種方式使用 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() 方法來執行寫入交易。我們的交易有兩個操作。

    首先,我們執行放置操作,以插入新的回應實體。作為該操作的一部份,我們指定了一個條件,該項目的 SK 屬性不應存在。這樣可以確保具有此 PKSK 的項目。如果確實如此,則意味著使用者已經將該回應新增至此相片。

    第二個操作是對使用者實體的更新操作,以新增動作屬性圖中的回應類型。DynamoDB 強大的更新表達式可讓您執行原子增量而無需先擷取該項目,然後對其進行更新。

    在終端機中使用以下命令執行此指令碼。

    python application/add_reaction.py

    終端機中的輸出應表明該回應已新增至相片。

    Added sunglasses reaction from kennedyheather

    請注意,若您嘗試再次執行該指令碼,則該函數將失敗。使用者 kennedyheather 已經將該回應新增至此相片,因此再次嘗試執行此操作將違反建立回應實體操作中的條件表達式。換言之,該函數為冪等,並且使用相同的輸入重複叫用,而不會產生非預期後果。

    新增 DynamoDB 交易大大簡化了圍繞此類複雜操作的工作流程。之前,這將需要具有復雜條件的多個 API 叫用,並在發生衝突時手動復原。現在可以使用不到 50 行的程式碼來實現。

    在下一步中,我們將介紹如何處理「關注使用者」存取模式。

  • 步驟 2:關注使用者

    在您的應用程式中,一位使用者可以關注另一位使用者。當應用程式後端收到關注使用者的請求時,我們需要做四件事:

    • 檢查關注使用者是否尚未關注請求的使用者;
    • 建立朋友關係實體以記錄以下關係;
    • 增加被關注使用者的關注者數量;
    • 為使用者關注增加關注計數。

    在您下載的程式碼中,名稱為 follow_user.py 的檔案位於 application/ 目錄。該檔案的內容如下:

    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 學習路徑中了解一些後續步驟。