Build A Product Catalog Module With Inventory Management

by Alex Johnson 57 views

In the realm of e-commerce, a well-structured and efficient product catalog module is the backbone of any successful online store. This article delves into the creation of such a module, focusing on inventory management, filtering capabilities, and ensuring thread-safe operations. We will explore the implementation details, testing strategies, and acceptance criteria, providing a comprehensive guide for developers. Let's dive in!

๐Ÿ“‹ Quick Summary

This task involves creating product catalog and inventory management functionality.

๐Ÿ“Š Metadata

  • Priority: medium
  • Dependencies: None
  • Status: Pending
  • Workflow Run: play-project-workflow-template-tbjbc
  • Started: 2025-11-12 02:59:10 UTC

๐Ÿ“– Full Task Details

Overview

The objective is to implement a comprehensive product catalog system with inventory management, advanced filtering capabilities, and thread-safe in-memory storage for the e-commerce API. This involves designing and developing a module that allows for efficient product CRUD (Create, Read, Update, Delete) operations, accurate inventory tracking, and robust search functionalities.

Context

This is a Level 0 task, meaning it has no dependencies and can be developed independently. The module will provide:

  • Product CRUD operations: Essential for managing the product catalog.
  • Inventory tracking and management: Crucial for maintaining accurate stock levels.
  • Product filtering and search: Enhances user experience by allowing easy product discovery.
  • Thread-safe concurrent access: Ensures the system remains stable under heavy load.
  • Decimal precision for financial calculations: Maintains accuracy in pricing and financial data.

Importantly, this module is designed to be independent, enabling parallel development alongside Tasks 1, 3, and 6. This approach accelerates the overall project timeline by allowing multiple developers to work simultaneously on different components.

Objectives

The primary objectives of this task are:

  1. Implement a thread-safe ProductService with in-memory storage: This involves creating a service that can handle concurrent requests without data corruption. The in-memory storage provides fast access to product data.
  2. Create Product and NewProduct models: These models define the structure of product data, including attributes like name, description, price, and inventory count. Product model includes the id and the NewProduct model is the same as Product model without the id field.
  3. Implement product filtering by name, price, and stock status: This feature allows users to quickly find products based on specific criteria.
  4. Handle decimal prices with rust_decimal: Ensuring accurate representation and calculation of prices.
  5. Support concurrent access with Arc: Employing Rust's concurrency primitives to ensure thread safety.
  6. Provide auto-incrementing product IDs: Automatically assign unique IDs to new products.

Dependencies

None - This task is foundational and can run in parallel with Tasks 1, 3, and 6, facilitating a streamlined development process. Because the task has no dependencies, the task can run independently and can accelerate the whole progress.

Architecture Context

For architectural context, refer to the following sections in .taskmaster/docs/architecture.md:

  • Product Catalog Module (lines 234-263): Provides details on the service implementation.
  • Backend Architecture (lines 73-105): Explains the overall module structure.
  • API Endpoints (lines 374-396): Describes the product routes.

Implementation Plan

Step 1: Add Product Catalog Dependencies

Update Cargo.toml to include necessary dependencies:

