Scalable E-Commerce Architecture - Part 2: Shopping Cart
Tags: ecommerce, shoppingcart, architecture, scalability
Published: 2024-02-16 20:48:37

In my previous article, I discussed Authorize.net and the basic architecture of inventory, product management and catalog services.
 
Through our exploration, we identified that to implement an effective search and filters system, maintaining an updated source of truth for products is essential.

We achieve this by denormalizing product data and managing each component in a different service, ultimately consolidating all parts into a single database known as Catalog. 
For instance, when a new quantity is added for the product in the inventory service, the new quantity is sent to the catalog service, enabling us to search and filter products based on their availability.


Now, ley's div into designing and discussing about the shopping cart service. it is necessary before moving to the `registering the order` phase.

## Advanced Shopping Cart Features

This service is responsible for:

1- Contact information (Buyer or customer) related to a store
2- Recipient information (Address and contact details)
3- Billing information (same as previous)
4- Products with details such as product ID (*optional), quantity, product tax, product coupon as discount, recipient, delivery date, product addons and product subtotal with/without discount.
5- Cart level coupon
6- Agent id (if shopping cart is filling by agent on behalf of the customer)
7- Cart totals, customer budget, total tax, total discount, total shipping rates, total shipping discount and finally grand total.
8- Notes regarding order, processing, delivery and products


In point 4, regarding the products, I've marked the product ID as optional, because sometimes we may need to charge a customer without prior knowledge of their specific product preferences. he may just set the delivery time or recipient with the specific budget.
The product details are typically filled in when the customer begins the checkout process.

## Architecture
I'm going to describe how we can build our shopping cart service.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/19fzxg2hwpi8r1unf4rc.jpg)
[Photo link](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/19fzxg2hwpi8r1unf4rc.jpg)

As illustrated, we have distinct API endpoints for our shopping cart:
1- **Get Cart**: This endpoint retrieves the last available shopping cart for a customer, agent, or gust. If the cart doesn't exist, it creates a new cart and returns the empty cart with a UUID. We utilize UUIDs to maintain security and statelessness of our service, a common practice in monolithic or stateful projects where sessions are typically used for managing shopping carts for guests.

2- **Add Contact**: This endpoint sets the buyer's information. While this could be automated in the previous endpoint for customers and guests, it's necessary to allow agents to set the contact for the shopping cart, especially when a customer calls the CRM to register a new order.

3- **Add Address**: This endpoint can be called by the customer, guest or agent. it sets either the recipient (shipping address) or the billing address (buyer), with or without specific products.

4- **Set Product**: This endpoint adds a product to the cart. the product details may be empty initially, but the delivery date and delivery zip code should be provided. Subsequently, another service calculates the cart totals.

5- **Set Cart Coupon**: As you may have noticed, we had different types of discounts and coupons. For example, a total discount on the cart or shipping discount.

6- **Set Shipping**: When adding a product, it's possible to set the shipping method later. so we add this endpoint to update the shipping method for specific cart product(s).

7- **Calculating the Cart Totals**: This service acts as a Saga handler. it validates the products in the cart and calculates the final cart totals. This process, being the most complex part of the shopping cart service. 

## Saga Challenges
When utilizing message broker and saga handler, designing a stateful engine is important. Steps should be executed in specific order to prevent any errors. 
Therefore, we have to set different statuses for the cart and lock it based on that status. the final status is `CALCULATED`. 

## Sync and Async challenge
When a customer adds a new product to the shopping cart, a long process begins to validate and calculate the cart totals.
This may take less than a few seconds if everything works well. 

**The first question is this: Should we return the calculated cart every time a customer adds a product to their cart?**

## Solutions

1- **Wait for Calculation**: The initial approach involves waiting for the calculation process to complete before returning the processed cart data as an API response to the frontend. 
This approach may seem ideal, especially for smaller applications or monolithic architectures. It's a best practice used in most e-commerce applications like Bagisto. Additionally, setting a timeout on the frontend can help manage instances where the process takes longer or fails.

2- **Return 'OK' Message**: The second solution could be returning an 'OK' message to the front immediately. The process of updating the cart is running in the background until the frontend sends another request and fetches the latest status of the cart.
This approach allows for a near-real-time experience, typically under 5 seconds. During this time, the frontend shows and calculates cart totals itself. A small loading could be added under the cart to indicate that the final status of the cart is not confirmed by the server, but it will likely be confirmed soon. 
Since This is the shopping cart stage and not the checkout, we can handle this stage asynchronously. 
However, when we proceed to the checkout pages, we have to wait for the final calculation and validation results before registering the order.

The downside of this approach might be showing invalid cart items on the frontend, as the frontend is responsible for calculating the cart totals based on previous data. For most businesses, this error in negligible. However, for some other businesses, it might be impossible due to a lack of data in the frontend. 
For example, the frontend might not have access to shipping calculation formula. Additionally, this approach requires a lot of work on the frontend. The frontend needs to use the same calculation formula, which involves extra work and may not be worth it.

3- Implement Event-driven Communication: The third option involves implementing SocketIO to send cart events from backend to the frontend once the processing is complete. 
This combines elements of previous solutions, with the frontend receiving an 'OK' message upon initiating the request. Subsequently, when the processing is finished, an event is sent to the frontend via WebSocket. While this approach provides real-time updates to the frontend, it requires additional implementation efforts.


## Conclusion
Regarding the Sync or Async challenge, I prefer the first option. Nonetheless, it's imperative to calculate and return the latest cart status whenever the frontend requests it. Whether to handle the process synchronously or asynchronously is entirely up to your discretion. You may opt to manage it entirely on the frontend side or delegate it to the backend while minimizing frontend involvement.

I will continue to explore system design topics, with a particular focus on e-commerce concepts, in future articles.

I'm interested in hearing your perspective. If you have any comments or questions, please don't hesitate to share them. Thank you for taking the time to read!