Giter Club home page Giter Club logo

aws-serverless-shopping-cart's Introduction

Serverless Shopping Cart Microservice

This application is a sample application to demonstrate how you could implement a shopping cart microservice using serverless technologies on AWS. The backend is built as a REST API interface, making use of Amazon API Gateway, AWS Lambda, Amazon Cognito, and Amazon DynamoDB. The frontend is a Vue.js application using the AWS Amplify SDK for authentication and communication with the API.

To assist in demonstrating the functionality, a bare bones mock "products" service has also been included. Since the authentication parts are likely to be shared between components, there is a separate template for it. The front-end doesn't make any real payment integration at this time.

Architecture & Design

Architecture Diagram

Design Notes

Before building the application, I set some requirements on how the cart should behave:

  • Users should be able to add items to the cart without logging in (an "anonymous cart"), and that cart should persist across browser restarts etc.
  • When logging in, if there were products in an anonymous cart, they should be added to the user's cart from any previous logged in sessions.
  • When logging out, the anonymous cart should not have products in it any longer.
  • Items in an anonymous cart should be removed after a period of time, and items in a logged in cart should persist for a longer period.
  • Admin users to be able to get an aggregated view of the total number of each product in users' carts at any time.

Cart Migration

When an item is added to the cart, an item is written in DynamoDB with an identifier which matches a randomly generated (uuid) cookie which is set in the browser. This allows a user to add items to cart and come back to the page later without losing the items they have added. When the user logs in, these items will be removed, and replaced with items with a user id as the pk. If the user already had that product in their cart from a previous logged in session, the quantities would be summed. Because we don't need the deletion of old items to happen immediately as part of a synchronous workflow, we put messages onto an SQS queue, which triggers a worker function to delete the messages.

To expire items from users' shopping carts, DynamoDB's native functionality is used where a TTL is written along with the item, after which the item should be removed. In this implementation, the TTL defaults to 1 day for anonymous carts, and 7 days for logged in carts.

Aggregated View of Products in Carts

It would be possible to scan our entire DynamoDB table and sum up the quantities of all the products, but this will be expensive past a certain scale. Instead, we can calculate the total as a running process, and keep track of the total amount.

When an item is added, deleted or updated in DynamoDB, an event is put onto DynamoDB Streams, which in turn triggers a Lambda function. This function calculates the change in total quantity for each product in users' carts, and writes the quantity back to DynamoDB. The Lambda function is configured so that it will run after either 60 seconds pass, or 100 new events are on the stream. This would enable an admin user to get real time data about the popular products, which could in turn help anticipate inventory. In this implementation, the API is exposed without authentication to demonstrate the functionality.

Api Design

Shopping Cart Service

GET
/cart
Retrieves the shopping cart for a user who is either anonymous or logged in.

POST
/cart
Accepts a product id and quantity as json. Adds specified quantity of an item to cart.

/cart/migrate
Called after logging in - migrates items in an anonymous user's cart to belong to their logged in user. If you already have a cart on your logged in user, your "anonymous cart" will be merged with it when you log in.

/cart/checkout
Currently just empties cart.

PUT
/cart/{product-id}
Accepts a product id and quantity as json. Updates quantity of given item to provided quantity.

GET
/cart/{product-id}/total
Returns the total amount of a given product across all carts. This API is not used by the frontend but can be manually called to test.

Product Mock Service

GET
/product
Returns details for all products.

/product/{product_id}
Returns details for a single product.

Running the Example

Requirements

python >= 3.8.0 boto3 SAM CLI, >= version 0.50.0
AWS CLI
yarn

Setup steps

Fork the github repo, then clone your fork locally: git clone https://github.com/<your-github-username>/aws-serverless-shopping-cart && cd aws-serverless-shopping-cart

If you wish to use a named profile for your AWS credentials, you can set the environment variable AWS_PROFILE before running the below commands. For a profile named "development": export AWS_PROFILE=development.

You now have 2 options - you can deploy the backend and run the frontend locally, or you can deploy the whole project using the AWS Amplify console.

Option 1 - Deploy backend and run frontend locally

Deploy the Backend

An S3 bucket will be automatically created for you which will be used for deploying source code to AWS. If you wish to use an existing bucket instead, you can manually set the S3_BUCKET environment variable to the name of your bucket.

Build and deploy the resources:

make backend  # Creates S3 bucket if not existing already, then deploys CloudFormation stacks for authentication, a 
product mock service and the shopping cart service.  

