-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Description
Problem
FacebookCatalogManager is the 2nd largest service in the codebase at 1454 lines, handling multiple distinct responsibilities that should be separated for better maintainability, testability, and scalability.
Current Responsibilities (All in One Class)
- Catalog lifecycle management - Creating, updating, deleting catalogs
- Product feed generation - Building XML/CSV feeds for Facebook
- Product syncing - Batch syncing products to Facebook API
- Batch API operations - Handling Facebook Graph API batch requests
- Error handling and retry logic - Managing API failures and rate limits
- Feed validation - Validating product data against Facebook requirements
- Image URL handling - Processing and validating product images
- Variant flattening - Converting multi-variant products to Facebook format
- Status tracking - Monitoring sync status and diagnostics
Maintenance Challenges
- Hard to navigate: 1454 lines require extensive scrolling and context switching
- Difficult to test: Testing requires understanding entire integration context
- Merge conflicts: Multiple developers working on different Facebook features
- Tight coupling: Changes to feed generation affect sync logic
- Cognitive overload: Understanding all responsibilities requires deep Facebook API knowledge
- Long methods: Some methods exceed 100 lines (e.g.,
syncProductsToFacebook())
Current Structure
// src/lib/integrations/facebook/catalog-manager.ts (1454 lines)
export class FacebookCatalogManager {
// Lines 1-100: Types, interfaces, constants
// Lines 101-300: Catalog CRUD operations
// Lines 301-500: Feed generation (XML, CSV, RSS)
// Lines 501-700: Product syncing with batch API
// Lines 701-900: Error handling and retry logic
// Lines 901-1100: Feed validation and diagnostics
// Lines 1101-1300: Image processing and URL validation
// Lines 1301-1454: Helper methods and utilities
}
````
**Method count**: 30+ public and private methods
**External dependencies**: Facebook Graph API, Prisma, file system, HTTP client
## Current Code Location
- **File**: `src/lib/integrations/facebook/catalog-manager.ts` (1454 lines)
- **Complexity**: Very High - External API integration, batch operations, multiple data formats
- **Dependencies**:
- Facebook Graph API
- Product/Variant data models
- File system (feed generation)
- HTTP client (API calls)
## Proposed Refactoring
Decompose into focused, single-responsibility modules following Domain-Driven Design principles.
### Phase 1: Extract Service Modules
````
src/lib/integrations/facebook/
├── catalog-manager.ts # Orchestrator (200-250 lines)
├── catalog-operations.service.ts # CRUD only (200-250 lines)
├── product-feed.service.ts # Feed generation (XML/CSV/RSS) (300-350 lines)
├── product-sync.service.ts # Sync to Facebook API (300-350 lines)
├── batch-api.service.ts # Facebook batch API wrapper (150-200 lines)
├── feed-validation.service.ts # Feed validation logic (150-200 lines)
└── types.ts # Shared types and interfacesProposed Architecture
1. Catalog Operations Service (CRUD)
// src/lib/integrations/facebook/catalog-operations.service.ts (~200 lines)
/**
* Handles Facebook catalog lifecycle operations
* Single Responsibility: Catalog entity management
*/
export class CatalogOperationsService {
constructor(private graphApi: FacebookGraphApiClient) {}
async createCatalog(storeId: string, name: string): Promise(Catalog) {
// Create catalog via Facebook API
const response = await this.graphApi.post('/catalogs', {
name,
vertical: 'commerce',
});
// Save to database
return prisma.facebookCatalog.create({
data: {
storeId,
catalogId: response.id,
name,
},
});
}
async getCatalog(storeId: string): Promise(Catalog | null) {
return prisma.facebookCatalog.findFirst({
where: { storeId },
});
}
async updateCatalog(catalogId: string, updates: CatalogUpdate): Promise(Catalog) {
// Update via Facebook API
await this.graphApi.post(`/\$\{catalogId}`, updates);
// Update database
return prisma.facebookCatalog.update({
where: { catalogId },
data: updates,
});
}
async deleteCatalog(catalogId: string): Promise(void) {
// Delete from Facebook
await this.graphApi.delete(`/\$\{catalogId}`);
// Delete from database
await prisma.facebookCatalog.delete({
where: { catalogId },
});
}
}2. Product Feed Service (Feed Generation)
// src/lib/integrations/facebook/product-feed.service.ts (~350 lines)
/**
* Generates product feeds in various formats for Facebook
* Single Responsibility: Feed generation and formatting
*/
export class ProductFeedService {
async generateXmlFeed(storeId: string): Promise(string) {
const products = await this.getProductsForFeed(storeId);
const feedItems = products.map(p => this.formatProductForFeed(p));
return this.buildXml(feedItems);
}
async generateCsvFeed(storeId: string): Promise(string) {
const products = await this.getProductsForFeed(storeId);
const feedItems = products.map(p => this.formatProductForFeed(p));
return this.buildCsv(feedItems);
}
private async getProductsForFeed(storeId: string): Promise(Product[]) {
return prisma.product.findMany({
where: {
storeId,
status: 'ACTIVE',
deletedAt: null,
},
include: {
variants: true,
category: true,
},
});
}
private formatProductForFeed(product: Product): FacebookProductItem {
// Flatten variants to individual items
return product.variants.map(variant => ({
id: `\$\{product.id}_\$\{variant.id}`,
title: `\$\{product.name} - \$\{variant.name}`,
description: product.description,
availability: variant.inventoryQty > 0 ? 'in stock' : 'out of stock',
price: `\$\{variant.price} BDT`,
link: `(store.example.com/redacted),
image_link: variant.image || product.thumbnailUrl,
brand: product.brand?.name,
condition: 'new',
}));
}
private buildXml(items: FacebookProductItem[]): string {
// XML generation logic
}
private buildCsv(items: FacebookProductItem[]): string {
// CSV generation logic
}
}3. Product Sync Service (API Sync)
// src/lib/integrations/facebook/product-sync.service.ts (~350 lines)
/**
* Syncs products to Facebook catalog via batch API
* Single Responsibility: Product synchronization
*/
export class ProductSyncService {
constructor(
private batchApi: BatchApiService,
private validation: FeedValidationService
) {}
async syncProducts(storeId: string, catalogId: string): Promise(SyncResult) {
// Get products
const products = await this.getProductsToSync(storeId);
// Validate products
const validationResults = await this.validation.validateProducts(products);
const validProducts = validationResults.filter(r => r.valid).map(r => r.product);
// Batch sync to Facebook
const batches = this.createBatches(validProducts, 100); // 100 items per batch
const results = await this.batchApi.executeBatches(catalogId, batches);
return {
total: products.length,
synced: results.success,
failed: results.failed,
errors: results.errors,
};
}
private async getProductsToSync(storeId: string): Promise(Product[]) {
return prisma.product.findMany({
where: {
storeId,
status: 'ACTIVE',
deletedAt: null,
},
include: {
variants: true,
},
});
}
private createBatches(products: Product[], batchSize: number): ProductBatch[] {
const batches: ProductBatch[] = [];
for (let i = 0; i < products.length; i += batchSize) {
batches.push(products.slice(i, i + batchSize));
}
return batches;
}
}4. Batch API Service (Facebook API Wrapper)
// src/lib/integrations/facebook/batch-api.service.ts (~200 lines)
/**
* Handles Facebook Graph API batch operations
* Single Responsibility: Batch API communication
*/
export class BatchApiService {
constructor(private graphApi: FacebookGraphApiClient) {}
async executeBatches(
catalogId: string,
batches: ProductBatch[]
): Promise(BatchResult) {
const results: BatchResult = {
success: 0,
failed: 0,
errors: [],
};
for (const batch of batches) {
try {
const response = await this.executeBatch(catalogId, batch);
results.success += response.success;
results.failed += response.failed;
results.errors.push(...response.errors);
} catch (error) {
results.failed += batch.length;
results.errors.push({
batch: batches.indexOf(batch),
error: error.message,
});
}
}
return results;
}
private async executeBatch(
catalogId: string,
batch: ProductBatch
): Promise(BatchResponse) {
const batchRequest = batch.map(product => ({
method: 'POST',
relative_url: `/\$\{catalogId}/products`,
body: this.formatProductForApi(product),
}));
return this.graphApi.batch(batchRequest);
}
private formatProductForApi(product: Product): FacebookProductData {
// Format product for Facebook API
}
}5. Feed Validation Service
// src/lib/integrations/facebook/feed-validation.service.ts (~200 lines)
/**
* Validates product data against Facebook requirements
* Single Responsibility: Data validation
*/
export class FeedValidationService {
validateProducts(products: Product[]): ProductValidationResult[] {
return products.map(product => this.validateProduct(product));
}
private validateProduct(product: Product): ProductValidationResult {
const errors: string[] = [];
// Required fields
if (!product.name || product.name.length < 3) {
errors.push('Product name must be at least 3 characters');
}
if (!product.price || product.price <= 0) {
errors.push('Product price must be greater than 0');
}
// Image validation
if (!product.thumbnailUrl) {
errors.push('Product must have a valid image');
} else if (!this.isValidImageUrl(product.thumbnailUrl)) {
errors.push('Product image URL is invalid or not accessible');
}
// Description validation
if (product.description && product.description.length > 5000) {
errors.push('Product description exceeds 5000 character limit');
}
return {
product,
valid: errors.length === 0,
errors,
};
}
private isValidImageUrl(url: string): boolean {
// Image URL validation logic
}
}6. Orchestrator (Main Catalog Manager)
// src/lib/integrations/facebook/catalog-manager.ts (~250 lines)
/**
* Orchestrates Facebook catalog operations
* Coordinates between services for complex workflows
*/
export class FacebookCatalogManager {
constructor(
private operations: CatalogOperationsService,
private feed: ProductFeedService,
private sync: ProductSyncService,
private validation: FeedValidationService
) {}
async setupCatalog(storeId: string, name: string): Promise(Catalog) {
// Create catalog
const catalog = await this.operations.createCatalog(storeId, name);
// Generate initial feed
const feed = await this.feed.generateXmlFeed(storeId);
// Upload feed to Facebook
await this.uploadFeed(catalog.catalogId, feed);
return catalog;
}
async syncStoreProducts(storeId: string): Promise(SyncResult) {
// Get catalog
const catalog = await this.operations.getCatalog(storeId);
if (!catalog) {
throw new Error('Catalog not found');
}
// Sync products
return this.sync.syncProducts(storeId, catalog.catalogId);
}
// Other high-level orchestration methods
}Code Example Summary
Before: 1454-line monolithic service
After: 6 focused modules (200-350 lines each) + orchestrator
Net improvement:
- Maintainability: Each service has single, clear responsibility
- Testability: Can test each service in isolation
- Reusability: Services can be composed for different workflows
- Scalability: Easy to add new services (e.g., analytics, reporting)
Impact Assessment
- Effort: High - Complex refactoring with external API dependencies
- Risk: Medium-High - Critical integration, requires thorough testing
- Benefit: Very High - Major improvement in maintainability and extensibility
- Priority: High - 2nd largest service, frequently modified
Related Files
src/lib/integrations/facebook/catalog-manager.ts(1454 lines - to decompose)src/lib/integrations/facebook/oauth-service.ts(863 lines - related)src/lib/integrations/facebook/tracking.ts(821 lines - related)
Testing Strategy
- Unit Tests: Test each service independently with mocked dependencies
- Integration Tests: Test service composition with mocked Facebook API
- Contract Tests: Verify Facebook API integration with real sandbox
- Regression Tests: Ensure existing workflows still work
- Performance Tests: Verify batch syncing performance with large catalogs
Implementation Order
- ✅ Extract
FeedValidationService(pure logic, no dependencies) - ✅ Extract
BatchApiService(wrapper around Graph API) - ✅ Extract
ProductFeedService(depends on validation) - ✅ Extract
ProductSyncService(depends on batch API and validation) - ✅ Extract
CatalogOperationsService(simple CRUD) - ✅ Refactor
CatalogManagerto orchestrator pattern - ✅ Update all consumers to use new services
- ✅ Comprehensive integration testing
- ✅ Staging deployment with real Facebook sandbox
- ✅ Production rollout with monitoring
Migration Strategy
Gradual Migration (Recommended):
- Extract services while keeping original
CatalogManageras facade - Migrate consumers one by one to new services
- Remove old
CatalogManageronce all consumers migrated - Monitor production metrics throughout
Big Bang Migration (Alternative):
- Complete entire refactoring in feature branch
- Comprehensive testing in staging
- Deploy all changes at once
- Requires careful coordination and extended freeze
Notes
- This is the 2nd highest priority refactoring after ProductService ([Refactoring] Decompose 1662-line ProductService into smaller, focused service modules #262)
- Consider extracting shared Facebook API client first for better testing
- Coordinate with Facebook integration team before starting
- Document Facebook API version and requirements
- Add comprehensive logging for production debugging
AI generated by Daily Codebase Analyzer - Semantic Function Extraction & Refactoring
- expires on Mar 5, 2026, 2:07 PM UTC
Reactions are currently unavailable
Metadata
Metadata
Assignees
Type
Projects
Status
Backlog