Build A Product Catalog Module With Inventory Management
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:
- 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.
- Create Product and NewProduct models:
These models define the structure of product data, including attributes like name, description, price, and inventory count.
Productmodel includes theidand theNewProductmodel is the same as Product model without theidfield. - Implement product filtering by name, price, and stock status: This feature allows users to quickly find products based on specific criteria.
- Handle decimal prices with rust_decimal: Ensuring accurate representation and calculation of prices.
- Support concurrent access with Arc
: Employing Rust's concurrency primitives to ensure thread safety. - 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 aDecimaltype for accurate decimal arithmetic, which is crucial for handling prices and financial calculations. Theserdefeature enables serialization and deserialization ofDecimalvalues.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. Thederivefeature 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. TheDecimaltype from therust_decimalcrate is used for the price to ensure accurate decimal arithmetic. TheClonetrait is derived to allow cloning ofProductinstances.NewProduct: Represents a new product to be created. It has the same fields asProduct, 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. Thenew()method creates a default filter with all fields set toNone.
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.Arcallows multiple threads to own the data, whileMutexensures 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 newProductServicewith 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 providedProductFilter.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.
-
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)
-
Unit Tests for Filtering:
- Test name filtering (case-insensitive)
- Test price range filtering
- Test stock status filtering
- Test combined filters
-
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.rscreated with module exports - [ ]
src/catalog/models.rsdefines Product, NewProduct, ProductFilter - [ ]
src/catalog/service.rsimplements 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 checkpasses without errors - [ ] Unit tests verify all operations
Files Modified/Created
Cargo.toml- Add rust_decimal dependencysrc/catalog/mod.rs- Module exportssrc/catalog/models.rs- Product data modelssrc/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.rsexists and exports models and service - [ ]
src/catalog/models.rsexists - [ ]
src/catalog/service.rsexists
3. Product Models
- [ ]
Productstruct with id, name, description, price (Decimal), inventory_count - [ ]
NewProductstruct for creation (no ID) - [ ]
ProductFilterstruct 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 checkpasses - [ ]
cargo buildsucceeds - [ ]
cargo clippyhas no warnings - [ ]
cargo fmt --checkpasses
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
- All files created and compile
- All functional requirements pass
- All tests pass
- Thread-safe concurrent access verified
- Ready for integration with Task 5 (Shopping Cart)
๐ฏ Implementation Notes (from tasks.json)
Implement the product catalog module with inventory management:
- Create
src/catalog/mod.rsto export the catalog module components:
pub mod models;
pub mod service;
pub use self::models::Product;
pub use self::service::ProductService;
- Create
src/catalog/models.rsfor 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>,
}
- Create
src/catalog/service.rsfor 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()
}
}
- Update
Cargo.tomlto add catalog dependencies:
[dependencies]
rust_decimal = { version = "1.30", features = ["serde"] }
๐งช Test Strategy (from tasks.json)
- Verify that all required files are created:
src/catalog/mod.rs,src/catalog/models.rs, andsrc/catalog/service.rs - Compile the code to ensure there are no syntax errors
- Write unit tests to verify product creation, retrieval, and inventory management
- Test the product filtering functionality with various filter combinations
- Verify that the catalog dependencies in
Cargo.tomlare correctly specified and can be resolved - 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
- Rex - Implementation
- Cleo - Code Quality Review
- Cipher - Security Analysis (if enabled)
- Tess - QA Testing
- Atlas - Integration & Merge
- 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.