-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Problem
The ProductService class in src/lib/services/product.service.ts is 1,662 lines and contains 30+ methods. While well-organized, it has significant code duplication in Prisma query patterns:
- Repeated
includeblocks for product relations (category, brand, variants, _count) appear in 10+ methods - Identical relation selection patterns duplicated across CRUD operations
- Similar error handling patterns repeated throughout
- Maintenance burden - any change to query structure requires updates in multiple places
- Testing complexity - large service class is harder to test comprehensively
This makes the service harder to maintain, test, and extend.
Current Code Location
- File:
src/lib/services/product.service.ts - Lines: 1,662 total
- Complexity: High (30+ methods, multiple concerns)
- Key methods affected:
getProducts,getProductById,getProductBySlug,createProduct,updateProduct,restoreProduct,updateInventory, and others
Proposed Refactoring
Extract reusable Prisma query configuration builders into a separate module or class methods to eliminate duplication.
Benefits
- DRY principle - Single source of truth for query patterns
- Consistency - All queries use the same relation structure
- Easier maintenance - Update query patterns in one place
- Better testability - Query builders can be tested independently
- Improved readability - Main service methods focus on business logic, not query construction
- Type safety - Centralized query builders ensure consistent typing
Suggested Approach
-
Create query builder helpers in the ProductService class:
getProductInclude()- Standard product relationsgetProductSelect()- Common selection patternsgetVariantSelect()- Standard variant selection
-
Extract common patterns:
- Full product with relations (for detail views)
- List product with minimal relations (for tables/lists)
- Variant-only queries (for inventory operations)
-
Refactor existing methods to use builders:
- Replace duplicated
includeblocks with method calls - Maintain same functionality while reducing code
- Replace duplicated
-
Consider extracting to separate module if builders grow large:
src/lib/services/product/query-builders.ts- Keep service focused on business logic
Code Example
Before (current duplication pattern):
// Appears 10+ times with slight variations
async getProductById(productId: string, storeId: string): Promise(ProductWithRelations | null) {
const product = await prisma.product.findFirst({
where: { id: productId, storeId, deletedAt: null },
include: {
category: { select: { id: true, name: true, slug: true } },
brand: { select: { id: true, name: true, slug: true } },
variants: {
select: {
id: true, name: true, sku: true, barcode: true,
price: true, compareAtPrice: true, inventoryQty: true,
lowStockThreshold: true, weight: true, image: true,
options: true, isDefault: true, createdAt: true, updatedAt: true,
},
orderBy: { isDefault: 'desc' },
},
_count: { select: { orderItems: true, reviews: true } },
},
});
return this.normalizeProductFields(product) as unknown as ProductWithRelations;
}
// Similar block repeated in: getProductBySlug, createProduct, updateProduct,
// restoreProduct, updateInventory, etc.After (using query builders):
// Helper methods (add to ProductService class)
private getStandardProductInclude(): Prisma.ProductInclude {
return {
category: { select: { id: true, name: true, slug: true } },
brand: { select: { id: true, name: true, slug: true } },
variants: {
select: this.getStandardVariantSelect(),
orderBy: { isDefault: 'desc' as const },
},
_count: { select: { orderItems: true, reviews: true } },
};
}
private getStandardVariantSelect() {
return {
id: true, name: true, sku: true, barcode: true,
price: true, compareAtPrice: true, inventoryQty: true,
lowStockThreshold: true, weight: true, image: true,
options: true, isDefault: true, createdAt: true, updatedAt: true,
};
}
private getListProductInclude(): Prisma.ProductInclude {
return {
category: { select: { id: true, name: true, slug: true } },
brand: { select: { id: true, name: true, slug: true } },
variants: {
select: {
id: true, name: true, sku: true, price: true,
inventoryQty: true, isDefault: true, image: true,
},
orderBy: { isDefault: 'desc' as const },
},
_count: { select: { orderItems: true, reviews: true } },
};
}
// Refactored methods (cleaner, more maintainable)
async getProductById(productId: string, storeId: string): Promise(ProductWithRelations | null) {
const product = await prisma.product.findFirst({
where: { id: productId, storeId, deletedAt: null },
include: this.getStandardProductInclude(),
});
return this.normalizeProductFields(product) as unknown as ProductWithRelations;
}
async getProducts(
storeId: string,
filters: ProductSearchFilters = {},
page: number = 1,
perPage: number = 10
): Promise(ProductListResult) {
const where = this.buildWhereClause(storeId, filters);
const orderBy = this.buildOrderByClause(filters.sortBy, filters.sortOrder);
const [products, total] = await Promise.all([
prisma.product.findMany({
where,
include: this.getListProductInclude(), // Use list-optimized include
orderBy,
take: perPage,
skip: (page - 1) * perPage,
}),
prisma.product.count({ where }),
]);
return {
products: products.map(p => this.normalizeProductFields(p)) as unknown as ProductWithRelations[],
pagination: { page, perPage, total, totalPages: Math.ceil(total / perPage) },
};
}Impact Assessment
- Effort: Medium - Requires careful refactoring of 10+ methods (~4-6 hours)
- Risk: Low-Medium - Query behavior must remain identical (requires thorough testing)
- Benefit: High - Significantly improves maintainability and reduces code by ~200+ lines
- Priority: Medium-High - High-impact improvement to a critical service
Related Files
src/lib/services/product.service.ts(primary file)src/lib/services/order.service.ts(similar patterns, could benefit from same approach)src/lib/services/inventory.service.ts(may share some query patterns)
Testing Strategy
-
Preserve existing behavior:
- All existing tests must pass without modification
- Query results must be identical to current implementation
-
Unit test query builders:
- Test
getStandardProductInclude()returns correct structure - Test
getListProductInclude()returns optimized structure - Test variant selection patterns
- Test
-
Integration tests:
- Verify product list queries return correct data
- Verify product detail queries include all relations
- Test all CRUD operations still work correctly
-
Performance testing:
- Benchmark query performance before/after
- Ensure no performance regression
-
Manual testing:
- Test product listing pages
- Test product detail pages
- Test product creation/update flows
- Verify inventory management still works
AI generated by Daily Codebase Analyzer - Semantic Function Extraction & Refactoring
- expires on Mar 6, 2026, 1:56 PM UTC
Metadata
Metadata
Assignees
Type
Projects
Status