Uno de los desafíos más grandes para los usuarios que son nuevos en DynamoDB y NoSQL es cómo modelar datos para filtrar a través de un conjunto de datos completo. Por ejemplo, en nuestro juego, necesitamos encontrar sesiones de juego con espacios disponibles para que podamos mostrar a los usuarios a qué sesión de juego pueden unirse.
En una base de datos relacional, únicamente hay que escribir código SQL para consultar los datos.

SELECT * FROM games
	WHERE status = “OPEN”

DynamoDB puede filtrar resultados en una operación Query o Scan, pero DynamoDB no funciona como una base de datos relacional. Se aplica un filtro de DynamoDB después de recuperar los elementos iniciales que coinciden con la operación de Query o Scan. El filtro reduce el tamaño de la carga útil enviada desde el servicio de DynamoDB, pero el número de elementos recuperados inicialmente está sujeto a los límites de tamaño de DynamoDB.

Afortunadamente, hay varias formas en que puede permitir consultas filtradas en su conjunto de datos en DynamoDB. Para proporcionar filtros eficientes en su tabla de DynamoDB, debe planificar los filtros en el modelo de datos de su tabla desde el principio. Recuerde la lección que aprendimos en el segundo módulo de este laboratorio: considere sus patrones de acceso y luego diseñe su tabla.

En los siguientes pasos, usamos un índice secundario global para encontrar juegos abiertos. Específicamente, utilizaremos la técnica de índice disperso para manejar este patrón de acceso.