[dependencies]
rust_decimal = { version = "1.30", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Here's a breakdown of the dependencies:

  • rust_decimal: This crate provides a Decimal type for accurate decimal arithmetic, which is crucial for handling prices and financial calculations. The serde feature enables serialization and deserialization of Decimal values.
  • serde: This crate provides a framework for serializing and deserializing data structures. Serialization is the process of converting a data structure into a format that can be easily stored or transmitted, while deserialization is the reverse process of converting a serialized format back into a data structure. The derive feature enables automatic generation of serialization and deserialization code for custom data structures.
  • serde_json: This crate provides support for serializing and deserializing data structures to and from JSON format.

Step 2: Create Catalog Module Structure

Create src/catalog/mod.rs to define the module structure and exports:

pub mod models;
pub mod service;

pub use self::models::{Product, NewProduct, ProductFilter};
pub use self::service::ProductService;

This module acts as the entry point for the catalog feature. It defines two submodules, models and service, and then exports the Product, NewProduct, ProductFilter models, and the ProductService.

Step 3: Implement Product Models

Create src/catalog/models.rs to define the data models:

use serde::{Serialize, Deserialize};
use rust_decimal::Decimal;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Product {
    pub id: i32,
    pub name: String,
    pub description: String,
    pub price: Decimal,
    pub inventory_count: i32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct NewProduct {
    pub name: String,
    pub description: String,
    pub price: Decimal,
    pub inventory_count: i32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ProductFilter {
    pub name_contains: Option<String>,
    pub min_price: Option<Decimal>,
    pub max_price: Option<Decimal>,
    pub in_stock: Option<bool>,
}

impl ProductFilter {
    pub fn new() -> Self {
        ProductFilter {
            name_contains: None,
            min_price: None,
            max_price: None,
            in_stock: None,
        }
    }
}

Here's a breakdown of the data models:

  • Product: Represents a product with an ID, name, description, price, and inventory count. The Decimal type from the rust_decimal crate is used for the price to ensure accurate decimal arithmetic. The Clone trait is derived to allow cloning of Product instances.
  • NewProduct: Represents a new product to be created. It has the same fields as Product, but without the ID, as the ID will be automatically generated by the service.
  • ProductFilter: Represents a filter for querying products. It has optional fields for filtering by name, price range, and stock status. The new() method creates a default filter with all fields set to None.

Step 4: Implement ProductService

Create src/catalog/service.rs to implement the product service logic:

use crate::catalog::models::{Product, NewProduct, ProductFilter};
use rust_decimal::Decimal;
use std::sync::{Arc, Mutex};

pub struct ProductService {
    products: Arc<Mutex<Vec<Product>>>,
    next_id: Arc<Mutex<i32>>,
}

impl ProductService {
    pub fn new() -> Self {
        ProductService {
            products: Arc::new(Mutex::new(Vec::new())),
            next_id: Arc::new(Mutex::new(1)),
        }
    }

    pub fn create(&self, new_product: NewProduct) -> Product {
        let mut products = self.products.lock().unwrap();
        let mut next_id = self.next_id.lock().unwrap();

        let product = Product {
            id: *next_id,
            name: new_product.name,
            description: new_product.description,
            price: new_product.price,
            inventory_count: new_product.inventory_count,
        };

        *next_id += 1;
        products.push(product.clone());
        product
    }

    pub fn get_all(&self) -> Vec<Product> {
        let products = self.products.lock().unwrap();
        products.clone()
    }

    pub fn get_by_id(&self, id: i32) -> Option<Product> {
        let products = self.products.lock().unwrap();
        products.iter().find(|p| p.id == id).cloned()
    }

    pub fn update_inventory(&self, id: i32, new_count: i32) -> Option<Product> {
        let mut products = self.products.lock().unwrap();
        if let Some(product) = products.iter_mut().find(|p| p.id == id) {
            product.inventory_count = new_count;
            Some(product.clone())
        } else {
            None
        }
    }

    pub fn filter(&self, filter: ProductFilter) -> Vec<Product> {
        let products = self.products.lock().unwrap();
        products
            .iter()
            .filter(|p| {
                let name_match = filter.name_contains
                    .as_ref()
                    .map_or(true, |name| {
                        p.name.to_lowercase().contains(&name.to_lowercase())
                    });

                let min_price_match = filter.min_price
                    .map_or(true, |min| p.price >= min);

                let max_price_match = filter.max_price
                    .map_or(true, |max| p.price <= max);

                let in_stock_match = filter.in_stock
                    .map_or(true, |in_stock| (p.inventory_count > 0) == in_stock);

                name_match && min_price_match && max_price_match && in_stock_match
            })
            .cloned()
            .collect()
    }

    pub fn delete(&self, id: i32) -> bool {
        let mut products = self.products.lock().unwrap();
        let initial_len = products.len();
        products.retain(|p| p.id != id);
        products.len() < initial_len
    }
}

impl Default for ProductService {
    fn default() -> Self {
        Self::new()
    }
}

Here's a breakdown of the ProductService:

  • Data Storage: Uses Arc<Mutex<Vec<Product>>> for thread-safe in-memory storage of products. Arc allows multiple threads to own the data, while Mutex ensures exclusive access to prevent data races.
  • ID Generation: Uses Arc<Mutex<i32>> to manage auto-incrementing product IDs.
  • Methods: Implements CRUD operations (create, read, update, delete) and filtering.
    • new(): Creates a new ProductService with an empty product list and an initial ID of 1.
    • create(): Creates a new product, assigns a unique ID, and adds it to the product list.
    • get_all(): Returns all products.
    • get_by_id(): Returns a product by ID.
    • update_inventory(): Updates the inventory count for a product.
    • filter(): Filters products based on the provided ProductFilter.
    • delete(): Deletes a product by ID. This function is optional.

Step 5: Register Module

Update src/main.rs or src/lib.rs to register the catalog module:

pub mod catalog;

This line makes the catalog module available for use in the rest of the application.

Testing Strategy

The testing strategy involves creating comprehensive unit tests to ensure the functionality and reliability of the ProductService.

  1. Unit Tests for ProductService:

    • Test product creation with auto-incrementing IDs
    • Test retrieving all products
    • Test getting product by ID
    • Test inventory updates
    • Test product filtering with various criteria
    • Test concurrent access (multiple threads)
  2. Unit Tests for Filtering:

    • Test name filtering (case-insensitive)
    • Test price range filtering
    • Test stock status filtering
    • Test combined filters
  3. Decimal Price Tests:

    • Test decimal precision is maintained
    • Test price comparisons work correctly

Success Criteria

  • [ ] All catalog dependencies added to Cargo.toml
  • [ ] src/catalog/mod.rs created with module exports
  • [ ] src/catalog/models.rs defines Product, NewProduct, ProductFilter
  • [ ] src/catalog/service.rs implements ProductService
  • [ ] Products use Decimal type for prices
  • [ ] ProductService is thread-safe with Arc
  • [ ] Auto-incrementing IDs work correctly
  • [ ] CRUD operations implemented
  • [ ] Filtering works for all criteria
  • [ ] cargo check passes without errors
  • [ ] Unit tests verify all operations

Files Modified/Created

  • Cargo.toml - Add rust_decimal dependency
  • src/catalog/mod.rs - Module exports
  • src/catalog/models.rs - Product data models
  • src/catalog/service.rs - Business logic

Next Steps

After completion, this module will be used by:

  • Task 5: Shopping Cart API (validates products before adding to cart)
  • Task 2: API Endpoints (will add product routes)
  • Task 7: Integration Tests (tests product operations)

โœ… Acceptance Criteria

Required Files Created

1. Dependencies

  • [ ] rust_decimal = { version = "1.30", features = ["serde"] } in Cargo.toml
  • [ ] serde and serde_json present

2. Module Structure

  • [ ] src/catalog/mod.rs exists and exports models and service
  • [ ] src/catalog/models.rs exists
  • [ ] src/catalog/service.rs exists

3. Product Models

  • [ ] Product struct with id, name, description, price (Decimal), inventory_count
  • [ ] NewProduct struct for creation (no ID)
  • [ ] ProductFilter struct with optional filters
  • [ ] All structs derive Debug, Serialize, Deserialize
  • [ ] Product derives Clone

4. ProductService Implementation

  • [ ] Thread-safe storage with Arc<Mutex<Vec>>
  • [ ] Auto-incrementing ID management
  • [ ] new() method implemented
  • [ ] create() method implemented
  • [ ] get_all() method implemented
  • [ ] get_by_id() method implemented
  • [ ] update_inventory() method implemented
  • [ ] filter() method implemented
  • [ ] delete() method implemented (optional)

Functional Requirements

Product Creation

  • [ ] Creates products with unique auto-incrementing IDs
  • [ ] IDs start at 1 and increment sequentially
  • [ ] Returns created product with assigned ID
  • [ ] Handles concurrent creation correctly

Product Retrieval

  • [ ] get_all() returns all products
  • [ ] get_by_id() returns Some(product) if found
  • [ ] get_by_id() returns None if not found
  • [ ] Clones products (doesn't expose internal state)

Inventory Management

  • [ ] Updates inventory count for existing products
  • [ ] Returns updated product
  • [ ] Returns None for non-existent products
  • [ ] Handles negative inventory counts

Product Filtering

  • [ ] Name filter is case-insensitive
  • [ ] Name filter uses substring matching
  • [ ] Min price filter works correctly
  • [ ] Max price filter works correctly
  • [ ] In stock filter returns only products with inventory > 0
  • [ ] Out of stock filter returns products with inventory = 0
  • [ ] Multiple filters work together (AND logic)
  • [ ] Empty filter returns all products

Decimal Precision

  • [ ] Prices use rust_decimal::Decimal type
  • [ ] Decimal precision is maintained
  • [ ] Price comparisons work correctly
  • [ ] Serialization preserves decimal values

Thread Safety

  • [ ] Multiple threads can safely create products
  • [ ] Multiple threads can safely read products
  • [ ] No data races occur
  • [ ] Lock contention is minimized

Compilation and Tests

Build Requirements

  • [ ] cargo check passes
  • [ ] cargo build succeeds
  • [ ] cargo clippy has no warnings
  • [ ] cargo fmt --check passes

Test Coverage

  • [ ] Test product creation
  • [ ] Test ID auto-increment
  • [ ] Test get_all
  • [ ] Test get_by_id (found and not found)
  • [ ] Test update_inventory
  • [ ] Test filtering by name
  • [ ] Test filtering by price range
  • [ ] Test filtering by stock status
  • [ ] Test combined filters
  • [ ] Test concurrent access
  • [ ] Test decimal precision

Definition of Done

  1. All files created and compile
  2. All functional requirements pass
  3. All tests pass
  4. Thread-safe concurrent access verified
  5. Ready for integration with Task 5 (Shopping Cart)

๐ŸŽฏ Implementation Notes (from tasks.json)

Implement the product catalog module with inventory management:

  1. Create src/catalog/mod.rs to export the catalog module components:
pub mod models;
pub mod service;

pub use self::models::Product;
pub use self::service::ProductService;
  1. Create src/catalog/models.rs for product models:
use serde::{Serialize, Deserialize};
use rust_decimal::Decimal;

#[derive(Debug, Serialize, Deserialize)]
pub struct Product {
    pub id: i32,
    pub name: String,
    pub description: String,
    pub price: Decimal,
    pub inventory_count: i32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct NewProduct {
    pub name: String,
    pub description: String,
    pub price: Decimal,
    pub inventory_count: i32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ProductFilter {
    pub name_contains: Option<String>,
    pub min_price: Option<Decimal>,
    pub max_price: Option<Decimal>,
    pub in_stock: Option<bool>,
}
  1. Create src/catalog/service.rs for product service logic:
use crate::catalog::models::{Product, NewProduct, ProductFilter};
use rust_decimal::Decimal;
use std::sync::{Arc, Mutex};

// In a real app, this would interact with the database
pub struct ProductService {
    products: Arc<Mutex<Vec<Product>>>,
    next_id: Arc<Mutex<i32>>,
}

impl ProductService {
    pub fn new() -> Self {
        ProductService {
            products: Arc::new(Mutex::new(Vec::new())),
            next_id: Arc::new(Mutex::new(1)),
        }
    }
    
    pub fn create(&self, new_product: NewProduct) -> Product {
        let mut products = self.products.lock().unwrap();
        let mut next_id = self.next_id.lock().unwrap();
        
        let product = Product {
            id: *next_id,
            name: new_product.name,
            description: new_product.description,
            price: new_product.price,
            inventory_count: new_product.inventory_count,
        };
        
        *next_id += 1;
        products.push(product.clone());
        product
    }
    
    pub fn get_all(&self) -> Vec<Product> {
        let products = self.products.lock().unwrap();
        products.clone()
    }
    
    pub fn get_by_id(&self, id: i32) -> Option<Product> {
        let products = self.products.lock().unwrap();
        products.iter().find(|p| p.id == id).cloned()
    }
    
    pub fn update_inventory(&self, id: i32, new_count: i32) -> Option<Product> {
        let mut products = self.products.lock().unwrap();
        if let Some(product) = products.iter_mut().find(|p| p.id == id) {
            product.inventory_count = new_count;
            Some(product.clone())
        } else {
            None
        }
    }
    
    pub fn filter(&self, filter: ProductFilter) -> Vec<Product> {
        let products = self.products.lock().unwrap();
        products
            .iter()
            .filter(|p| {
                let name_match = filter.name_contains
                    .as_ref()
                    .map_or(true, |name| p.name.to_lowercase().contains(&name.to_lowercase()));
                
                let min_price_match = filter.min_price
                    .as_ref()
                    .map_or(true, |min| p.price >= *min);
                
                let max_price_match = filter.max_price
                    .as_ref()
                    .map_or(true, |max| p.price <= *max);
                
                let in_stock_match = filter.in_stock
                    .as_ref()
                    .map_or(true, |in_stock| (p.inventory_count > 0) == in_stock);
                
                name_match && min_price_match && max_price_match && in_stock_match
            })
            .cloned()
            .collect()
    }
}
  1. Update Cargo.toml to add catalog dependencies:
[dependencies]
rust_decimal = { version = "1.30", features = ["serde"] }

๐Ÿงช Test Strategy (from tasks.json)

  1. Verify that all required files are created: src/catalog/mod.rs, src/catalog/models.rs, and src/catalog/service.rs
  2. Compile the code to ensure there are no syntax errors
  3. Write unit tests to verify product creation, retrieval, and inventory management
  4. Test the product filtering functionality with various filter combinations
  5. Verify that the catalog dependencies in Cargo.toml are correctly specified and can be resolved
  6. Check that the Product models can be serialized and deserialized correctly

๐Ÿ”— Task Master Integration

This issue is automatically synchronized with TaskMaster.

Task File: .taskmaster/tasks/tasks.json Task Details: .taskmaster/docs/task-4/ Service: cto-parallel-test Workflow: play-project-workflow-template-tbjbc

Agent Pipeline

  1. Rex - Implementation
  2. Cleo - Code Quality Review
  3. Cipher - Security Analysis (if enabled)
  4. Tess - QA Testing
  5. Atlas - Integration & Merge
  6. Bolt - Production Deployment

๐Ÿ“ก Live Status

This issue is monitored by Morgan PM. Status updates are posted automatically as agents progress through the workflow.

Current Status: View the Project board for real-time updates Feedback: Comment with @morgan to request scope changes or clarifications


This issue is managed by Morgan (Project Manager) for workflow play-project-workflow-template-tbjbc.

External Resource: For more information on Rust and its ecosystem, visit the official Rust website. This will provide more info on the language itself.