- Updated export statements in user and mapping mappers for consistency. - Enhanced FreebitAuthService to explicitly define response types for better type inference. - Refactored various services to improve error handling and response structure. - Cleaned up unused code and comments across multiple files to enhance readability. - Improved type annotations in invoice and subscription services for better validation and consistency.
11 KiB
Domain Violations Report
Customer Portal - BFF and Portal Domain Violations Analysis
Executive Summary
This report identifies cases where the BFF (Backend for Frontend) and Portal applications are not following the domain package as the single source of truth for types, validation, and business logic. The analysis reveals several categories of violations that need to be addressed to maintain clean architecture principles.
Architecture Overview
The customer portal follows a clean architecture pattern with:
- Domain Package (
packages/domain/): Single source of truth for types, schemas, and business logic - BFF (
apps/bff/): NestJS backend that should consume domain types - Portal (
apps/portal/): Next.js frontend that should consume domain types
Key Findings
✅ Good Practices Found
- Domain Package Structure: Well-organized with clear separation of concerns
- Type Imports: Most services correctly import domain types
- Validation Patterns: Many components properly extend domain schemas
- Provider Adapters: Clean separation between domain contracts and provider implementations
❌ Domain Violations Identified
1. BFF Layer Violations
1.1 Duplicate Validation Schemas
Location: apps/bff/src/modules/orders/services/order-fulfillment-validator.service.ts
// ❌ VIOLATION: Duplicate validation schema
const salesforceAccountIdSchema = z.string().min(1, "Salesforce AccountId is required");
Issue: Creating local Zod schemas instead of using domain validation Impact: Duplication of validation logic, potential inconsistencies
1.2 Infrastructure-Specific Types
Location: apps/bff/src/modules/orders/services/order-fulfillment-validator.service.ts
// ❌ VIOLATION: BFF-specific interface that could be domain type
export interface OrderFulfillmentValidationResult {
sfOrder: SalesforceOrderRecord;
clientId: number;
isAlreadyProvisioned: boolean;
whmcsOrderId?: string;
}
Issue: Business logic types defined in BFF instead of domain Impact: Business logic scattered across layers
1.3 Local Type Definitions
Location: apps/bff/src/modules/invoices/services/invoice-retrieval.service.ts
// ❌ VIOLATION: Local interface instead of domain type
interface UserMappingInfo {
userId: string;
whmcsClientId: number;
}
Issue: Infrastructure types that could be standardized in domain Impact: Inconsistent type definitions across services
1.4 Response Schema Definitions
Location: apps/bff/src/modules/orders/orders.controller.ts
// ❌ VIOLATION: Controller-specific response schema
private readonly createOrderResponseSchema = apiSuccessResponseSchema(
z.object({
sfOrderId: z.string(),
status: z.string(),
message: z.string(),
})
);
Issue: Response schemas defined in controllers instead of domain Impact: API contract not centralized
1.5 SOQL Utility Schemas
Location: apps/bff/src/integrations/salesforce/utils/soql.util.ts
// ❌ VIOLATION: Utility-specific schemas
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");
Issue: Common validation patterns not centralized Impact: Repeated validation logic
2. Portal Layer Violations
2.1 Component-Specific Types
Location: apps/portal/src/features/catalog/components/base/PricingDisplay.tsx
// ❌ VIOLATION: UI-specific types that could be domain types
export interface PricingTier {
name: string;
price: number;
billingCycle: "Monthly" | "Onetime" | "Annual";
description?: string;
features?: string[];
isRecommended?: boolean;
originalPrice?: number;
}
Issue: Business concepts defined in UI components Impact: Business logic in presentation layer
2.2 Checkout-Specific Types
Location: apps/portal/src/features/checkout/hooks/useCheckout.ts
// ❌ VIOLATION: Business logic types in UI layer
type CheckoutItemType = "plan" | "installation" | "addon" | "activation" | "vpn";
interface CheckoutItem extends CatalogProductBase {
quantity: number;
itemType: CheckoutItemType;
autoAdded?: boolean;
}
interface CheckoutTotals {
monthlyTotal: number;
oneTimeTotal: number;
}
Issue: Business domain types defined in UI hooks Impact: Business logic scattered across layers
2.3 Catalog Utility Types
Location: apps/portal/src/features/catalog/utils/catalog.utils.ts
// ❌ VIOLATION: TODO comment indicates missing domain type
// TODO: Define CatalogFilter type properly
type CatalogFilter = {
category?: string;
priceMin?: number;
priceMax?: number;
search?: string;
};
Issue: Explicitly acknowledged missing domain type Impact: Business concepts not properly modeled
2.4 Form Extension Patterns
Location: apps/portal/src/features/auth/components/SignupForm/SignupForm.tsx
// ⚠️ ACCEPTABLE: Extending domain schema with UI concerns
export const signupFormSchema = signupInputSchema
.extend({
confirmPassword: z.string().min(1, "Please confirm your password"),
})
.refine(data => data.acceptTerms === true, {
message: "You must accept the terms and conditions",
path: ["acceptTerms"],
})
Status: This is actually a good pattern - extending domain schemas with UI-specific fields
3. Cross-Cutting Concerns
3.1 Environment Configuration
Location: apps/bff/src/core/config/env.validation.ts
// ⚠️ ACCEPTABLE: Infrastructure configuration
export const envSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
// ... many environment variables
});
Status: Environment configuration is appropriately infrastructure-specific
3.2 Database Mapping Types
Location: apps/bff/src/infra/mappers/mapping.mapper.ts
// ✅ GOOD: Infrastructure mapping with clear documentation
export function mapPrismaMappingToDomain(mapping: PrismaIdMapping): UserIdMapping {
// Maps Prisma entity to Domain type
}
Status: Proper infrastructure-to-domain mapping
Recommendations
1. Immediate Actions (High Priority)
1.1 Move Business Types to Domain
Action: Move the following types to appropriate domain modules:
// Move to packages/domain/orders/
export interface OrderFulfillmentValidationResult {
sfOrder: SalesforceOrderRecord;
clientId: number;
isAlreadyProvisioned: boolean;
whmcsOrderId?: string;
}
// Move to packages/domain/catalog/
export interface PricingTier {
name: string;
price: number;
billingCycle: "Monthly" | "Onetime" | "Annual";
description?: string;
features?: string[];
isRecommended?: boolean;
originalPrice?: number;
}
// Move to packages/domain/orders/
export interface CheckoutItem {
// Define based on CatalogProductBase + quantity + itemType
}
1.2 Centralize Validation Schemas
Action: Move common validation patterns to domain:
// Add to packages/domain/common/schema.ts
export const salesforceAccountIdSchema = z.string().min(1, "Salesforce AccountId is required");
export const nonEmptyStringSchema = z.string().min(1, "Value cannot be empty").trim();
export const soqlFieldNameSchema = z.string().trim().regex(/^[A-Za-z0-9_.]+$/, "Invalid SOQL field name");
1.3 Define Missing Domain Types
Action: Complete the TODO items and define missing types:
// Add to packages/domain/catalog/
export interface CatalogFilter {
category?: string;
priceMin?: number;
priceMax?: number;
search?: string;
}
// Add to packages/domain/orders/
export interface OrderCreateResponse {
sfOrderId: string;
status: string;
message: string;
}
2. Medium Priority Actions
2.1 Standardize Response Types
Action: Create standardized API response types in domain:
// Add to packages/domain/common/
export interface OrderCreateResponse {
sfOrderId: string;
status: string;
message: string;
}
export const orderCreateResponseSchema = z.object({
sfOrderId: z.string(),
status: z.string(),
message: z.string(),
});
2.2 Refactor Checkout Logic
Action: Move checkout business logic to domain:
// Add to packages/domain/orders/
export interface CheckoutCart {
items: CheckoutItem[];
totals: CheckoutTotals;
configuration: OrderConfigurations;
}
export function calculateCheckoutTotals(items: CheckoutItem[]): CheckoutTotals {
// Move calculation logic from Portal to Domain
}
3. Long-term Improvements
3.1 Domain Service Layer
Action: Consider creating domain services for complex business logic:
// Add to packages/domain/orders/services/
export class OrderValidationService {
static validateFulfillmentRequest(
sfOrderId: string,
idempotencyKey: string
): Promise<OrderFulfillmentValidationResult> {
// Move validation logic from BFF to Domain
}
}
3.2 Type Safety Improvements
Action: Enhance type safety across layers:
// Ensure all domain types are properly exported
// Add runtime validation for all domain types
// Implement proper error handling with domain error types
Implementation Plan
Phase 1: Critical Violations (Week 1-2)
- Move
OrderFulfillmentValidationResultto domain - Move
PricingTierto domain - Centralize validation schemas
- Define missing
CatalogFiltertype
Phase 2: Business Logic Consolidation (Week 3-4)
- Move checkout types to domain
- Standardize API response types
- Refactor validation services
- Update imports across applications
Phase 3: Architecture Improvements (Week 5-6)
- Create domain service layer
- Implement proper error handling
- Add comprehensive type exports
- Update documentation
Success Metrics
- Type Reuse: 90%+ of business types defined in domain
- Validation Consistency: Single validation schema per business rule
- Import Clarity: Clear domain imports in BFF and Portal
- Documentation: Updated architecture documentation
Conclusion
The analysis reveals that while the domain package is well-structured, there are several violations where business logic and types are scattered across the BFF and Portal layers. The recommended actions will help establish the domain package as the true single source of truth, improving maintainability and consistency across the application.
The violations are primarily in business logic types and validation schemas that should be centralized in the domain layer. The form extension patterns in the Portal are actually good examples of how to properly extend domain schemas with UI-specific concerns.
Report Generated: $(date)
Analysis Scope: BFF and Portal applications
Domain Package: packages/domain/