Tiempo para completar el módulo: 40 minutos


  • Paso 1: Modelar un índice secundario disperso

    Los índices secundarios son herramientas de modelado de datos cruciales en DynamoDB. Le permiten remodelar sus datos para permitir patrones de consulta alternativos. Para crear un índice secundario, especifique la clave primaria del índice, al igual que cuando creó una tabla anteriormente. Tenga en cuenta que la clave primaria para un índice secundario global no tiene que ser única para cada elemento. Luego, DynamoDB copia elementos en el índice en función de los atributos especificados y puede consultarlo tal como lo hace en la tabla.

    El uso de índices secundarios dispersos es una estrategia avanzada en DynamoDB. Con los índices secundarios, DynamoDB copia elementos de la tabla original solo si tienen los elementos de la clave primaria en el índice secundario. Los elementos que no tienen los elementos clave principales no se copian, razón por la cual estos índices secundarios se denominan “dispersos”.

    Veamos cómo nos afecta esto. Quizá recuerde que tenemos dos patrones de acceso para encontrar juegos abiertos:

    • Buscar juegos abiertos (lectura)
    • Buscar juegos abiertos por mapa (Leer)

    Podemos crear un índice secundario mediante una clave primaria compuesta donde la clave HASH es el atributo Map para el juego y la clave RANGE es el atributo open_timestamp para el juego, que indica la hora en que se abrió el juego.

    La parte importante para nosotros es que cuando un juego se cumple, el atributo open_timestamp se elimina. Cuando se elimina el atributo, el juego completo se elimina del índice secundario porque no tiene un valor para el atributo clave RANGE. Esto es lo que mantiene disperso nuestro índice: incluye solo juegos abiertos que tienen el atributo open_timestamp.

    En el siguiente paso, creamos el índice secundario.

  • Paso 2: Crear un índice secundario disperso

    En este paso, creamos el índice secundario disperso para juegos abiertos (juegos que aún poseen espacios disponibles).

    Crear un índice secundario es similar a crear una tabla. En el código que descargó, encontrará un archivo de script en el directorio scripts/ denominado add_secondary_index.py. El contenido de ese archivo es el siguiente.

    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)

    Siempre que los atributos se usen en una clave primaria para una tabla o índice secundario, se deben definir en AttributeDefinitions. Luego, creamos un nuevo índice secundario en la propiedad GlobalSecondaryIndexUpdates. Para este índice secundario, especificamos el nombre del índice, el esquema de la clave primaria, el rendimiento aprovisionado y los atributos que queremos proyectar.

    Tenga en cuenta que no tuvimos que especificar que nuestro índice secundario está destinado a utilizarse como un índice disperso. Eso es únicamente una función de los datos que ingresa. Si escribe elementos en su tabla que no tienen los atributos para sus índices secundarios, no se incluirán en su índice secundario.

    Cree su índice secundario mediante la ejecución del siguiente comando.

    python scripts/add_secondary_index.py

    Debería ver el siguiente mensaje en la consola: “Tabla actualizada correctamente”.

    En el siguiente paso, usamos el índice disperso para encontrar juegos abiertos por mapa.

  • Paso 3: Consultar el índice secundario disperso

    Ahora que hemos configurado el índice secundario, utilicémoslo para satisfacer algunos de los patrones de acceso.

    Para usar un índice secundario, tiene dos llamadas a la API disponibles: Query y Scan. Con Query, debe especificar la clave HASH y devolverá un resultado específico. Con Scan, no especifica una clave HASH y la operación se ejecuta en toda la tabla. Los Scan no se recomiendan en DynamoDB, excepto en circunstancias específicas porque acceden a todos los elementos de su base de datos. Si tiene una cantidad significativa de datos en su tabla, el escaneo puede llevar mucho tiempo. En el siguiente paso, le mostramos por qué los Scans pueden ser una herramienta poderosa cuando se usan con índices dispersos.

    Podemos usar la API de Query contra el índice secundario que creamos en el paso anterior para encontrar todos los juegos abiertos por nombre de mapa. El índice secundario está dividido según el nombre del mapa, lo que nos permite hacer consultas específicas para encontrar juegos abiertos.

    En el código que descargó, hay un archivo find_open_games_by_map.py en el directorio application/. El contenido de este script sigue.

    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)

    En el script anterior, la función find_open_games_by_map es similar a una función que tendría en su aplicación. La función acepta un nombre de mapa y realiza una consulta en OpenGamesIndex para encontrar todos los juegos abiertos para el mapa. Luego, ensambla las entidades devueltas en objetos Game que pueden usarse en su aplicación.

    Para ejecutar el script, ejecute el siguiente comando en el terminal.

    python application/find_open_games_by_map.py

    La terminal mostrará la siguiente salida con cuatro juegos abiertos para el mapa de 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/

    En el siguiente paso, usamos la API Scan para escanear el índice secundario disperso.

  • Paso 4: Escanear el índice secundario disperso

    En el paso anterior, vimos cómo encontrar juegos para un mapa en particular. Algunos jugadores prefieren jugar un mapa específico, por lo que esto es útil. Otros jugadores pueden estar dispuestos a jugar un juego en cualquier mapa. En esta sección, mostramos cómo encontrar cualquier juego abierto en la aplicación, independientemente del tipo de mapa. Para hacer esto, utilizamos la API de Scan.

    En general, no debe diseñar su tabla para utilizar la operación de Scan de DynamoDB ya que DynamoDB está diseñado para consultas específicas que captan las entidades exactas que necesita. Una operación de Scan toma una colección aleatoria de entidades en su tabla, por lo que encontrar las entidades que necesita puede requerir múltiples viajes de ida y vuelta a la base de datos.

    Sin embargo, a veces Scan puede ser útil. En nuestra situación, tenemos un índice secundario disperso, lo que significa que nuestro índice no debería tener tantas entidades. Además, el índice incluye solo aquellos juegos que están abiertos y eso es exactamente lo que necesitamos.

    Para este caso de uso, Scan funciona muy bien. Veamos cómo funciona. En el código que descargó, hay un archivo find_open_games.py en el directorio application/. El archivo contiene lo siguiente.

    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)

    Este código es similar al código del paso anterior. Sin embargo, en lugar de usar el método query() en el cliente de DynamoDB, usamos el método scan(). Debido a que estamos usando scan(), no necesitamos especificar nada sobre las condiciones clave como lo hicimos con query(). Solo hacemos que DynamoDB devuelva varios elementos sin un orden específico.

    Ejecute el script con el siguiente comando en su terminal.

    python application/find_open_games.py

    Su terminal debe mostrar una lista de nueve juegos que están abiertos en una variedad de mapas.

    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>
    

    En este paso, vimos cómo usar la operación Scan puede ser la opción correcta en circunstancias específicas. Utilizamos Scan para obtener una variedad de entidades de nuestro índice secundario disperso para mostrar juegos abiertos a los jugadores.

    En los siguientes pasos, cubrimos dos patrones de acceso:

    • Unirse al juego para un usuario (escritura)
    • Comenzar juego (escritura)

    Para satisfacer el patrón de acceso “Unirse al juego para un usuario” en los siguientes pasos, vamos a utilizar las transacciones de DynamoDB. Las transacciones son populares en los sistemas relacionales para operaciones que afectan múltiples elementos de datos a la vez. Por ejemplo, imagine que dirige un banco. Una cliente, Alejandra, transfiere 100 USD a otra cliente, Ana. Cuando registra esta transacción, usaría una transacción para asegurarse de que los cambios se apliquen a los saldos de ambas clientes en lugar de solo una.

    Las transacciones de DynamoDB facilitan la creación de aplicaciones que alteran múltiples elementos como parte de una sola operación. Con las transacciones, puede operar hasta 10 elementos como parte de una sola solicitud de transacción.

    En una llamada a la API TransactWriteItem, puede usar las siguientes operaciones:

    • Put: para insertar o sobrescribir un elemento.
    • Update: para actualizar un elemento existente.
    • Delete: para eliminar un elemento.
    • ConditionCheck: para afirmar una condición en un elemento existente sin alterar el elemento.

     

    En el siguiente paso, usamos una transacción de DynamoDB cuando agregamos nuevos usuarios a un juego mientras evitamos que el juego se sobrecargue.

  • Paso 5: Agregar usuarios a un juego

    El primer patrón de acceso que abordamos en este módulo es agregar nuevos usuarios a un juego.

    Para agregar un nuevo usuario a un juego, necesitamos:

    • Confirmar que todavía no hay 50 jugadores en el juego (cada juego puede tener un máximo de 50 jugadores).
    • Confirmar que el usuario aún no está en el juego.
    • Cree una nueva entidad UserGameMapping para agregar al usuario al juego.
    • Incremente el atributo people en la entidad Game para rastrear cuántos jugadores hay en el juego.

    Tenga en cuenta que lograr todas estas cosas requiere acciones de escritura en la entidad Game existente y la nueva entidad UserGameMapping, así como también la lógica condicional para cada una de las entidades. Este es el tipo de operación que se adapta perfectamente a las transacciones de DynamoDB porque necesita trabajar en varias entidades en la misma solicitud y desea que toda la solicitud tenga éxito o falle conjuntamente.

    En el código que descargó, hay un script join_game.py en el directorio application/. La función en ese script usa una transacción de DynamoDB para agregar un usuario a un juego.

    El contenido del script sigue.

    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)

    En la función join_game_for_user de este script, el método transact_write_items() realiza una transacción de escritura. Esta transacción tiene dos operaciones.

    En la primera operación de la transacción, usamos una operación Put para insertar una nueva entidad de UserGameMapping. Como parte de esa operación, especificamos la condición de que el atributo SK no debe existir para esta entidad. Esto asegura que una entidad con este PK y SK no exista. Si tal entidad ya existiera, eso significaría que este usuario ya se unió al juego.

    La segunda operación es una operación de actualización en la entidad del Game para incrementar el atributo people en uno. Como parte de esta operación, agregamos una verificación condicional de que el valor actual de people no es mayor que 50. Tan pronto como 50 personas se unan al juego, el juego estará completo y listo para comenzar.

    Ejecute este script con el siguiente comando en la terminal.

    python application/join_game.py

    La salida en su terminal debe indicar que el usuario se agregó al juego.

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

    Tenga en cuenta que si intenta ejecutar el script nuevamente, la función fallará. El usuario vlopez ya fue agregado al juego, por lo que intentar agregarlo nuevamente no cubre las condiciones que especificamos.

    La adición de transacciones de DynamoDB simplifica enormemente el flujo de trabajo en torno a operaciones complejas como estas. Sin transacciones, esto habría requerido múltiples llamadas a la API con condiciones complejas y reversiones manuales en caso de conflictos. Ahora, podemos implementar operaciones tan complejas con menos de 50 líneas de código.

    En el siguiente paso, manejamos el patrón de acceso “Comenzar juego (escritura)”.

  • Paso 6: Comenzar un juego

    Tan pronto como un juego tenga 50 usuarios, el creador del juego puede comenzar el juego para iniciar la partida. En este paso, mostramos cómo manejar este patrón de acceso.

    Cuando el backend de nuestra aplicación recibe una solicitud para comenzar el juego, verificamos tres cosas:

    • Que el juego tenga 50 personas registradas.
    • Que el usuario solicitante sea el creador del juego.
    • Que el juego aún no haya comenzado.

    Podemos manejar cada una de estas comprobaciones en una expresión de condición en una solicitud para actualizar el juego. Si todas estas condiciones se cumplen, necesitamos actualizar nuestra entidad de las siguientes maneras:

    • Elimine el atributo open_timestamp para que no aparezca como un juego abierto en el índice secundario disperso del módulo anterior.
    • Agrega un atributo start_time para indicar cuándo comenzó el juego.

    En el código que descargó, hay un script start_game.py en el directorio application/. El archivo contiene lo siguiente.

    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))

    En este script, la función start_time es similar a la función que tendría en su aplicación. Necesita un game_id, requesting_user, and start_time y ejecuta una solicitud para actualizar la entidad Game para iniciar el juego.

    El parámetro ConditionExpression en la llamadaupdate_item() especifica cada una de las tres comprobaciones que enumeramos anteriormente en este paso: el juego debe tener 50 personas, el usuario solicita que el inicio del juego sea el creador del juego y el juego no puede tener un atributo start_time, que indicaría que ya comenzó.

    En el parámetro UpdateExpression, puede ver los cambios que queremos realizar en nuestra entidad. Primero eliminamos el atributo open_timestamp de la entidad y luego establecemos el atributo start_time la hora de inicio del juego.

    Ejecute este script en su terminal con el siguiente comando.

    python application/start_game.py

    Debería ver los resultados en su terminal que indican que el juego se inició correctamente.

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

    Intente ejecutar el script por segunda vez en su terminal. Esta vez, debería ver un mensaje de error que indica que no pudo iniciar el juego. Esto se debe a que ya ha comenzado el juego, por lo que existe el atributo start_time. Como resultado, la solicitud falló la verificación condicional de la entidad.

    Recuerde que existe una relación de muchos a muchos entre la entidad Game y las entidades User asociadas y la relación está representada por una entidad UserGameMapping.

    A menudo, desea consultar ambos lados de una relación. Con nuestra configuración de clave principal, podemos encontrar todas las entidades de User en un Game. Podemos habilitar la consulta de todas las entidades Game para un User mediante el uso de un índice invertido.

    En DynamoDB, un índice invertido es un índice secundario que es el inverso de su clave primaria. La clave RANGE se convierte en su clave HASH y viceversa. Este patrón invierte los roles y le permite consultar en el otro lado de sus relaciones de muchos a muchos.

    En los siguientes pasos, agregamos un índice invertido a la tabla y mostramos cómo usarlo para recuperar todas las entidades Game para un User específico. 

  • Paso 7: Agregar un índice invertido

    En este paso, agregamos un índice invertido a la tabla. Los índices invertidos son creados al igual que los índices secundarios.

    En el código que descargó, encontrará el script add_inverted_index.py en el directorio scripts/ . Este script de Python agrega un índice invertido a su tabla.

    El archivo contiene lo siguiente.

    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)
    

    En este script, llamamos a un método update_table() en nuestro cliente de DynamoDB. En el método, pasamos detalles sobre el índice secundario que queremos crear, incluido el esquema clave para el índice, el rendimiento aprovisionado y los atributos para proyectar en el índice.

    Ejecute el script mediante la escritura del siguiente comando en su terminal.

    python scripts/add_inverted_index.py

    Su terminal mostrará el resultado de que su índice se creó con éxito.

    Table updated successfully.

    En el siguiente paso, usamos nuestro índice invertido para recuperar todas las entidades Game para un User en particular.

  • Paso 8: Recuperar juegos para un usuario

    Ahora que hemos creado nuestro índice invertido, utilicémoslo para recuperar las entidades Game jugadas por un User. Para manejar esto, necesitamos consultar el índice invertido con el User cuyas entidades Game queremos ver.

    En el código que descargó, hay un script find_games_for_user en el directorio application/. El archivo contiene lo siguiente.

    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)
    

    En este script, tenemos una función llamada find_games_for_user() que es similar a una función que tendría en su juego. Esta función toma un nombre de usuario y devuelve todos los juegos jugados por el usuario dado.

    Ejecute el script en su terminal con el siguiente comando.

    python application/find_games_for_user.py

    El script debe imprimir todos los juegos jugados por el usuario 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>
    

    En este módulo, agregamos un índice secundario a la tabla. Esto cubrió dos patrones de acceso adicionales:

    • Buscar juegos abiertos por mapa (Leer)
    • Buscar juegos abiertos (lectura)

    Para lograr esto, utilizamos un índice disperso que incluía solo los juegos que aún estaban abiertos para jugadores adicionales. Luego, utilizamos las API de Query y Scan contra el índice para encontrar juegos abiertos.

    Además, vimos cómo cubrir dos operaciones de escritura avanzadas en la aplicación. Primero, usamos transacciones de DynamoDB cuando un usuario se unió a un juego. Con las transacciones, gestionamos una escritura condicional compleja en varias entidades en una sola solicitud.

    En segundo lugar, implementamos la función para que un creador de un juego inicie un juego cuando esté listo. En este patrón de acceso, tuvimos una operación de actualización que requería verificar el valor de tres atributos y actualizar dos atributos. Puede expresar esta lógica compleja en una sola solicitud a través del poder de las expresiones de condición y las expresiones de actualización.

    En tercer lugar, cumplimos con el patrón de acceso final mediante la recuperación de todas las entidades Game que jugó un User. Para manejar este patrón de acceso, creamos un índice secundario mediante el patrón de índice invertido para permitir consultas en el otro lado de la relación de muchos a muchos entre las entidades User y Game.

    En el siguiente módulo, limpiaremos los recursos que creamos.