5.2 KiB
Validation Cleanup Complete ✅
Summary
Successfully removed redundant validation layer and established proper validation architecture using Zod schemas.
Changes Made
1. Added URL Validation to Domain ✅
File: packages/domain/common/validation.ts
Added URL validation utilities:
urlSchema- Zod schema for URL validationvalidateUrlOrThrow()- Throwing variantvalidateUrl()- Non-throwing variant returning validation resultisValidUrl()- Boolean check
2. Refactored Invoice Services ✅
Files Changed:
apps/bff/src/modules/invoices/services/invoice-retrieval.service.tsapps/bff/src/modules/invoices/services/invoices-orchestrator.service.tsapps/bff/src/modules/invoices/invoices.controller.ts
Changes:
- Removed dependency on
InvoiceValidatorService - Use Zod schemas directly for validation
- Controller uses
ZodValidationPipewithinvoiceListQuerySchema - Services validate using domain schemas:
invoiceSchema,invoiceListQuerySchema - Removed redundant manual validation checks
3. Deleted Redundant Files ✅
Deleted:
- ❌
apps/bff/src/modules/invoices/validators/invoice-validator.service.ts - ❌
apps/bff/src/modules/invoices/types/invoice-service.types.ts
Created:
- ✅
apps/bff/src/modules/invoices/types/invoice-monitoring.types.ts(for infrastructure types)
Updated:
apps/bff/src/modules/invoices/invoices.module.ts- Removed validator from providersapps/bff/src/modules/invoices/index.ts- Updated exports
4. Preserved Infrastructure Types ✅
Created invoice-monitoring.types.ts for BFF-specific infrastructure concerns:
InvoiceServiceStats- Monitoring/metricsInvoiceHealthStatus- Health check results
Validation Architecture (Confirmed)
✅ Schema Validation (Domain - Zod)
Location: packages/domain/*/schema.ts
Purpose: Format, type, range validation
Examples:
export const invoiceListQuerySchema = z.object({
page: z.coerce.number().int().positive().optional(),
limit: z.coerce.number().int().positive().max(100).optional(),
status: invoiceListStatusSchema.optional(),
});
✅ Business Validation (Domain - Pure Functions)
Location: packages/domain/*/validation.ts
Purpose: Cross-field rules, business constraints
Examples:
// packages/domain/mappings/validation.ts
export function validateNoConflicts(
request: CreateMappingRequest,
existingMappings: UserIdMapping[]
): MappingValidationResult {
// Business rule: no duplicate userId or whmcsClientId
}
✅ Infrastructure Validation (BFF - Services)
Location: apps/bff/src/modules/*/services/*.service.ts
Purpose: Data-dependent validation (DB/API calls)
Examples:
// Invoice retrieval service
private async getUserMapping(userId: string): Promise<UserMappingInfo> {
validateUuidV4OrThrow(userId); // Domain validation
const mapping = await this.mappingsService.findByUserId(userId); // DB call
if (!mapping?.whmcsClientId) {
throw new NotFoundException("WHMCS client mapping not found");
}
return mapping;
}
Key Principle Established
❌ DON'T: Create validator services for field validation
class XyzValidatorService {
validateField(value) {
if (!value || value < 1) throw new Error(...); // Redundant with schema
}
}
✅ DO: Use Zod schemas + validation pipe
const xyzRequestSchema = z.object({
field: z.number().int().positive(), // Schema handles validation
});
@Post()
async create(
@Body(new ZodValidationPipe(xyzRequestSchema)) body: XyzRequest
) {
// Already validated!
}
Validation Coverage by Module
| Module | Schema Validation | Business Validation | Infrastructure | Status |
|---|---|---|---|---|
| Mappings | ✅ domain/mappings/schema.ts |
✅ domain/mappings/validation.ts |
✅ BFF service | ✅ Complete |
| Invoices | ✅ domain/billing/schema.ts |
N/A (no business rules) | ✅ BFF service | ✅ Complete |
| Orders | ✅ domain/orders/schema.ts |
✅ domain/orders/validation.ts |
✅ BFF service | ✅ Complete |
| SIM | ✅ domain/sim/schema.ts |
✅ domain/sim/validation.ts |
✅ BFF service | ✅ Complete |
| Common | ✅ domain/common/validation.ts |
N/A | N/A | ✅ Complete |
Verification
✅ Domain package compiles without errors
✅ BFF compiles without errors
✅ No linter errors
✅ All validation handled by schemas at entry points
✅ Infrastructure concerns properly separated
Benefits Achieved
- Single Source of Truth: Zod schemas in domain define all field validation
- No Duplication: Removed redundant validation logic
- Type Safety: Schemas generate TypeScript types
- Consistency: Same validation rules everywhere
- Maintainability: Less code, clearer responsibilities
- Proper Separation: Schema → Business → Infrastructure layers clearly defined
Pattern for Future Development
When adding new validation:
- Field validation? → Add to domain schema (Zod)
- Business rule? → Add pure function to
domain/*/validation.ts - Needs DB/API? → Keep in BFF service layer
Never create a validator service just to duplicate what schemas already do!