Run the Frontend Locally

Start the frontend locally:

make frontend-serve  # Retrieves backend config from ssm parameter store to a .env file, then starts service.  

Once the service is running, you can access the frontend on http://localhost:8080/ and start adding items to your cart. You can create an account by clicking on "Sign In" then "Create Account". Be sure to use a valid email address as you'll need to retrieve the verification code.

Note: CORS headers on the backend service default to allowing http://localhost:8080/. You will see CORS errors if you access the frontend using the ip (http://127.0.0.1:8080/), or using a port other than 8080.

Clean Up

Delete the CloudFormation stacks created by this project:

make backend-delete

Option 2 - Automatically deploy backend and frontend using Amplify Console

One-click deployment

  1. Use 1-click deployment button above, and continue by clicking "Connect to Github"
  2. If you don't have an IAM Service Role with admin permissions, select "Create new role". Otherwise proceed to step 5)
  3. Select "Amplify" from the drop-down, and select "Amplify - Backend Deployment", then click "Next".
  4. Click "Next" again, then give the role a name and click "Create role"
  5. In the Amplify console and select the role you created, then click "Save and deploy"
  6. Amplify Console will fork this repository into your GitHub account and deploy it for you
  7. You should now be able to see your app being deployed in the Amplify Console
  8. Within your new app in Amplify Console, wait for deployment to complete (this should take approximately 12 minutes for the first deploy)

Clean Up

Delete the CloudFormation stacks created by this project. There are 3 of them, with names starting with "aws-serverless-shopping-cart-".

License

This library is licensed under the MIT-0 License. See the LICENSE file.

aws-serverless-shopping-cart's People

Contributors

amazon-auto avatar darko-mesaros avatar dependabot[bot] avatar ganipcanot avatar nmoutschen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aws-serverless-shopping-cart's Issues

Build fails

Unsure if this is the same issue previously stated, I followed the hotfix and added the permissions to IAM role Build failed at
2022-06-21T16:16:48.687Z [INFO]: -------------------------------------------------------------------------------------------------
2022-06-21T16:16:48.990Z [WARNING]: Error: Failed to create/update the stack: amplify-aws-serverless-shopping-cart-shoppingcart-service, Waiter StackCreateComplete failed: Waiter encountered a terminal failure state: For expression "Stacks[].StackStatus" we matched expected path: "ROLLBACK_COMPLETE" at least once
2022-06-21T16:16:49.103Z [WARNING]: make[1]: *** [deploy] Error 1
2022-06-21T16:16:49.104Z [WARNING]: make: *** [backend] Error 2
2022-06-21T16:16:49.104Z [INFO]: make[1]: Leaving directory `/codebuild/output/src425599724/src/aws-serverless-shopping-cart/backend'
2022-06-21T16:16:49.105Z [ERROR]: !!! Build failed
2022-06-21T16:16:49.105Z [ERROR]: !!! Non-Zero Exit Code detected
2022-06-21T16:16:49.105Z [INFO]: # Starting environment caching...
2022-06-21T16:16:49.106Z [INFO]: # Environment caching completed
Terminating logging...

Python 3.9 make issue

This is a heads up for anyone that has python 3.9 locally if you get the error (i got it on OSX)

Error: PythonPipBuilder:Resolver - Path resolution for runtime: python3.8 of binary: python was not successful

product-mock.yaml and shoppingcart-service.yaml will break the make backend unless you update the runtime to python 3.9 in those files

Build fails

Hi, I just tried to test this with the instructions provided and build fails on:

2022-03-09T15:27:06.601Z [WARNING]: Error: Failed to create changeset for the stack: aws-serverless-shopping-cart-auth, An error occurred (AccessDenied) when calling the CreateChangeSet operation: User: arn:aws:sts::684570837450:assumed-role/aws-serverless-shopping-cart/BuildSession is not authorized to perform: cloudformation:CreateChangeSet on resource: arn:aws:cloudformation:us-east-2:684570837450:stack/aws-serverless-shopping-cart-auth/* because no identity-based policy allows the cloudformation:CreateChangeSet action
2022-03-09T15:27:06.690Z [WARNING]: make[1]: *** [deploy] Error 1
2022-03-09T15:27:06.691Z [WARNING]: make: *** [backend] Error 2
2022-03-09T15:27:06.691Z [INFO]: make[1]: Leaving directory `/codebuild/output/src727662775/src/aws-serverless-shopping-cart/backend'
2022-03-09T15:27:06.692Z [ERROR]: !!! Build failed
2022-03-09T15:27:06.692Z [ERROR]: !!! Non-Zero Exit Code detected
2022-03-09T15:27:06.692Z [INFO]: # Starting environment caching...
2022-03-09T15:27:06.692Z [INFO]: # Environment caching completed
Terminating logging...

