Dynamodb-serverless 一对多和多对多关系

时间:2021-03-08 10:18:25

标签: aws-lambda amazon-dynamodb serverless-framework dynamodb-queries

我对 Dynamodb 和 NoSQL 世界真的很陌生。我正在练习 AWS 的 GSILSI。我正在使用无服务器框架。我将我的处理程序拆分为多个 lambda 函数。我想创建我的数据,在那里我可以看到所有餐馆以及他们的价格是什么类型的啤酒。另外我想查询所有没有价格的啤酒。我成功创建了餐厅,它的分区键是 id。我可以获得所有餐厅数据。但我坚持啤酒逻辑。我为这样的啤酒创建了 api 端点 restaurant/{id}/createBeers,当我提出发布请求时,我收到错误 "message": "One or more parameter values were invalid: Missing the key id in the item",因为它要求提供餐厅的 ID。我找不到可以添加餐厅 ID 来创建啤酒的逻辑,以及如何免费获得所有啤酒。

餐厅是一对多的。啤酒是多对多的(同名不同价格基于餐厅)。这就是我想要实现的目标。

[
      {
        "id": 1,
        "name": "restaurent 1",
        "beers": {
            "tap-beers": [{
                    "name": "beer 1",
                    "price": "2$"
                },
                {
                    "name": "beer 2",
                    "price": "2$"
                }
            ],
            "bottle-beers": [{
                    "name": "beer 3",
                    "price": "2$"
                },
                {
                    "name": "beer 4",
                    "price": "2$"
                }
            ]
        }
    },
    
      {
        "id": 2,
        "name": "restaurent 2",
        "beers": {
            "tap-beers": [{
                    "name": "beer 1",
                    "price": "3$"
                },
                {
                    "name": "beer 2",
                    "price": "3$"
                }
            ],
            "bottle-beers": [{
                    "name": "beer 3",
                    "price": "4$"
                },
                {
                    "name": "beer 4",
                    "price": "6$"
                }
            ]
        }
    }
]

这就是我想要得到桌上所有啤酒的方式

[
  {
    "beername": "beer 1"
  },
   {
    "beername": "beer 2"
  },
   {
    "beername": "beer 3"
  },
   {
    "beername": "beer 4"
  }
]

这是我的餐厅

const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports.createRestaurant = async event => {
  const resquestBody = JSON.parse(event.body);

  const params = {
    TableName: "table name",
    Item: {
      id: uuid.v1(),
      name: resquestBody.name,
      beers: [] // in here I will add beers when I can create a beer my post method path 
                // is restaurant/{id}/createBeers
    }
  }

  try {
    await dynamoDb.put(params).promise();
    return {
      statusCode: 200,
      body: JSON.stringify(resquestBody),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify(error),
    };
  }
};

这是创建啤酒处理程序

const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports.createBeers = async event => {
  const requestBody = JSON.parse(event.body);
  const params = {
    TableName: "table name",
    Item: {
      beer_name: requestBody.beer_name,
      beer_type: requestBody.beer_type,
      beer_price: requestBody.beer_price
    }
  };

  try {
    await dynamoDb.put(params).promise();
    return {
      statusCode: 200,
      body: JSON.stringify(requestBody),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify(error),
    };
  }
};

这是我的GSI免费获得所有啤酒

module.exports.getBeers = async event => {

  const params = {
    TableName: "beer",
    IndexName: "beers",
    KeyConditionExpression: "beer_name = :beer_name",
    ExpressionAttributeValues: {
      ":beer_name": "beer_name"
    },
    Limit: 1
  }

  try {
    let data = await dynamoDb.query(params).promise();
    return {
      statusCode: 200,
      body: JSON.stringify(data.Items),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify(error),
    };
  }

}

这是我的无服务器 yml 文件

functions:
  createRestaurant:
    handler: handlers/createRestaurant.createRestaurant
    events:
      - http:
          path: restaurant
          method: post
          cors: true
  getRestaurants:
    handler: handlers/getRestaurants.getRestaurants
    events:
      - http:
          path: restaurant/all
          method: get
          cors: true
  createBeers:
    handler: handlers/createBeers.createBeers
    events:
      - http:
          path: restaurant/{id}/beers
          method: post
          cors: true
  getBeers:
    handler: handlers/getBeers.getBeers
    events:
      - http:
          path: beers/all
          method: get
          cors: true
resources:
  Resources:
    table:
      Type: "AWS::DynamoDB::Table"
      DeletionPolicy: Retain
      Properties:
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
          - AttributeName: beer_name
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        GlobalSecondaryIndexes:
          - IndexName: beers
            KeySchema:
              - AttributeName: beer_name
                KeyType: HASH
            Projection:
              ProjectionType: ALL
            ProvisionedThroughput:
              ReadCapacityUnits: 1
              WriteCapacityUnits: 1
        TableName: "tableName"

