Assist_Design/COMPREHENSIVE_CODE_REVIEW_2025.md

856 lines
25 KiB
Markdown
Raw Normal View History

# Comprehensive Code Review Report
**Customer Portal - Architecture & Code Quality Analysis**
**Date**: October 28, 2025
**Reviewer**: AI Code Reviewer
**Scope**: Full codebase review for separation of concerns, domain design, and code quality
---
## Executive Summary
The customer portal codebase demonstrates **good overall architecture** with a clear BFF (Backend for Frontend) pattern and a dedicated domain package. However, there are **several critical architectural violations** and **technical debt** that need to be addressed to achieve true separation of concerns and maintainable code.
### Key Metrics
- **Architecture Score**: 7/10 (Good foundation, needs refinement)
- **Separation of Concerns**: 6/10 (Domain exists but inconsistently used)
- **Code Quality**: 7.5/10 (Generally clean with some issues)
- **File Structure**: 8/10 (Well-organized, some redundancy)
---
## ✅ Strengths (What's Done Well)
### 1. **Domain Package Architecture**
- **Well-structured domain package** at `packages/domain/` with clear separation by business domain (catalog, orders, sim, billing, etc.)
- **Schema-first approach** using Zod for runtime validation with TypeScript type inference
- **Provider adapters** properly isolated (Salesforce, WHMCS, Freebit) with clean mapping logic
- **Centralized validation** schemas in domain package
### 2. **BFF Layer Implementation**
- **Clean controller layer** with proper use of NestJS decorators and guards
- **Service-oriented architecture** with clear separation (orchestrators, validators, builders)
- **Proper dependency injection** patterns throughout
- **Structured logging** using nestjs-pino
- **Rate limiting** on sensitive endpoints (orders, catalog)
### 3. **Frontend Architecture**
- **Feature-based organization** in `apps/portal/src/features/`
- **Component hierarchy** (atoms, molecules, organisms) following atomic design
- **Zustand state management** for complex state (auth, catalog configuration)
- **React Query integration** for data fetching
- **Proper hooks abstraction** for business logic encapsulation
### 4. **Type Safety**
- **Strong TypeScript usage** throughout the codebase
- **Type inference from Zod schemas** (schema-first approach)
- **Minimal use of `any` types**
---
## ❌ Critical Issues (Must Fix)
### 1. **Business Logic in Frontend** ⚠️ CRITICAL
**Location**: `apps/portal/src/features/catalog/utils/catalog.utils.ts`
**Issue**: Frontend contains business logic for filtering, sorting, and calculating product values.
```typescript
// ❌ BAD: Frontend has business logic
export function filterProducts(
products: CatalogProduct[],
filters: { category?: string; priceMin?: number; priceMax?: number; search?: string; }
): CatalogProduct[] {
return products.filter(product => {
if (typeof filters.priceMin === "number") {
const price = product.monthlyPrice ?? product.oneTimePrice ?? 0;
if (price < filters.priceMin) return false;
}
// ... more filtering logic
});
}
export function sortProducts(products: CatalogProduct[], sortBy: "name" | "price" = "name"): CatalogProduct[] {
// ... sorting logic
}
```
**Why This Is Wrong**:
- **Separation of concerns violated**: Frontend should only display data, not transform it
- **Inconsistent business rules**: Client-side logic can diverge from server-side
- **Security risk**: Business rules can be bypassed by modifying frontend code
- **Performance**: Heavy operations on client-side
**Solution**:
```typescript
// ✅ GOOD: Move to BFF
// apps/bff/src/modules/catalog/services/catalog.service.ts
async getFilteredProducts(
filters: CatalogFilter
): Promise<CatalogProduct[]> {
// Apply filtering server-side
// Return pre-filtered, pre-calculated results
}
```
**Impact**: HIGH - Violates fundamental architectural principles
---
### 2. **Business Validation Logic Split Between Layers** ⚠️ HIGH
**Location**:
- `apps/portal/src/features/checkout/hooks/useCheckout.ts` (lines 51-59, 211-213)
- `apps/bff/src/modules/orders/services/order-validator.service.ts`
**Issue**: Business validation duplicated in frontend and backend.
```typescript
// ❌ BAD: Business validation in frontend hook
const hasActiveInternetSubscription = useMemo(() => {
if (!Array.isArray(activeSubs)) return false;
return activeSubs.some(
subscription =>
String(subscription.groupName || subscription.productName || "")
.toLowerCase()
.includes("internet") &&
String(subscription.status || "").toLowerCase() === "active"
);
}, [activeSubs]);
// Client-side guard: prevent Internet orders if an Internet subscription already exists
if (orderType === ORDER_TYPE.INTERNET && hasActiveInternetSubscription && !isDevEnvironment) {
throw new Error(ACTIVE_INTERNET_WARNING_MESSAGE);
}
```
**Why This Is Wrong**:
- **Duplicate validation**: Same rule exists in BFF (`validateInternetDuplication`)
- **Inconsistency risk**: Frontend and backend rules can diverge
- **Security**: Client-side checks can be bypassed
- **Development mode override**: Business rules shouldn't be environment-dependent
**Solution**:
```typescript
// ✅ GOOD: Single source of truth in BFF
// Frontend only displays warnings/hints
// BFF enforces business rules absolutely
// Remove all business rule enforcement from frontend
```
**Impact**: HIGH - Security and consistency risk
---
### 3. **Frontend Stores Contain Business Logic** ⚠️ MEDIUM-HIGH
**Location**: `apps/portal/src/features/catalog/services/catalog.store.ts`
**Issue**: Store contains complex business logic for building checkout parameters, validating forms, and transforming data.
```typescript
// ❌ BAD: Complex transformation logic in frontend store (lines 196-304)
buildSimCheckoutParams: () => {
const { sim } = get();
if (!sim.planSku) return null;
const rawSelections: Record<string, string> = {
plan: sim.planSku,
simType: sim.simType,
activationType: sim.activationType,
};
// 50+ lines of complex transformation logic
// Building MNP data, normalizing selections, etc.
}
```
**Why This Is Wrong**:
- **Business logic in presentation layer**: Stores should only manage state, not transform it
- **Complex transformations**: Should be in domain package
- **Tight coupling**: Frontend tightly coupled to domain implementation details
**Solution**:
```typescript
// ✅ GOOD: Move to domain package
// packages/domain/orders/helpers.ts
export function buildSimCheckoutSelections(config: SimConfigState): OrderSelections {
// Transformation logic here
}
// Frontend store only calls helper
buildSimCheckoutParams: () => {
const { sim } = get();
return buildSimCheckoutSelections(sim);
}
```
**Impact**: MEDIUM-HIGH - Maintainability and testability issues
---
### 4. **Validation Schema Duplication** ⚠️ MEDIUM
**Location**: Multiple files
**Issue**: Validation patterns duplicated across layers instead of using domain schemas.
```typescript
// ❌ BAD: Local validation schema in BFF
// apps/bff/src/integrations/salesforce/utils/soql.util.ts
const nonEmptyStringSchema = z.string().min(1, "Value cannot be empty").trim();
const soqlFieldNameSchema = z.string().trim().regex(/^[A-Za-z0-9_.]+$/, "Invalid SOQL field name");
// ❌ BAD: Controller-specific response schema
// apps/bff/src/modules/orders/orders.controller.ts
private readonly createOrderResponseSchema = apiSuccessResponseSchema(
z.object({
sfOrderId: z.string(),
status: z.string(),
message: z.string(),
})
);
```
**Why This Is Wrong**:
- **Duplication**: Same validation logic defined in multiple places
- **Inconsistency**: Validation rules can diverge
- **Not DRY**: Violates Don't Repeat Yourself principle
**Solution**:
```typescript
// ✅ GOOD: Define once in domain
// packages/domain/common/validation.ts
export const nonEmptyStringSchema = z.string().min(1).trim();
export const salesforceIdSchema = z.string().length(18);
// packages/domain/orders/schema.ts
export const orderCreateResponseSchema = z.object({
sfOrderId: z.string(),
status: z.string(),
message: z.string(),
});
```
**Impact**: MEDIUM - Code duplication and maintenance burden
---
### 5. **Infrastructure Types in Business Layer** ⚠️ MEDIUM
**Location**: Various service files
**Issue**: Infrastructure-specific types defined in business services instead of proper boundaries.
```typescript
// ❌ BAD: Infrastructure type in service
// apps/bff/src/modules/invoices/services/invoice-retrieval.service.ts
interface UserMappingInfo {
userId: string;
whmcsClientId: number;
}
// ❌ BAD: BFF-specific interface that could be domain type
// apps/bff/src/modules/orders/services/order-fulfillment-validator.service.ts
export interface OrderFulfillmentValidationResult {
sfOrder: SalesforceOrderRecord;
clientId: number;
isAlreadyProvisioned: boolean;
whmcsOrderId?: string;
}
```
**Why This Is Wrong**:
- **Blurred boundaries**: Infrastructure concerns mixed with business logic
- **Tight coupling**: Services coupled to specific infrastructure details
- **Hard to test**: Infrastructure types make unit testing harder
**Solution**:
```typescript
// ✅ GOOD: Define in appropriate domain or create proper abstraction
// packages/domain/orders/types.ts
export interface OrderFulfillmentValidation {
orderId: string;
customerAccountId: string;
isProvisioned: boolean;
externalOrderId?: string;
}
```
**Impact**: MEDIUM - Architectural clarity and testability
---
### 6. **Complex Business Logic in Frontend Hooks** ⚠️ MEDIUM
**Location**: `apps/portal/src/features/checkout/hooks/useCheckout.ts` (lines 90-123)
**Issue**: Complex parsing and configuration building in frontend hooks.
```typescript
// ❌ BAD: Complex business logic in hook (lines 90-123)
const { selections, configurations } = useMemo(() => {
const rawSelections: Record<string, string> = {};
params.forEach((value, key) => {
if (key !== "type") {
rawSelections[key] = value;
}
});
try {
const normalizedSelections = normalizeOrderSelections(rawSelections);
let configuration: OrderConfigurations | undefined;
try {
configuration = buildOrderConfigurations(normalizedSelections);
} catch (error) {
logger.warn("Failed to derive order configurations from selections", {
error: error instanceof Error ? error.message : String(error),
});
}
return {
selections: normalizedSelections,
configurations: configuration,
};
} catch (error) {
// ... error handling
}
}, [params]);
```
**Why This Is Wrong**:
- **Complex logic in UI layer**: Hooks should be simple state managers
- **Silent error swallowing**: Try-catch with fallbacks hide issues
- **Hard to test**: Logic tied to React hooks ecosystem
**Solution**:
```typescript
// ✅ GOOD: Move to service layer
// apps/portal/src/features/checkout/services/checkout-params.service.ts
export class CheckoutParamsService {
static parseSelections(params: URLSearchParams): OrderSelections {
// Parsing logic
}
static buildConfigurations(selections: OrderSelections): OrderConfigurations {
// Building logic
}
}
// Hook becomes simple
const selections = CheckoutParamsService.parseSelections(params);
const configurations = CheckoutParamsService.buildConfigurations(selections);
```
**Impact**: MEDIUM - Maintainability and testability
---
## 🔴 Architectural Issues
### 1. **Inconsistent Domain Package Usage**
**Issue**: Not all business types are in the domain package. Some are scattered across BFF and Portal.
**Examples**:
- `CheckoutItem` used in Portal but not defined in domain
- `PricingTier` defined in UI components
- `CatalogFilter` has TODO comment indicating missing domain type
**Solution**:
- Move ALL business types to domain package
- Frontend and BFF should ONLY import from domain
- Domain package should be the single source of truth
---
### 2. **Lack of Domain Services**
**Issue**: Business logic spread across BFF services without clear domain service layer.
**Observation**:
```
packages/domain/
catalog/
schema.ts ✅
contract.ts ✅
providers/ ✅
index.ts ✅
services/ ❌ MISSING
validation.ts ❌ MISSING
```
**Solution**:
```typescript
// packages/domain/orders/services/order-validation.service.ts
export class OrderValidationService {
static validateOrderFulfillment(order: Order): ValidationResult {
// Pure business logic here
}
}
```
**Impact**: Architectural clarity and testability
---
### 3. **Mixed Responsibility in Controllers**
**Issue**: Controllers doing more than HTTP request/response handling.
**Example**:
```typescript
// ❌ BAD: Controller handles parsing logic
@Get("internet/plans")
async getInternetPlans(@Request() req: RequestWithUser) {
const userId = req.user?.id;
if (!userId) {
const catalog = await this.internetCatalog.getCatalogData();
return parseInternetCatalog(catalog); // Parsing in controller
}
// ...
}
```
**Solution**:
```typescript
// ✅ GOOD: Service handles all logic
@Get("internet/plans")
async getInternetPlans(@Request() req: RequestWithUser) {
return this.catalogService.getInternetPlansForUser(req.user?.id);
}
```
---
### 4. **Tight Coupling to External Services**
**Issue**: Business logic services directly coupled to integration services (WHMCS, Salesforce).
**Example**:
```typescript
// apps/bff/src/modules/orders/services/order-validator.service.ts
constructor(
private readonly whmcs: WhmcsConnectionOrchestratorService,
private readonly mappings: MappingsService,
// ... directly depends on infrastructure
)
```
**Solution**: Introduce repository pattern or ports/adapters to decouple business logic from infrastructure.
---
## 🟡 Code Quality Issues
### 1. **Inconsistent Error Handling**
**Issue**: Mix of generic `throw new Error()` and typed exceptions.
**Found**:
- Some services use typed exceptions (`SimActivationException`, `OrderValidationException`)
- Many still use generic `throw new Error()`
- Inconsistent error message formatting
**Solution**: Standardize on typed exception hierarchy across all services.
---
### 2. **Missing Input Validation in Some Services**
**Issue**: Some service methods don't validate inputs before processing.
**Example**:
```typescript
// apps/bff/src/modules/orders/services/sim-fulfillment.service.ts (lines 28-34)
const simType = this.readEnum(configurations.simType, ["eSIM", "Physical SIM"]) ?? "eSIM";
const eid = this.readString(configurations.eid);
// ... no validation that configurations is not null/undefined
```
**Solution**: Add input validation at service boundaries.
---
### 3. **Complex Service Methods**
**Issue**: Some service methods are very long (100+ lines) with complex logic.
**Examples**:
- `OrderFulfillmentOrchestrator.fulfillOrder()` (likely 200+ lines)
- `SimFulfillmentService.fulfillSimOrder()` (100 lines with nested conditionals)
**Solution**: Break down into smaller, focused methods with single responsibility.
---
### 4. **Inadequate Logging Context**
**Issue**: Some log statements lack sufficient context for debugging.
**Example**:
```typescript
this.logger.log("SIM fulfillment completed successfully", {
orderId: orderDetails.id,
account: phoneNumber,
planSku,
});
// Missing: simType, activationType, whether MNP was used, etc.
```
**Solution**: Add comprehensive context to all log statements.
---
### 5. **Magic Strings and Numbers**
**Issue**: Various magic strings and numbers scattered throughout code.
**Examples**:
```typescript
// apps/portal/src/features/checkout/hooks/useCheckout.ts
const ACTIVE_INTERNET_WARNING_MESSAGE = "You already have an active..."; // Should be in constants
const isDevEnvironment = process.env.NODE_ENV === "development"; // Direct env check
// apps/bff/src/modules/orders/controllers/checkout.service.ts (line 55)
if (simType === "eSIM" && (!eid || eid.length < 15)) { // Magic number 15
```
**Solution**: Extract to constants or configuration.
---
## 📁 File Structure Issues
### 1. **Inconsistent Module Organization**
**Issue**: Some modules have deep nesting, others are flat.
**Example**:
```
modules/auth/
application/
infra/
workflows/
workflows/ ← Redundant nesting
presentation/
services/
vs.
modules/catalog/
services/
catalog.controller.ts
catalog.module.ts
```
**Solution**: Standardize on consistent depth and organization.
---
### 2. **Mixed Concerns in Directories**
**Issue**: Some directories contain both business logic and infrastructure code.
**Example**:
```
modules/orders/
controllers/ ← HTTP layer
services/ ← Mix of business logic and orchestration
queue/ ← Infrastructure
types/ ← Shared types
```
**Solution**: Separate by architectural layer (domain, application, infrastructure, presentation).
---
### 3. **Redundant Type Directories**
**Issue**: Types scattered across multiple locations.
**Found**:
- `apps/bff/src/types/`
- `apps/bff/src/modules/*/types/`
- `packages/domain/*/`
**Solution**: All business types in domain, infrastructure types in their respective modules.
---
## 🔧 Technical Debt
### 1. **Build Artifacts in Source** ⚠️ RESOLVED
**Status**: Fixed (per CODEBASE_ANALYSIS.md)
- Previously: 224 generated files in source tree
- Now: Build outputs isolated to `dist/`
---
### 2. **Console.log Statements**
**Issue**: 3 console.log instances found in portal (should use logger)
**Found**: `apps/portal/src/lib/logger.ts:2`
**Solution**: Replace with proper logging infrastructure.
---
### 3. **Incomplete Type Safety**
**Issue**: Some areas still have loose typing.
**Examples**:
- `unknown` and `any` usage in error handling
- Generic `Record<string, unknown>` for complex objects
- Missing discriminated unions for variant types
---
### 4. **Missing Tests**
**Observation**: No test files found in search (likely in separate test directories).
**Recommendation**: Ensure comprehensive test coverage, especially for:
- Domain validation logic
- Business rules
- Order fulfillment workflows
- Integration adapters
---
## 📊 Detailed Findings by Domain
### Authentication & Authorization
**Strengths**:
- ✅ Clean store implementation with Zustand
- ✅ Proper JWT handling with httpOnly cookies
- ✅ Session management with refresh tokens
**Issues**:
- ⚠️ Complex workflow logic in frontend store (auth.store.ts)
- ⚠️ Nested directory structure (`infra/workflows/workflows/`)
---
### Catalog & Product Management
**Strengths**:
- ✅ Domain types well-defined (CatalogProduct, InternetPlan, SimProduct, etc.)
- ✅ Provider adapters cleanly separated
- ✅ Caching implemented (CatalogCacheService)
**Issues**:
- ❌ Filtering and sorting logic in frontend utils
- ❌ Business logic in catalog store
- ⚠️ Parsing logic in controllers instead of services
---
### Orders & Checkout
**Strengths**:
- ✅ Clear orchestration pattern (OrderOrchestrator)
- ✅ Separate validation service (OrderValidator)
- ✅ Domain schemas for order types
- ✅ Business validation in domain package
**Issues**:
- ❌ Frontend validation duplicating backend validation
- ❌ Complex checkout hook with business logic
- ⚠️ Long service methods that could be broken down
---
### Subscriptions & Billing
**Strengths**:
- ✅ Clean data fetching with React Query
- ✅ Proper type definitions
**Issues**:
- ⚠️ Minimal business logic encapsulation
- ⚠️ Direct WHMCS coupling in some areas
---
## 🎯 Recommendations
### Immediate Actions (High Priority)
#### 1. **Remove Business Logic from Frontend** ⚠️ CRITICAL
- **Timeline**: 1-2 weeks
- **Impact**: HIGH
- **Action**:
- Move `filterProducts`, `sortProducts` from `catalog.utils.ts` to BFF
- Remove client-side business validation from `useCheckout` hook
- Simplify frontend stores to only manage UI state
#### 2. **Consolidate Validation Schemas** ⚠️ HIGH
- **Timeline**: 1 week
- **Impact**: MEDIUM
- **Action**:
- Move all validation schemas to domain package
- Remove duplicate schemas from BFF and Portal
- Update imports across codebase
#### 3. **Extract Business Types to Domain** ⚠️ HIGH
- **Timeline**: 1 week
- **Impact**: MEDIUM
- **Action**:
- Move `OrderFulfillmentValidationResult`, `PricingTier`, `CheckoutItem` to domain
- Define missing types (`CatalogFilter`, etc.)
- Update all imports
---
### Medium Priority Actions
#### 4. **Introduce Domain Services Layer**
- **Timeline**: 2-3 weeks
- **Impact**: MEDIUM
- **Action**:
- Create `packages/domain/*/services/` directories
- Move pure business logic from BFF services to domain services
- BFF services become thin orchestration layer
#### 5. **Standardize Error Handling**
- **Timeline**: 1-2 weeks
- **Impact**: MEDIUM
- **Action**:
- Complete typed exception hierarchy
- Replace all generic `throw new Error()` with typed exceptions
- Add proper error context
#### 6. **Refactor Long Service Methods**
- **Timeline**: 2 weeks
- **Impact**: LOW-MEDIUM
- **Action**:
- Break down methods >50 lines into smaller functions
- Apply Single Responsibility Principle
- Improve testability
---
### Long-term Improvements
#### 7. **Implement Repository Pattern**
- **Timeline**: 3-4 weeks
- **Impact**: MEDIUM
- **Action**:
- Introduce repositories to decouple from Salesforce/WHMCS
- Define ports/adapters architecture
- Improve testability with mocking
#### 8. **Comprehensive Testing Strategy**
- **Timeline**: Ongoing
- **Impact**: HIGH
- **Action**:
- Unit tests for all domain services
- Integration tests for BFF services
- E2E tests for critical user flows
#### 9. **Documentation Improvements**
- **Timeline**: 1-2 weeks
- **Impact**: LOW
- **Action**:
- Architecture Decision Records (ADRs)
- API documentation
- Code comments for complex business logic
---
## 📋 Implementation Plan
### Phase 1: Critical Fixes (Weeks 1-3)
1. **Week 1**: Remove business logic from frontend utilities
2. **Week 2**: Consolidate validation schemas in domain
3. **Week 3**: Extract business types to domain package
### Phase 2: Architectural Improvements (Weeks 4-6)
4. **Week 4-5**: Introduce domain services layer
5. **Week 6**: Standardize error handling across codebase
### Phase 3: Quality Improvements (Weeks 7-9)
6. **Week 7**: Refactor long service methods
7. **Week 8-9**: Implement repository pattern
### Phase 4: Long-term Excellence (Weeks 10+)
8. **Week 10+**: Comprehensive testing strategy
9. **Ongoing**: Documentation improvements
---
## 📈 Success Metrics
### Code Quality Metrics
- **Type Safety**: 95%+ of code strongly typed
- **Business Logic in Domain**: 90%+ of business logic in domain package
- **Validation Consistency**: Single validation schema per business rule
- **Test Coverage**: 80%+ for domain and business logic
### Architectural Metrics
- **Separation of Concerns**: Clear boundaries between layers
- **Coupling**: Low coupling between modules
- **Cohesion**: High cohesion within modules
- **Import Clarity**: All business types imported from domain
---
## 🎓 Learning & Best Practices
### What to Continue
1. **Schema-first approach**: Continue using Zod schemas as source of truth
2. **Provider adapters**: Continue clean separation of external service integration
3. **Feature-based organization**: Frontend feature structure is good
4. **Dependency injection**: Continue using DI in BFF
### What to Stop
1. **Business logic in frontend**: Stop putting business rules in UI components/hooks
2. **Duplicate validation**: Stop defining validation schemas in multiple places
3. **Direct external service coupling**: Stop directly coupling business logic to infrastructure
4. **Magic values**: Stop using magic strings/numbers without constants
### What to Start
1. **Domain services**: Start creating pure business logic services in domain
2. **Repository pattern**: Start abstracting data access
3. **Typed exceptions**: Start using typed exceptions everywhere
4. **Comprehensive logging**: Start adding rich context to all logs
---
## 🔍 Conclusion
The customer portal has a **solid architectural foundation** with good separation between BFF and Portal, and a well-structured domain package. However, there are **critical violations of separation of concerns**, particularly business logic leaking into the frontend.
### Summary Assessment
- **Architecture**: 7/10 - Good foundation but needs refinement
- **Domain Design**: 6/10 - Well-structured but underutilized
- **Code Quality**: 7.5/10 - Generally clean with some technical debt
- **Maintainability**: 6.5/10 - Will improve significantly after addressing issues
### Priority Focus Areas
1. **Remove all business logic from frontend** (Critical)
2. **Consolidate validation in domain** (High)
3. **Complete domain type migration** (High)
4. **Introduce domain services layer** (Medium)
By addressing these issues systematically, the codebase will achieve:
- ✅ True separation of concerns
- ✅ Single source of truth for business logic
- ✅ Improved testability
- ✅ Better maintainability
- ✅ Cleaner architecture
---
**Next Steps**: Prioritize Phase 1 actions and create detailed task tickets for implementation.
**Review Schedule**: Re-assess after Phase 1 completion (3 weeks) to measure progress.