Movie Store — Building a modern web application on AWS — Part (4)- Bookmark and Cart Services

ramanan kannan
5 min readNov 24, 2020

In this article, we will see how we built the bookmark and the services

Authors: Sriram Sundararajan & Ramanan Kannan

Disclaimer: This is our personal learning project and not an official AWS post. This project is for learning purpose only and the code here might not confirm to any production ready standards

To read the previous article (part 3 of the series), please visit:

Architecture

Bookmarks and Cart API

Using this architecture we will build:

  1. A NoSQL datastore to store all the user’s bookmarks and cart info using AWS DynamoDB
  2. We will build a single Lambda function that will do all the CRUD operations with the DynamoDB
  3. And AWS API Gateway to create a REST API endpoint to the Lambda function

Setup

The first step, will be to define the access patterns to the DynamoDB and create a datamodel.

Below are the access patterns that we need from the database for the Bookmarks API:

  1. given a userid, get the list of all the movies that the user has bookmarked
  2. given a userid and movieid, bookmark the movieid
  3. given a userid and movieid, delete the bookmark

Similarly, below are the access patterns that we need from the database for the Cart API:

  1. given a userid, get the list of all the movies in the user’s cart
  2. given a userid and movieid, add the movie to the cart
  3. given a userid, clear the cart when the user checks-out

In keeping with the access patterns we will create a dynamodb table with the below data-model as below:

Primary Key — this will be a combination of userid and movieid

userid — this will be the partition key

movieid — this will be the sort key (this value can either contain a movieid or it can contain a particular genre. The genre will be a user preference, we can later implement)

other attributes in the table

bookmarks — true or false

cart — true or false

description — a general description field to include a description value

As a next step we will provision an AWS DynamoDB table. You can provision a DynamoDB table using the steps documented here and the data-model that we described above:

Once we have the DynamoDB setup, we will then setup Lambda functions with the appropriate IAM user role and permission to do CRUD operations against the DynamoDB table we created above — one for Bookmarks service and another for the Cart service.

We used Python to create the Lambda functions. Here is the bookmarks service Lambda function that we created:

import json
import boto3
from boto3.dynamodb.conditions import Key, Attr
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('movies')
##################
#Create Bookmark#
##################
def create_bookmark (item):
response1 = table.query(
KeyConditionExpression=Key('userid').eq(item['userid']) & Key('sortkey').eq(item['sortkey'])
)
items = response1['Items']
print(response1['Items'])

if items == []:
response = table.put_item(
Item = item
)
return response
else:
table.update_item(
Key={
'userid': item['userid'],
'sortkey': item['sortkey']
},
UpdateExpression='SET bookmark = :val1',
ExpressionAttributeValues={
':val1': 'true'
}
)


##################
#Get Bookmark#
##################
def get_bookmarks(userid):
userid = userid
response = table.query(
KeyConditionExpression=Key('userid').eq(userid),
FilterExpression=Attr('bookmark').eq('true')
)
items = response['Items']
return items
##################
#DELETE Bookmark#
##################
def delete_bookmark(userid,movie):
userid = userid
movie = movie
table.update_item(
Key={
'userid': userid,
'sortkey': movie
},
UpdateExpression='SET bookmark = :val1',
ExpressionAttributeValues={
':val1': 'false'
}
)
##################
#Handler Function#
##################
def lambda_handler(event, context):

if event['httpMethod'] == 'POST':
item = {}
body = json.loads(event['body'])
item['sortkey'] = 'movie:'+body['movie_id']
item['userid'] = event['pathParameters']['user_id']
item['description'] = body['description']
item['bookmark'] = 'true'

response = create_bookmark(item)
return {
'statusCode': 201,
'headers': {
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*'
},
'body': ''
}
elif event['httpMethod'] == 'GET':

userid = event['pathParameters']['user_id']
response3 = get_bookmarks(userid)
print(response3)
return {
'statusCode': 200,
'headers': {
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*'
},
'body': json.dumps(response3)
}

elif event['httpMethod'] == 'DELETE':
userid = event['pathParameters']['user_id']
movie = event['queryStringParameters']['movie_id']
response = delete_bookmark(userid,movie)
return {
'statusCode': 204,
'headers': {
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*'
},
'body': ''
}

Below is the Cart Service Lambda function:

import json
import boto3
from boto3.dynamodb.conditions import Key, Attr
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('movies')
##################
#Create Bookmark#
##################
def create_cart (item):
response1 = table.query(
KeyConditionExpression=Key('userid').eq(item['userid']) & Key('sortkey').eq(item['sortkey'])
)
items = response1['Items']
print(response1['Items'])

if items == []:
response = table.put_item(
Item = item
)
return response
else:
table.update_item(
Key={
'userid': item['userid'],
'sortkey': item['sortkey']
},
UpdateExpression='SET cart = :val1',
ExpressionAttributeValues={
':val1': 'true'
}
)


##################
#Get Cart#
##################
def get_cart(userid):
userid = userid
response = table.query(
KeyConditionExpression=Key('userid').eq(userid),
FilterExpression=Attr('cart').eq('true')
)
items = response['Items']
return items
##################
#DELETE Cart#
##################
def delete_cart(userid,movie):
userid = userid
movie = movie
table.update_item(
Key={
'userid': userid,
'sortkey': movie
},
UpdateExpression='SET cart = :val1',
ExpressionAttributeValues={
':val1': 'false'
}
)
def checkout(userid):
response = table.query(
KeyConditionExpression=Key('userid').eq('sriram'),
FilterExpression=Attr('cart').eq('true')
)
items = response['Items']
for item in items:
table.update_item(Key = {'userid': item['userid'], 'sortkey': item['sortkey']}, UpdateExpression='SET cart = :val1',ExpressionAttributeValues={
':val1': 'false'
})
##################
#Handler Function#
##################
def lambda_handler(event, context):

path = event['path']

if event['httpMethod'] == 'POST' and path.find("checkout") > -1:
item = {}
user_id = event['pathParameters']['user_id']

response = checkout(user_id)
return {
'statusCode': 201,
'headers': {
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*'
},
'body': ''
}

elif event['httpMethod'] == 'POST' and path.find("checkout") == -1:
item = {}
body = json.loads(event['body'])
item['sortkey'] = 'movie:'+body['movie_id']
item['userid'] = event['pathParameters']['user_id']
item['description'] = body['description']
item['cart'] = 'true'

response = create_cart(item)
return {
'statusCode': 201,
'headers': {
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*'
},
'body': ''
}
elif event['httpMethod'] == 'GET':

userid = event['pathParameters']['user_id']
response3 = get_cart(userid)
print(response3)
return {
'statusCode': 200,
'headers': {
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*'
},
'body': json.dumps(response3)
}

elif event['httpMethod'] == 'DELETE':
userid = event['pathParameters']['user_id']
movie = event['queryStringParameters']['movie_id']
response = delete_cart(userid,movie)
return {
'statusCode': 204,
'headers': {
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*'
},
'body': ''
}

Once Lambda functions are created, we will create an API gateway to front the Lambda functions to create REST API endpoints and finally integrate the API endpoint with the app front-end.

--

--