8.8 KiB
8.8 KiB
Service Refactoring Guide: Modular Architecture
🎯 Problem Solved: Monolithic Services
❌ Before: Monolithic Structure
// CatalogService.ts (700+ lines)
class CatalogService {
// Internet logic mixed with SIM logic mixed with VPN logic
// Caching logic mixed with business logic
// Hard to test, maintain, and extend
}
// OrdersService.ts (400+ lines)
class OrdersService {
// Validation, building, item creation, provisioning all mixed
// Difficult to isolate specific functionality
}
Problems:
- ❌ Single Responsibility Principle violated
- ❌ Hard to test individual components
- ❌ Difficult to maintain and debug
- ❌ High coupling between unrelated logic
- ❌ Large classes with mixed concerns
✅ After: Modular Architecture
🏗️ Catalog Services Structure
📁 catalog/
├── 📁 services/
│ ├── base-catalog.service.ts # 🔧 Common Salesforce operations
│ ├── internet-catalog.service.ts # 🌐 Internet-specific logic
│ ├── sim-catalog.service.ts # 📱 SIM-specific logic
│ ├── vpn-catalog.service.ts # 🔒 VPN-specific logic
│ └── catalog-orchestrator.service.ts # 🎯 Coordinates everything + caching
├── catalog.controller.ts # 🚀 Routes to orchestrator
├── catalog.service.ts # ⚡ Legacy (backward compatible)
└── structured-catalog.service.ts # ⚡ Legacy (backward compatible)
🏗️ Orders Services Structure
📁 orders/
├── 📁 services/
│ ├── order-validator.service.ts # ✅ Validation logic only
│ ├── order-builder.service.ts # 🔨 Order header building
│ ├── order-item-builder.service.ts # 📦 OrderItem creation
│ └── order-orchestrator.service.ts # 🎯 Coordinates order flow
├── orders.controller.ts # 🚀 Routes to orchestrator
└── orders.service.ts # ⚡ Legacy (backward compatible)
🧩 Service Responsibilities
Catalog Services
| Service | Responsibility | Lines | Dependencies |
|---|---|---|---|
BaseCatalogService |
Common Salesforce queries, field mapping, error handling | ~120 | SF, Logger |
InternetCatalogService |
Internet plans, installations, add-ons logic | ~180 | BaseCatalog |
SimCatalogService |
SIM plans, activation fees, add-ons logic | ~140 | BaseCatalog |
VpnCatalogService |
VPN plans, activation fees logic | ~100 | BaseCatalog |
CatalogOrchestrator |
Coordinates services, handles caching | ~120 | All catalog services |
Order Services
| Service | Responsibility | Lines | Dependencies |
|---|---|---|---|
OrderValidator |
Business rule validation, user mapping | ~180 | Mappings, WHMCS, SF |
OrderBuilder |
Order header field building | ~90 | None |
OrderItemBuilder |
SKU parsing, OrderItem creation | ~160 | SF |
OrderOrchestrator |
Coordinates order creation flow | ~120 | All order services |
🎯 Usage Examples
Using New Catalog Architecture
// In your service/controller
constructor(
private catalogOrchestrator: CatalogOrchestrator,
private internetCatalog: InternetCatalogService
) {}
// Get complete structured catalog (cached)
const catalog = await this.catalogOrchestrator.getStructuredCatalog();
// Get only Internet data (cached)
const internetData = await this.catalogOrchestrator.getInternetCatalog();
// Get specific Internet plans directly
const plans = await this.internetCatalog.getPlans();
Using New Order Architecture
// In your controller
constructor(private orderOrchestrator: OrderOrchestrator) {}
// Create order using orchestrated flow
const result = await this.orderOrchestrator.createOrder(userId, orderData);
// Get user orders
const orders = await this.orderOrchestrator.getOrdersForUser(userId);
Testing Individual Components
// Test only Internet catalog logic
describe('InternetCatalogService', () => {
let service: InternetCatalogService;
let mockBaseCatalog: jest.Mocked<BaseCatalogService>;
beforeEach(() => {
const module = Test.createTestingModule({
providers: [
InternetCatalogService,
{ provide: BaseCatalogService, useValue: mockBaseCatalog }
]
});
service = module.get(InternetCatalogService);
});
it('should parse internet plans correctly', async () => {
// Test only internet-specific logic, isolated
});
});
🔄 Backward Compatibility
Clean API Endpoints
// ✅ Clean architecture endpoints
GET /api/catalog // Uses CatalogOrchestrator (legacy format)
GET /api/catalog/structured // Uses CatalogOrchestrator (recommended)
GET /api/catalog/internet/addons // Uses CatalogOrchestrator
// ✅ Clean order endpoints
POST /api/orders // Uses OrderOrchestrator
GET /api/orders/user // Uses OrderOrchestrator
GET /api/orders/:id // Uses OrderOrchestrator
Clean Architecture Implementation
// ✅ Final clean implementation - no legacy code
@Module({
providers: [
BaseCatalogService, // Common operations
CatalogOrchestrator, // Main coordinator
InternetCatalogService, // Focused responsibility
SimCatalogService, // Focused responsibility
VpnCatalogService, // Focused responsibility
]
})
// ✅ Controller uses only orchestrator
@Controller()
class CatalogController {
constructor(
private catalogOrchestrator: CatalogOrchestrator // Single clean interface
) {}
@Get() getCatalog() { return this.catalogOrchestrator.getLegacyCatalog(); }
@Get('structured') getStructured() { return this.catalogOrchestrator.getStructuredCatalog(); }
}
📊 Benefits Achieved
| Aspect | Before | After |
|---|---|---|
| Service Size | 700+ lines | ~120 lines per service |
| Testability | Hard (monolithic) | Easy (focused) |
| Maintainability | Difficult (mixed concerns) | Simple (single responsibility) |
| Performance | No specialized caching | Service-specific caching |
| Extensibility | Break existing code | Add new services cleanly |
| Debugging | Search through 700+ lines | Check specific 120-line service |
| Code Coupling | High (everything connected) | Low (focused dependencies) |
🚀 Extension Examples
Adding New Product Type
// 1. Create focused service
@Injectable()
export class IotCatalogService extends BaseCatalogService {
async getDevices(): Promise<IotDevice[]> {
const soql = this.buildCatalogServiceQuery('IoT');
// IoT-specific logic only, ~100 lines
}
}
// 2. Add to orchestrator
@Injectable()
export class CatalogOrchestrator {
constructor(
// ... existing services
private iotCatalog: IotCatalogService // Add new service
) {}
async getStructuredCatalog() {
const [internet, sim, vpn, iot] = await Promise.all([
this.internetCatalog.getCatalogData(),
this.simCatalog.getCatalogData(),
this.vpnCatalog.getCatalogData(),
this.iotCatalog.getCatalogData() // Add new data
]);
return { internet, sim, vpn, iot };
}
}
// 3. Zero changes to existing services! 🎉
Adding New Order Validation
// Add to OrderValidator - single responsibility
@Injectable()
export class OrderValidator {
validateCreditCheck(orderData: any): void {
// Add new validation logic without touching other services
}
}
// OrderOrchestrator automatically uses new validation
// No changes needed elsewhere! 🎉
🔧 Development Benefits
Focused Development
// Working on Internet features? Only touch InternetCatalogService
// Working on order validation? Only touch OrderValidator
// Working on caching? Only touch CatalogOrchestrator
Easy Testing
// Test only what you're working on
describe('InternetCatalogService', () => {
// Mock only BaseCatalogService
// Test only Internet logic
// ~50 test lines vs 200+ before
});
Clear Code Ownership
// 🌐 Internet features → InternetCatalogService
// 📱 SIM features → SimCatalogService
// 🔒 VPN features → VpnCatalogService
// ✅ Order validation → OrderValidator
// 📦 Order items → OrderItemBuilder
🎯 Implementation Complete
✅ Clean Architecture Achieved:
- Modular services with single responsibilities
- Shared types eliminating duplication
- Reusable utilities for business logic
- Clean API endpoints using orchestrators
- Easy testing with focused components
The clean architecture provides excellent maintainability, testability, and extensibility! 🎉