Checked permissions and that CreateChangeSet is set on the newly created role:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "CLICloudformationPolicy",
            "Effect": "Allow",
            "Action": [
                "cloudformation:CreateChangeSet",
                "cloudformation:CreateStack",
                "cloudformation:DeleteStack",
                "cloudformation:DescribeChangeSet",
                "cloudformation:DescribeStackEvents",
                "cloudformation:DescribeStackResource",
                "cloudformation:DescribeStackResources",
                "cloudformation:DescribeStacks",
                "cloudformation:ExecuteChangeSet",
                "cloudformation:GetTemplate",
                "cloudformation:UpdateStack",
                "cloudformation:ListStackResources",
                "cloudformation:DeleteStackSet",
                "cloudformation:DescribeStackSet",
                "cloudformation:UpdateStackSet"
            ],
            "Resource": [
                "arn:aws:cloudformation:*:*:stack/amplify-*"
            ]
        },
        {
            "Sid": "CLIManageviaCFNPolicy",
            "Effect": "Allow",
            "Action": [
                "iam:ListRoleTags",
                "iam:TagRole",
                "iam:AttachRolePolicy",
                "iam:CreatePolicy",
                "iam:DeletePolicy",
                "iam:DeleteRole",
                "iam:DeleteRolePolicy",
                "iam:DetachRolePolicy",
                "iam:PutRolePolicy",
                "iam:UpdateRole",
                "iam:GetRole",
                "iam:GetPolicy",
                "iam:GetRolePolicy",
                "iam:PassRole",
                "iam:ListPolicyVersions",
                "iam:CreatePolicyVersion",
                "iam:DeletePolicyVersion",
                "iam:CreateRole",
                "iam:ListRolePolicies",
                "iam:PutRolePermissionsBoundary",
                "iam:DeleteRolePermissionsBoundary",
                "appsync:CreateApiKey",
                "appsync:CreateDataSource",
                "appsync:CreateFunction",
                "appsync:CreateResolver",
                "appsync:CreateType",
                "appsync:DeleteApiKey",
                "appsync:DeleteDataSource",
                "appsync:DeleteFunction",
                "appsync:DeleteResolver",
                "appsync:DeleteType",
                "appsync:GetDataSource",
                "appsync:GetFunction",
                "appsync:GetIntrospectionSchema",
                "appsync:GetResolver",
                "appsync:GetSchemaCreationStatus",
                "appsync:GetType",
                "appsync:GraphQL",
                "appsync:ListApiKeys",
                "appsync:ListDataSources",
                "appsync:ListFunctions",
                "appsync:ListGraphqlApis",
                "appsync:ListResolvers",
                "appsync:ListResolversByFunction",
                "appsync:ListTypes",
                "appsync:StartSchemaCreation",
                "appsync:UpdateApiKey",
                "appsync:UpdateDataSource",
                "appsync:UpdateFunction",
                "appsync:UpdateResolver",
                "appsync:UpdateType",
                "appsync:TagResource",
                "appsync:CreateGraphqlApi",
                "appsync:DeleteGraphqlApi",
                "appsync:GetGraphqlApi",
                "appsync:ListTagsForResource",
                "appsync:UpdateGraphqlApi",
                "apigateway:DELETE",
                "apigateway:GET",
                "apigateway:PATCH",
                "apigateway:POST",
                "apigateway:PUT",
                "cognito-idp:CreateUserPool",
                "cognito-identity:CreateIdentityPool",
                "cognito-identity:DeleteIdentityPool",
                "cognito-identity:DescribeIdentity",
                "cognito-identity:DescribeIdentityPool",
                "cognito-identity:SetIdentityPoolRoles",
                "cognito-identity:GetIdentityPoolRoles",
                "cognito-identity:UpdateIdentityPool",
                "cognito-idp:CreateUserPoolClient",
                "cognito-idp:DeleteUserPool",
                "cognito-idp:DeleteUserPoolClient",
                "cognito-idp:DescribeUserPool",
                "cognito-idp:DescribeUserPoolClient",
                "cognito-idp:ListTagsForResource",
                "cognito-idp:ListUserPoolClients",
                "cognito-idp:UpdateUserPoolClient",
                "cognito-idp:CreateGroup",
                "cognito-idp:DeleteGroup",
                "cognito-identity:TagResource",
                "cognito-idp:TagResource",
                "cognito-idp:UpdateUserPool",
                "cognito-idp:SetUserPoolMfaConfig",
                "lambda:AddPermission",
                "lambda:CreateFunction",
                "lambda:DeleteFunction",
                "lambda:GetFunction",
                "lambda:GetFunctionConfiguration",
                "lambda:InvokeAsync",
                "lambda:InvokeFunction",
                "lambda:RemovePermission",
                "lambda:UpdateFunctionCode",
                "lambda:UpdateFunctionConfiguration",
                "lambda:ListTags",
                "lambda:TagResource",
                "lambda:UntagResource",
                "lambda:AddLayerVersionPermission",
                "lambda:CreateEventSourceMapping",
                "lambda:DeleteEventSourceMapping",
                "lambda:DeleteLayerVersion",
                "lambda:GetEventSourceMapping",
                "lambda:GetLayerVersion",
                "lambda:ListEventSourceMappings",
                "lambda:ListLayerVersions",
                "lambda:PublishLayerVersion",
                "lambda:RemoveLayerVersionPermission",
                "dynamodb:CreateTable",
                "dynamodb:DeleteItem",
                "dynamodb:DeleteTable",
                "dynamodb:DescribeContinuousBackups",
                "dynamodb:DescribeTable",
                "dynamodb:DescribeTimeToLive",
                "dynamodb:ListStreams",
                "dynamodb:PutItem",
                "dynamodb:TagResource",
                "dynamodb:ListTagsOfResource",
                "dynamodb:UpdateContinuousBackups",
                "dynamodb:UpdateItem",
                "dynamodb:UpdateTable",
                "dynamodb:UpdateTimeToLive",
                "s3:CreateBucket",
                "s3:ListBucket",
                "s3:PutBucketAcl",
                "s3:PutBucketCORS",
                "s3:PutBucketNotification",
                "s3:PutBucketPolicy",
                "s3:PutBucketWebsite",
                "s3:PutObjectAcl",
                "cloudfront:CreateCloudFrontOriginAccessIdentity",
                "cloudfront:CreateDistribution",
                "cloudfront:DeleteCloudFrontOriginAccessIdentity",
                "cloudfront:DeleteDistribution",
                "cloudfront:GetCloudFrontOriginAccessIdentity",
                "cloudfront:GetCloudFrontOriginAccessIdentityConfig",
                "cloudfront:GetDistribution",
                "cloudfront:GetDistributionConfig",
                "cloudfront:TagResource",
                "cloudfront:UntagResource",
                "cloudfront:UpdateCloudFrontOriginAccessIdentity",
                "cloudfront:UpdateDistribution",
                "events:DeleteRule",
                "events:DescribeRule",
                "events:ListRuleNamesByTarget",
                "events:PutRule",
                "events:PutTargets",
                "events:RemoveTargets",
                "mobiletargeting:GetApp",
                "kinesis:AddTagsToStream",
                "kinesis:CreateStream",
                "kinesis:DeleteStream",
                "kinesis:DescribeStream",
                "kinesis:DescribeStreamSummary",
                "kinesis:ListTagsForStream",
                "kinesis:PutRecords",
                "es:AddTags",
                "es:CreateElasticsearchDomain",
                "es:DeleteElasticsearchDomain",
                "es:DescribeElasticsearchDomain",
                "s3:PutEncryptionConfiguration"
            ],
            "Resource": "*",
            "Condition": {
                "ForAnyValue:StringEquals": {
                    "aws:CalledVia": [
                        "cloudformation.amazonaws.com"
                    ]
                }
            }
        },
        {
            "Sid": "CLISDKCalls",
            "Effect": "Allow",
            "Action": [
                "appsync:GetIntrospectionSchema",
                "appsync:GraphQL",
                "appsync:UpdateApiKey",
                "appsync:ListApiKeys",
                "amplify:*",
                "amplifybackend:*",
                "amplifyuibuilder:*",
                "sts:AssumeRole",
                "mobiletargeting:*",
                "cognito-idp:AdminAddUserToGroup",
                "cognito-idp:AdminCreateUser",
                "cognito-idp:CreateGroup",
                "cognito-idp:DeleteGroup",
                "cognito-idp:DeleteUser",
                "cognito-idp:ListUsers",
                "cognito-idp:AdminGetUser",
                "cognito-idp:ListUsersInGroup",
                "cognito-idp:AdminDisableUser",
                "cognito-idp:AdminRemoveUserFromGroup",
                "cognito-idp:AdminResetUserPassword",
                "cognito-idp:AdminListGroupsForUser",
                "cognito-idp:ListGroups",
                "cognito-idp:AdminListUserAuthEvents",
                "cognito-idp:AdminDeleteUser",
                "cognito-idp:AdminConfirmSignUp",
                "cognito-idp:AdminEnableUser",
                "cognito-idp:AdminUpdateUserAttributes",
                "cognito-idp:DescribeIdentityProvider",
                "cognito-idp:DescribeUserPool",
                "cognito-idp:DeleteUserPool",
                "cognito-idp:DescribeUserPoolClient",
                "cognito-idp:CreateUserPool",
                "cognito-idp:CreateUserPoolClient",
                "cognito-idp:UpdateUserPool",
                "cognito-idp:AdminSetUserPassword",
                "cognito-idp:ListUserPools",
                "cognito-idp:ListUserPoolClients",
                "cognito-idp:ListIdentityProviders",
                "cognito-idp:GetUserPoolMfaConfig",
                "cognito-identity:GetIdentityPoolRoles",
                "cognito-identity:SetIdentityPoolRoles",
                "cognito-identity:CreateIdentityPool",
                "cognito-identity:DeleteIdentityPool",
                "cognito-identity:ListIdentityPools",
                "cognito-identity:DescribeIdentityPool",
                "dynamodb:DescribeTable",
                "dynamodb:ListTables",
                "lambda:GetFunction",
                "lambda:CreateFunction",
                "lambda:AddPermission",
                "lambda:DeleteFunction",
                "lambda:DeleteLayerVersion",
                "lambda:InvokeFunction",
                "lambda:ListLayerVersions",
                "iam:PutRolePolicy",
                "iam:CreatePolicy",
                "iam:AttachRolePolicy",
                "iam:ListPolicyVersions",
                "iam:ListAttachedRolePolicies",
                "iam:CreateRole",
                "iam:PassRole",
                "iam:ListRolePolicies",
                "iam:DeleteRolePolicy",
                "iam:CreatePolicyVersion",
                "iam:DeletePolicyVersion",
                "iam:DeleteRole",
                "iam:DetachRolePolicy",
                "cloudformation:ListStacks",
                "sns:CreateSMSSandboxPhoneNumber",
                "sns:GetSMSSandboxAccountStatus",
                "sns:VerifySMSSandboxPhoneNumber",
                "sns:DeleteSMSSandboxPhoneNumber",
                "sns:ListSMSSandboxPhoneNumbers",
                "sns:ListOriginationNumbers",
                "rekognition:DescribeCollection",
                "logs:DescribeLogStreams",
                "logs:GetLogEvents",
                "lex:GetBot",
                "lex:GetBuiltinIntent",
                "lex:GetBuiltinIntents",
                "lex:GetBuiltinSlotTypes",
                "cloudformation:GetTemplateSummary",
                "codecommit:GitPull"
            ],
            "Resource": "*"
        },
        {
            "Sid": "AmplifySSMCalls",
            "Effect": "Allow",
            "Action": [
                "ssm:PutParameter",
                "ssm:DeleteParameter",
                "ssm:GetParametersByPath",
                "ssm:GetParameters",
                "ssm:GetParameter",
                "ssm:DeleteParameters"
            ],
            "Resource": "arn:aws:ssm:*:*:parameter/amplify/*"
        },
        {
            "Sid": "GeoPowerUser",
            "Effect": "Allow",
            "Action": [
                "geo:*"
            ],
            "Resource": "*"
        },
        {
            "Sid": "AmplifyStorageSDKCalls",
            "Effect": "Allow",
            "Action": [
                "s3:CreateBucket",
                "s3:DeleteBucket",
                "s3:DeleteBucketPolicy",
                "s3:DeleteBucketWebsite",
                "s3:DeleteObject",
                "s3:DeleteObjectVersion",
                "s3:GetBucketLocation",
                "s3:GetObject",
                "s3:ListAllMyBuckets",
                "s3:ListBucket",
                "s3:ListBucketVersions",
                "s3:PutBucketAcl",
                "s3:PutBucketCORS",
                "s3:PutBucketNotification",
                "s3:PutBucketPolicy",
                "s3:PutBucketVersioning",
                "s3:PutBucketWebsite",
                "s3:PutEncryptionConfiguration",
                "s3:PutLifecycleConfiguration",
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "*"
        },
        {
            "Sid": "AmplifySSRCalls",
            "Effect": "Allow",
            "Action": [
                "cloudfront:CreateCloudFrontOriginAccessIdentity",
                "cloudfront:CreateDistribution",
                "cloudfront:CreateInvalidation",
                "cloudfront:GetDistribution",
                "cloudfront:GetDistributionConfig",
                "cloudfront:ListCloudFrontOriginAccessIdentities",
                "cloudfront:ListDistributions",
                "cloudfront:ListDistributionsByLambdaFunction",
                "cloudfront:ListDistributionsByWebACLId",
                "cloudfront:ListFieldLevelEncryptionConfigs",
                "cloudfront:ListFieldLevelEncryptionProfiles",
                "cloudfront:ListInvalidations",
                "cloudfront:ListPublicKeys",
                "cloudfront:ListStreamingDistributions",
                "cloudfront:UpdateDistribution",
                "cloudfront:TagResource",
                "cloudfront:UntagResource",
                "cloudfront:ListTagsForResource",
                "iam:AttachRolePolicy",
                "iam:CreateRole",
                "iam:CreateServiceLinkedRole",
                "iam:GetRole",
                "iam:PutRolePolicy",
                "iam:PassRole",
                "lambda:CreateFunction",
                "lambda:EnableReplication",
                "lambda:DeleteFunction",
                "lambda:GetFunction",
                "lambda:GetFunctionConfiguration",
                "lambda:PublishVersion",
                "lambda:UpdateFunctionCode",
                "lambda:UpdateFunctionConfiguration",
                "lambda:ListTags",
                "lambda:TagResource",
                "lambda:UntagResource",
                "route53:ChangeResourceRecordSets",
                "route53:ListHostedZonesByName",
                "route53:ListResourceRecordSets",
                "s3:CreateBucket",
                "s3:GetAccelerateConfiguration",
                "s3:GetObject",
                "s3:ListBucket",
                "s3:PutAccelerateConfiguration",
                "s3:PutBucketPolicy",
                "s3:PutObject",
                "s3:PutBucketTagging",
                "s3:GetBucketTagging",
                "lambda:ListEventSourceMappings",
                "lambda:CreateEventSourceMapping",
                "iam:UpdateAssumeRolePolicy",
                "iam:DeleteRolePolicy",
                "sqs:CreateQueue",
                "sqs:DeleteQueue",
                "sqs:GetQueueAttributes",
                "sqs:SetQueueAttributes",
                "amplify:GetApp",
                "amplify:GetBranch",
                "amplify:UpdateApp",
                "amplify:UpdateBranch"
            ],
            "Resource": "*"
        }
    ]
}

Idempotency Concern in CartDBStreamHandler Function

Description:
I would like to kindly bring attention to a potential issue with the CartDBStreamHandler function, which is used to change the quantity of items in the shopping cart stored as records in DynamoDB. The function operates by iterating through each pair of (item, quantity delta) in quantity_change_counter and using DynamoDB’s UpdateExpression : "ADD … to change the absolute quantity stored in the table. However, this method is not idempotent. Suppose when the function is invoked for the first time, x is added to the absolute quantity of itemA. If the function crashes and retries, x more items will be added to the record. This is undesirable, as it duplicates the user's action of adding items to the cart if untimely retries ever happen.

Suggested Fix:
To address this issue, please consider adding a LastRequestId field to the record that stores the most recent lambda's request id, which is constant across retries. Then, use a condition expression along with the update expression; specifically, apply the quantity change and update the LastRequestId field only if context.aws_request_id != item.LastRequestId. This approach should help ensure idempotency and improve the reliability of the at-least-once executed CartDBStreamHandler function.

Closing Remarks:
I appreciate your attention to this matter and hope that my suggestion proves helpful in enhancing the reliability of the CartDBStreamHandler function. Thank you for considering this feedback, and please don't hesitate to reach out if you have any questions or concerns.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.