Finora abbiamo rispettato i modelli di accesso per la creazione e il recupero di entità essenziali nella nostra applicazione mobile, come Utenti e Foto. Abbiamo inoltre imparato a utilizzare un indice inverso per abilitare di query aggiuntivi nelle nostre entità.

In questo modulo rispetteremo due modelli di accesso:

  • Metti una reazione a una foto (Scrivi)
  • Segui amico (Scrivi)

Tieni presente che entrambi i modelli di accesso scrivono dati in DynamoDB, diversamente dai modelli particolarmente gravosi in lettura eseguiti finora

Per rispettare entrambi i modelli di accesso nelle fasi seguenti, andremo a utilizzare le transazioni ACID di DynamoDB. Le transazioni di DynamoDB sono state rilasciate a novembre 2018. Vediamo rapidamente come funzionano le transazioni in DynamoDB.

Le transazioni sono molto utilizzate nei sistemi relazionali per le operazioni che riguardano tanti elementi con più dati allo stesso tempo. Ad esempio, immagina di gestire una banca. Una cliente, Karen, invia 100 USD a un'altra cliente, Sujith. Al momento di registrare la transazione, utilizzeresti una transazione per assicurarti che le modifiche vengano applicate al bilancio di entrambe le clienti e non solo a una delle due.

L'aggiunta di transazioni a DynamoDB facilita la creazione di applicazioni laddove devi modificare più elementi come parte di un'unica operazione. Con le transazioni DynamoDB, puoi lavorare su un massimo di 10 elementi per una singola richiesta di transazione.

In una chiamata API TransactWriteItem, puoi utilizzare le seguenti operazioni.

  • Put: per inserire o sovrascrivere un elemento;
  • Update: per aggiornare un elemento pre-esistente;
  • Delete: per rimuovere un elemento.
  • ConditionCheck: per controllare la condizione di un elemento già esistente senza alterarlo.

Nelle fasi seguenti, utilizzeremo una transazione DynamoDB in due modi per gestire operazioni complesse.

Tempo necessario per completare il modulo: 20 minuti


  • Fase 1: Reazione a una foto

    Il primo modello di accesso al quale facciamo riferimento in questo modulo è la reazione a una foto.

    Quando si aggiunge una reazione a una foto di un utente, è necessarie compiere alcune operazioni:

    • Confermare che l'utente non abbia già utilizzato questo tipo di reazione nella foto
    • Creare una nuova entità Reazione per archiviare la reazione
    • Incrementare il tipo di reazione appropriato nella proprietà reactions nell'entità Foto in modo da poter visualizzare i dettagli della reazione in una foto

    Tieni presente che per questa operazione sono richieste azioni di scrittura in due voci diverse, l'entità Foto e la nuova entità Reazione, nonché la logica condizionale per una delle voci. Questo tipo di operazione si adatta perfettamente alle transazioni DynamoDB.

    Nel codice scaricato è presente uno script nella directory application/ denominato add_reaction.py nel quale è inclusa una funzione per aggiungere una reazione a una foto. La funzione nello script utilizza una transazione DynamoDB per aggiungere una reazione.

    I contenuti del file sono i seguenti:

    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)
    

    Nella funzione add_reaction_to_photo utilizziamo il metodo transact_write_items() per eseguire una transazione in scrittura. La nostra transazione consiste in due operazioni.

    In primo luogo, eseguiamo un'operazione Put per inserire una nuova entità Reaction. Come parte di tale operazione, specifichiamo che l'attributo SK non dovrebbe esistere per questo elemento. Questo modo assicura che una voce con questi PK e SK non esista già. In caso contrario, significherebbe che la reazione è già stata aggiunta alla foto dall'utente.

    La seconda è un'operazione Update nell'entità Utente per incrementare il tipo di reazione nella mappa degli attributi reactions. Le espressioni di aggiornamento avanzate di DynamoDB ti consentono di eseguire incrementi eccezionali senza dover recuperare prima la voce e poi aggiornarla.

    Esegui lo script nel terminale con il comando seguente.

    python application/add_reaction.py

    L'output nel tuo terminale dovrebbe indicare che la reazione è stata aggiunta alla foto.

    Added sunglasses reaction from kennedyheather

    Ricorda che, se proverai a eseguire nuovamente lo script, la funzione fallirà. L'utente kennedyheather ha già aggiunto questa reazione alla foto, pertanto tentare di ripetere l'operazione violerebbe l'espressione della condizione nell'operazione per creare l'entità Reazione. In altre parole, la funzione è idempotente e le sue invocazioni ripetute con gli stessi input non comporteranno conseguenze indesiderate.

    L'aggiunta delle transazioni DynamoDB semplifica enormemente il flusso di lavoro delle operazioni complesse come queste. In precedenza, questo avrebbe richiesto più chiamate API con condizioni complesse e rollback manuali in caso di conflitti. Adesso è possibile l'implementazione con meno di 50 righe di codice.

    Nella fase successiva, vedremo come gestire il nostro modello di accesso "Segui utente".

  • Fase 2: Seguire un utente

    Nell'applicazione, un utente può seguirne un altro. Quando il back-end dell'applicazione riceve una richiesta per seguire un utente, dobbiamo eseguire quattro operazioni:

    • verificare che l'utente follower non stia già seguendo l'utente richiesto;
    • creare un'entità Amicizia per registrare la relazione seguente;
    • incrementare il numero di follower per l'utente che viene seguito;
    • incrementare il numero dei seguiti per l'utente follower.

    Nel codice che hai scaricato, nella directory application/ è presente un file denominato follow_user.py. I contenuti del file sono i seguenti:

    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)

    La funzione follow_user nel file è simile a qualunque funzione della tua applicazione. Sono richiesti due nome utente, uno dell'utente seguito e uno dell'utente follower, e viene eseguita una richiesta per creare un'entità Amicizia e aggiornare due entità Utente.

    Esegui lo script nel tuo terminale con il comando seguente.

    python application/follow_user.py

    Dovresti vedere nel tuo terminale l'output che indica che l'operazione è completata.

    User john42 is now following user tmartinez

    Prova a eseguire lo script una seconda volta nel terminale. Questa volta, dovresti ricevere un messaggio di errore che indica che non è stato possibile aggiungere la relazione per seguire gli utenti. Ciò è dovuto al fatto che l'utente adesso segue l'utente richiesto, quindi la richiesta ha avuto esito negativo durante la verifica condizionale effettuata sulla voce.

  • Conclusioni

    In questo modulo abbiamo visto come soddisfare due operazioni di scrittura avanzate nell'applicazione. Prima, abbiamo utilizzato le transazioni DynamoDB per far mettere a un utente la reazione a una foto. Con le transazioni, abbiamo gestito la scrittura condizionale complessa su più elementi in una sola richiesta. Abbiamo inoltre visto come utilizzare espressioni di aggiornamento di DynamoDB per incrementare un attributo nidificato in una proprietà mappa.

    Secondariamente, abbiamo implementato la funzione per un utente in modo che questo ne segua un altro. A tal fine abbiamo dovuto modificare tre voci in una singola richiesta, eseguendo al contempo una verifica condizionale su una delle voci. Sebbene questa sia solitamente considerata un'operazione difficile, con DynamoDB è possibile gestirla semplicemente tramite le transazioni DynamoDB.

    Nel prossimo modulo eseguiremo la pulizia delle risorse create e vedremo alcune fasi successive del nostro percorso di apprendimento di DynamoDB.