Reputation: 3423
I am really new to Dynamodb and NoSQL world. I am practicing AWS' GSI
and LSI
. I am using serverless framework. I split my handlers multiple lambda function.I want to create my data where I can see all the restaurants and what type of beers they have its' price. Also I want to query all the beers without price. I successfully create the restaurant and it's partition key is id
. I can able to get all the restaurant data. But I stuck in beer logic. I create api endpoint for beers like this restaurant/{id}/createBeers
, when I made post request, I got error "message": "One or more parameter values were invalid: Missing the key id in the item"
because it asked for restaurant's id. I cant find the logic where i can add the restaurant's id to create the beers and how to get all beer's without price.
The Restaurant is one to many. Beer is many to many(Same name different price based on Restaurant). This is what I want to achieve.
[
{
"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$"
}
]
}
}
]
This is How I want get all the beers in the table
[
{
"beername": "beer 1"
},
{
"beername": "beer 2"
},
{
"beername": "beer 3"
},
{
"beername": "beer 4"
}
]
This is is my restaurant create
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),
};
}
};
This is create Beer handler
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),
};
}
};
This is my GSI
get all beers without price
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),
};
}
}
This is my serverless yml file
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"
Upvotes: 0
Views: 530
Reputation: 13108
From what I gather you have a two entities:
Each restaurant may have multiple beers and each bear is either bottled or tap and also has a price.
You want to enable these access patterns:
I propose a single table with a Global Secondary Index (GSI1) that looks like this:
GSI1
with 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"]
PK=R<restaurant-id> and SK begins_with B
(You can replace B
with B-TP
for a list of all tap beers or B-BB
for all bottled beers. Get rid of the SK-condition for all info about the restaurant.)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"]
GSI1
with PK=BEERS
. You will get duplicates, so you need to remove those client-side.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]
When you now add items to the table, you have to consider what type of entity you want to create.
Restaurants are pretty simple, they just need an ID and a name. From these information, we can compute the key-attributes:
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
)
The beer always belongs to a restaurant, so we need to have it's id - it also always has a price and a name.
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
)
Upvotes: 2