An ecommerce web application. the backend system follows a microservices based architecture which consists of the following services:
- authenticaiton-service: Microservice for authentication and user accounts managment (Developed in Java Springboot)
- products-service: Microservice for products and categories management and CRUD operations (Developed in Java Springboot)
- carts-service: Microservice for Shopping cart management and syncing (Developed in Java Springboot)
- orders-service: Microservice for customer orders management and CRUD operations (Developed in Java Springboot)
- ai-service: Microservice responsible for adds on AI features such as (Chatbot, Recommendation service & vector search) (Developed in Python FastAPI )
Other components include:
- nginx: API Gateway for routing traffic to microservices
- postgres DB
- swagger-ui: Centralized service hosting complete OpenAPI documentation for the backend end system
- zcommerce-ui: Frontend side of zcommerce
| Component | Technology |
|---|---|
| Backend Frameworks | Java 21 (Spring Boot), Python 3.12 (FastAPI) |
| Frontend Framework | Next.js |
| Database | PostgreSQL 15 + pgvector |
| API Gateway | Nginx |
| Security | JWT (Stateless), Spring Security |
| AI / ML | Gemini LLM (MCP), Scikit-Learn (PCA), PyTorch (NCF) |
| Documentation | OpenAPI 3.0 / Swagger UI |
| DevOps | Docker, Docker Compose |
๐ฅ Watch the ZCommerce System Showcase
In this section each microservice is further broken down to its internal and related operations
Each microservice share the same layered architecture pattern for seperation of concerns. The layered architecture consists of:
- controllers (Presentation layer)
- services (Business logic layer)
- dtos (Data transfer objects)
- models (Domain layer)
- repositories (Data Access Layer)
- clients (External integration layer)
- configurations (Definiiton of Service configuraitons)
- exceptions (Custom excpetion definitions and Exception handling)
Mainly the service is responsible for
- Creating new user accounts through the signup endpoint
- Authenticating users through a login endpoint
- User accounts management
There are three types of accounts based on their authorization roles:
- ADMIN: Responsible for Inventory management
- USER: Registered non-admin users that can access all the available features presented by the website including purchasing orders.
- GUEST: This role is for guest users where they only can browse products, adding products to carts but is required to signup/login to complete the purchase. This kind of role gives high flexibility for customers to discover the website without the burden of creating accounts.
A JWT token is generated through the following endpoints
-
/auth-token: Generates a token with the containing the GUEST role ENUM in the metadata for guest users.
-
/login: Generates a token for registered users, with USER role enum for regular registered users and ADMIN role enum for registered admin users.
The JWT token consists of other user metadata other than the authorization roles such as:
- User ID (Required for business logic operations)
- Location, Job (Optional and are primarily used for the recommendation engine operation)
The JWT token is resolved through a defined JWT Security filter utilizing Security filters presented by the Spring Security project.
The JWT Token resolution layer exists at each microservice to authenticate all API calls, for this scenario the JWT Filter configuraiton and JWT utility is shared as a common local Maven dependency for the rest of services where it can be found in the /libs directory inside each service.
Mainly the service is responsible for:
- Products CRUD operations, key operations include:
- Fetching products based on both Semantic and Syntax search
- Returning Top K recommended products based on customer metadata
- Adding new products
- Product categories CRUD operations
- Integration with the ai-service through REST for the following operations:
- Generating vector embeddings for each product, the vector embedding is later used for recommendations and semantic search operations
- Fetching a recommendation scores for a specific product
Admin-based endpoints are annotated with the @PreAuthorize annotation to apply authorizaiton check, as shown in the below sample controller endpoint definition:
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/add-product")
public ResponseEntity<ProductDto> addProduct(@RequestBody @Valid ProductDto productDto) {
// Implementation for adding a product
logger.info("Received request to add product: {}", productDto);
/* Add product */
productDto = productsService.addProduct(productDto);
logger.info("Product added successfully: {}", productDto);
/* Success Response */
return ResponseEntity.status(HttpStatus.CREATED).body(productDto);
}AI-based features are accessed via integrating with the ai-service (Explained in depth below) for the following features:
Generating a vector embeddings for each newly added product:
Endpoint: /vector
Logic: A statement is created as follows "[CATEGORY] " + {productCategory} + " [NAME] " + {productName} + " [DESC] " + {productDescription}. This statement is then fed to the ai-service vector embedding endpoint to generate a corresponding vector embedding that act as a similarity score. This similarity score is then used for semantic search & recommendation-based operations.
Use case: This feature is used A. during product search where the semantic part of the search is done based on the cosine similarity between the search text and products in the database, B. When products are added where the similarity score of the product is calculated and stored in the DB to fetch results that are similar to the searched text.
ENDPOINT: /predict a JSON body is sent containing the user metadata as below:
{
"user_id": 200,
"item_id": 90,
"job": "Student",
"location": "Alexandria",
"category": "Sports"
}Based on the input JSON body a recommendation score [0,1] is returned from the ai-service measuring the probability that the user would buy the product.
When a user searches for a product the search result is built on both a semantic-based search process and a syntax-based search process
semantic-search: Search based on the context similarity between the search data and the product records in the database, this search is good for finiding similar matches for ex. When a user searches for the word summer he can find summer-related products appearing.
syntax-search: Search based on the syntax search data. This is a straight forward search process to find actual products that matches the syntax of the search data.
The integration between semantic-search and syntax-based results is done through a method called Rank Reciprocal Fusion (RRF), where a score is calculated for all products from the two lists, the score follows the following equation 1 / (k + index) where k is a constant value usually set to a number like 60 and index is the index of the product indicating its order within the list. products with top scores are selected as the final filtered list for the hybrid search result.
Mainly the service is responsible for the following:
- User carts management operations including:
- Creating a shopping cart
- Reseting a shopping carts
- Adding a product to a shopping cart
- Removing a product from a shopping cart
A shopping cart is created for guest users to enable them add products to the cart, whenever the user wishes to complete the purchase a login is required.
Once the user is registered the guest cart is synced with the user's actual cart, the syncing process goes as:
- Guest cart fetched
- User's cart fetched
- Products from the guest cart are added to the user's cart
When adding products or removing products to shopping carts, an integration with products-service is done to validate the existence of the products in terms of quantity or product id validity before being added to the shopping cart. Sample of interservice communication is shown below
List<CartItems> cartItemsList = cartItemRepository.findByCartId(cart.getId());
cartItemsList.forEach(cartItem -> {
/* Fetch product */
ProductDto productDto = productServiceExternalClient.fetchProductById(cartItem.getProductId().toString());
/* Define CartItemDto */
CartItemDto cartItemDto = new CartItemDto();
/* Map cart item */
CartMapper.productToCartItemDto(productDto, cartItemDto);
cartItemDto.setProductTotalPrice(productDto.getPrice() * cartItem.getQuantity());
cartItemDto.setProductQuantity(cartItem.getQuantity());
cartDto.setTotalPrice(cartDto.getTotalPrice() + cartItemDto.getProductTotalPrice());
cartDto.setTotalQuantity(cartDto.getTotalQuantity() + cartItem.getQuantity());
/* Add to cart items list */
cartItemDtos.add(cartItemDto);
});
cartDto.setCartItems(cartItemDtos);Mainly the microservice is responsible for the following:
- Placing orders
- Confirming orders
- Fetching delivered orders
- Fetching pending orders
A user should be registered to place orders, fetch orders or do any operation related to order management. Thus an authorizaiton check is done to check whether the user has either a USER role or an ADMIN role. As shown in the block of code below
/* Extract user ID and check user type */
SessionBodyDto sessionBodyDto = userContext.getSession();
// /* Check for guest */
if(sessionBodyDto.getRole().equals("GUEST")) {
/* Guest user */
throw new UserIsGuestException("Guest users cannot place orders");
}In order to fetch a complete order detail summary, the order-service integrates externally with products-service through REST to fetch product details as shown in the below code snippet
/* Get order items */
List<OrderItem> orderItems = orderItemRepository.findByOrderId(order.getId().toString());
List<OrderItemDetailsDto> orderItemDtos = new ArrayList<>();
orderItems.forEach((orderItem) -> {
/* Fetch product from external API calls */
ProductDto productDto = productServiceExternalClient.fetchProductById(orderItem.getProductId());
OrderItemDetailsDto orderItemDto = new OrderItemDetailsDto();
OrderMapper.productToOrderItemDto(productDto, orderItemDto);
orderItemDto.setProductQuantity(orderItem.getQuantity());
orderItemDto.setProductTotalPrice(productDto.getPrice() * orderItem.getQuantity());
orderItemDtos.add(orderItemDto);
});
/* Set order items */
orderDetailsDto.setOrderItems(orderItemDtos);Mainly the service is responsible for the following:
- Generating embedding vector for a given string
- Generate a recommendation score
- Chatbot feature
In this feature a model is used for generating an embedding vector of size 32 of a given input string. For this case a TextEmbedding model like all-MiniLM-L6-v2 is used to generate the embedding vector.
Initially the model generates a 384-d embedding and then using PCA method the dimensionality is reduced from 384 to 32.
In this feature an NCF model is built for this task. A Neural Collaborative Filtering (NCF) Model that uses Multi-Layer Perceptron (MLP) to return a final score for a given user & item embeddings.
The NCF Model used for the products recommendation task contains the following properties was built as:
- An Embedding layer of (user_id, item_id, job, location, category) used as the input embeddings to the Model
- The MLP layers dimensions were as follows [64,32,16,8]
- A sigmoid function is used on the predict layer to return the final prediction score
Since there is no enough real production data for the website that can be used for the model training, A generated dataset were userd for the model training, these datasets were designed to define different personas that are hypothetically representing the customers of the webiste. Sample data sets were like:
personas = {
"Tech Enthusiast": {
"job": "Software Engineer",
"location": "Riyadh",
"categories": ["Electronics", "Computers", "Accessories"],
"avg_spend": "High"
},
"Student": {
"job": "Student",
"location": "Alexandria",
"categories": ["Stationery", "Books", "Backpacks"],
"avg_spend": "Low"
},
"Fitness Buff": {
"job": "Personal Trainer",
"location": "Dubai",
"categories": ["Sports", "Health", "Clothing"],
"avg_spend": "Medium"
}
}After the model training, the weights of the model are exported in .pt format and is used in the model definition inside the ai-service for inference.
For the chatbot feature, the ai-service integrated with external LLM this case GEMINI LLM models were used through the genai python library tool. MCP tasks were defined utilizing the strength of MCP for giving the LLM enough context about the ecommerce inventory regarding orders, products, interacting user's shopping data and orders history list. sample of the defined MCP tasks are shown as below:
@mcp.tool()
def fetch_cart_content_mcp():
"""
Fetch the items currently in the user's shopping cart or bag.
Use this for queries like 'what is in my bag?', 'show my cart', or 'check my items'.
"""
return run_async(fetch_cart_content())
@mcp.tool()
def fetch_top_bought_products_mcp():
"""Fetch products with the highest total sales volume overall."""
return run_async(fetch_top_bought_products())
@mcp.tool()
def fetch_top_ordered_products_mcp():
"""Fetch products most frequently appearing in orders overall."""
return run_async(fetch_top_ordered_products())
@mcp.tool()
def fetch_website_products_mcp():
"""Fetch a general list of available products on the website."""
return run_async(fetch_all_products())Also powerful mcp tasks such as add_to_cart_mcp which gives the opportunity for customers instructing the chatbot agent to add orders inside the cart easing the user's shopping experience.
The chatbot agent's responses were designed to be structured, formal and personalized through a set of defined system prompts that shapes the format of the response of the chatbot agent.
A Docker compose file is written to easily contenarize and deploy zcommerce. To run it up we can follow these steps:
1- Clone the repo locally
git clone 2- Update the docker compose variables regarding the LLM integration for the chatbot feature (Optional)
- API_KEY=**YOUR_API_KEY**
- GEMINI_MODEL=MODEL (for ex. gemini-3-flash-preview)3- Run compose
docker compose up4- Enjoy using zcommerce either through the frontend portal http://localhost:3000/ or through the OpenAPI swagger interactive interface http://localhost:80/swagger .
You can check the swagger openAPI documentation for the complete API documentation for the zcommerce backend system through BACKEND_PATH/swagger. The documentation can be used to populate the database by adding products and categories as an Admin user through the following endpoints:
- /authentication/auth/signup: Create an admin user account, using the sample request body:
{
"username": "USERNAME",
"password": "PASS",
"email": "EMAIL",
"firstName": "FIRST_NAME",
"lastName": "LAST_NAME",
"role": "ADMIN"
}- /authentication/auth/login: Login using the admin account
{
"password": "PASS",
"email": "EMAIL"
}- categories/admin/create-category: Add product categories (Authenticated with admin token)
{
"name": "Clothes",
"description": "All sorts of clothers that can be found in here"
}- products/admin/add-product: Add a new product (Authenticated with admin token)
{
"name": "Men's Cotton T-Shirt",
"description": "Soft cotton crew neck T-shirt available in multiple colors",
"price": 250,
"currency": "EGP",
"image1": "https://images.pexels.com/photos/996329/pexels-photo-996329.jpeg",
"stockQuantity": 50,
"categoryId": "CATEGORY_ID_RETURNED_FROM_LAST_REQUEST"
}