1 个答案:

答案 0 :(得分:2)

实体 + 关系

据我所知,您有两个实体:

  • 餐厅
    • 身份证
    • 姓名
  • 啤酒
    • 姓名

每家餐厅可能有多种啤酒,每只熊要么是瓶装的,要么是自来水的,也有价格。

访问模式

您想要启用这些访问模式:

  1. 获取餐厅列表
  2. 获取每家餐厅的啤酒清单
  3. 获取所有餐厅的啤酒清单

表格布局

我提出了一个带有全局二级索引 (GSI1) 的单个表,如下所示:

主表视图

Primary Key Perspective

GSI1 表视图

GSI1 Perspective

如何查询

  1. 要获取所有餐厅的列表,请使用 GSI1 查询 PK=RESTAURANTS
import typing
import boto3
import boto3.dynamodb.conditions as conditions

def get_all_restaurants() -> typing.List[dict]:

    table = boto3.resource("dynamodb").Table("table-name")

    response = table.query(
        KeyConditionExpression=conditions.Key("GSI1PK").eq("RESTAURANTS"),
        IndexName="GSI1"
    )

    return response["Items"]
  1. 要获取给定餐厅中所有啤酒的列表,您可以使用 PK=R<restaurant-id> and SK begins_with B 对主索引进行查询(您可以将 B 替换为 B-TP beers 或 B-BB 表示所有瓶装啤酒。去掉有关餐厅的所有信息的 SK 条件。)
import typing
import boto3
import boto3.dynamodb.conditions as conditions

def get_beers_in_restaurant(restaurant_id: str) -> typing.List[dict]:

    table = boto3.resource("dynamodb").Table("table-name")

    response = table.query(
        KeyConditionExpression=conditions.Key("PK").eq(f"R#{restaurant_id}") \
            & conditions.Key("SK").begins_with("B-")
    )

    return response["Items"]

def get_tap_beers_in_restaurant(restaurant_id: str) -> typing.List[dict]:

    table = boto3.resource("dynamodb").Table("table-name")

    response = table.query(
        KeyConditionExpression=conditions.Key("PK").eq(f"R#{restaurant_id}") \
            & conditions.Key("SK").begins_with("B-TB")
    )

    return response["Items"]

def get_bottled_beers_in_restaurant(restaurant_id: str) -> typing.List[dict]:

    table = boto3.resource("dynamodb").Table("table-name")

    response = table.query(
        KeyConditionExpression=conditions.Key("PK").eq(f"R#{restaurant_id}") \
            & conditions.Key("SK").begins_with("B-BB")
    )

    return response["Items"]
  1. 要获取所有啤酒的列表,请使用 GSI1 查询 PK=BEERS。您将获得重复项,因此您需要删除这些客户端。
import typing
import boto3
import boto3.dynamodb.conditions as conditions

def get_all_beers() -> typing.List[dict]:

    table = boto3.resource("dynamodb").Table("data")

    response = table.query(
        KeyConditionExpression=conditions.Key("GSI1PK").eq("BEERS"),
        IndexName="GSI1"
    )
    
    list_with_duplicates = [item["name"] for item in response["Items"]]
    list_without_duplicates = list(set(list_with_duplicates))

    return [{"beername": name} for name in list_without_duplicates]

向表格中添加项目

当您现在向表中添加项目时,您必须考虑要创建的实体类型。

添加餐厅

餐馆很简单,他们只需要一个 ID 和一个名字。根据这些信息,我们可以计算出关键属性:

import boto3

def create_restaurant(restaurant_id: str, name: str) -> None:
    table = boto3.resource("dynamodb").Table("data")

    item = {
        "PK": f"R#{restaurant_id}",
        "SK": "META",
        "GSI1PK": "RESTAURANTS",
        "GSI1SK": f"R#{restaurant_id}",
        "type": "RESTAURANT",
        "id": restaurant_id,
        "name": name
    }

    table.put_item(
        Item=item
    )

为餐厅添加啤酒

啤酒总是属于一家餐厅,所以我们需要有它的 id - 它也总是有价格和名称。

import boto3

def create_beer_for_restaurant(restaurant_id: str, name: str, price: str, is_tap: bool):

    table = boto3.resource("dynamodb").Table("data")

    sk = f"B-TB#{name}" if is_tap else f"B-BB#{name}"

    item = {
        "PK": f"R#{restaurant_id}",
        "SK": sk,
        "GSI1PK": "BEERS",
        "GSI1SK": f"B#{name}",
        "name": name,
        "price": price
    }

    table.put_item(
        Item=item
    )
相关问题