- Revised README and documentation links to reflect updated paths and improve clarity on service offerings. - Refactored service components to enhance organization and maintainability, including updates to the Internet and SIM offerings. - Improved user navigation and experience in service-related views by streamlining component structures and enhancing data handling. - Updated internal documentation to align with recent changes in service architecture and eligibility processes.
9.5 KiB
9.5 KiB
New Domain Architecture
Overview
The @customer-portal/domain package is now a unified domain layer following the Provider-Aware Structure pattern. It consolidates all types, schemas, and provider-specific logic into a single, well-organized package.
🎯 Goals
- Single Source of Truth: One place for all domain contracts, schemas, and transformations
- Provider Isolation: Raw provider types and mappers co-located within each domain
- Clean Exports: Simple, predictable import paths
- Type Safety: Runtime validation with Zod + TypeScript inference
- Maintainability: Easy to find and update domain logic
📁 Structure
packages/domain/
├── billing/
│ ├── contract.ts # Invoice, InvoiceItem, InvoiceList
│ ├── schema.ts # Zod schemas
│ ├── providers/
│ │ └── whmcs/
│ │ ├── raw.types.ts # WHMCS-specific types
│ │ └── mapper.ts # Transform WHMCS → Invoice
│ └── index.ts # Public exports
├── subscriptions/
│ ├── contract.ts # Subscription, SubscriptionList
│ ├── schema.ts
│ ├── providers/
│ │ └── whmcs/
│ │ ├── raw.types.ts
│ │ └── mapper.ts
│ └── index.ts
├── payments/
│ ├── contract.ts # PaymentMethod, PaymentGateway
│ ├── schema.ts
│ ├── providers/
│ │ └── whmcs/
│ │ ├── raw.types.ts
│ │ └── mapper.ts
│ └── index.ts
├── sim/
│ ├── contract.ts # SimDetails, SimUsage, SimTopUpHistory
│ ├── schema.ts
│ ├── providers/
│ │ └── freebit/
│ │ ├── raw.types.ts
│ │ └── mapper.ts
│ └── index.ts
├── orders/
│ ├── contract.ts # OrderSummary, OrderDetails, FulfillmentOrder
│ ├── schema.ts
│ ├── providers/
│ │ ├── whmcs/
│ │ │ ├── raw.types.ts # WHMCS AddOrder API types
│ │ │ └── mapper.ts # Transform FulfillmentOrder → WHMCS
│ │ └── salesforce/
│ │ ├── raw.types.ts # Salesforce Order/OrderItem
│ │ └── mapper.ts # Transform Salesforce → OrderDetails
│ └── index.ts
├── services/
│ ├── contract.ts # CatalogProduct, InternetPlan, SimProduct, VpnProduct
│ ├── schema.ts
│ ├── providers/
│ │ └── salesforce/
│ │ ├── raw.types.ts # Salesforce Product2
│ │ └── mapper.ts # Transform Product2 → CatalogProduct
│ └── index.ts
├── common/
│ ├── types.ts # IsoDateTimeString, branded types, API wrappers
│ └── index.ts
├── toolkit/
│ ├── formatting/ # Currency, date, phone, text formatters
│ ├── validation/ # Email, URL, string validators
│ ├── typing/ # Type guards, assertions, helpers
│ └── index.ts
├── package.json
├── tsconfig.json
└── index.ts # Main entry point
📦 Usage
Import Contracts
// Import domain contracts
import type { Invoice, InvoiceList } from "@customer-portal/domain/billing";
import type { Subscription } from "@customer-portal/domain/subscriptions";
import type { SimDetails } from "@customer-portal/domain/sim";
import type { OrderSummary } from "@customer-portal/domain/orders";
import type { InternetPlanCatalogItem } from "@customer-portal/domain/services";
Import Schemas
// Import Zod schemas for runtime validation
import { invoiceSchema, invoiceListSchema } from "@customer-portal/domain/billing";
import { simDetailsSchema } from "@customer-portal/domain/sim";
// Validate at runtime
const invoice = invoiceSchema.parse(rawData);
Import Provider Mappers
// Import provider-specific mappers
import { WhmcsBillingMapper } from "@customer-portal/domain/billing";
import { FreebitSimMapper } from "@customer-portal/domain/sim";
import { WhmcsOrderMapper, SalesforceOrderMapper } from "@customer-portal/domain/orders";
// Transform provider data to normalized domain contracts
const invoice = WhmcsBillingMapper.transformWhmcsInvoice(whmcsInvoiceData);
const simDetails = FreebitSimMapper.transformFreebitAccountDetails(freebitAccountData);
const order = SalesforceOrderMapper.transformOrderToDetails(salesforceOrderRecord, items);
Import Utilities
// Import toolkit utilities
import { Formatting, Validation, Typing } from "@customer-portal/domain/toolkit";
// Use formatters
const formatted = Formatting.formatCurrency(10000, "JPY");
const date = Formatting.formatDate(isoString);
// Use validators
const isValid = Validation.isValidEmail(email);
// Use type guards
if (Typing.isDefined(value)) {
// TypeScript knows value is not null/undefined
}
🔄 Data Flow
Inbound (Provider → Domain)
External API Response
↓
Raw Provider Types (providers/*/raw.types.ts)
↓
Provider Mapper (providers/*/mapper.ts)
↓
Zod Schema Validation (schema.ts)
↓
Domain Contract (contract.ts)
↓
Application Code (BFF, Portal)
Outbound (Domain → Provider)
Application Intent
↓
Domain Contract (contract.ts)
↓
Provider Mapper (providers/*/mapper.ts)
↓
Raw Provider Payload (providers/*/raw.types.ts)
↓
External API Request
🎨 Design Principles
1. Co-location
- Domain contracts, schemas, and provider logic live together
- Easy to find related code
- Clear ownership and responsibility
2. Provider Isolation
- Raw types and mappers nested in
providers/subfolder - Each provider is self-contained
- Easy to add/remove providers
3. Type Safety
- Zod schemas for runtime validation
- TypeScript types inferred from schemas
- Branded types for stronger type checking
4. Clean Exports
- Barrel exports (
index.ts) control public API - Provider mappers exported as namespaces (
WhmcsBillingMapper.*) - Predictable import paths
5. Minimal Dependencies
- Only depends on
zodfor runtime validation - No circular dependencies
- Self-contained domain logic
📋 Domain Reference
Billing
- Contracts:
Invoice,InvoiceItem,InvoiceList - Providers: WHMCS
- Use Cases: Display invoices, payment history, invoice details
Subscriptions
- Contracts:
Subscription,SubscriptionList - Providers: WHMCS
- Use Cases: Display active services, manage subscriptions
Payments
- Contracts:
PaymentMethod,PaymentGateway - Providers: WHMCS
- Use Cases: Payment method management, gateway configuration
SIM
- Contracts:
SimDetails,SimUsage,SimTopUpHistory - Providers: Freebit
- Use Cases: SIM management, usage tracking, top-up history
Orders
- Contracts:
OrderSummary,OrderDetails,FulfillmentOrderDetails - Providers: WHMCS (provisioning), Salesforce (order management)
- Use Cases: Order fulfillment, order history, order details
Services
- Contracts:
InternetPlanCatalogItem,SimCatalogProduct,VpnCatalogProduct - Providers: Salesforce (Product2)
- Use Cases: Product catalog display, product selection, service browsing
Common
- Types:
IsoDateTimeString,UserId,AccountId,OrderId,ApiResponse,PaginatedResponse - Use Cases: Shared utility types across all domains
Toolkit
- Formatting: Currency, date, phone, text formatters
- Validation: Email, URL, string validators
- Typing: Type guards, assertions, helpers
- Use Cases: UI formatting, input validation, type safety
🚀 Migration Guide
From Old Structure
Before:
import type { Invoice } from "@customer-portal/contracts/billing";
import { invoiceSchema } from "@customer-portal/schemas/business/billing.schema";
import { transformWhmcsInvoice } from "@customer-portal/integrations-whmcs/mappers/billing.mapper";
After:
import type { Invoice } from "@customer-portal/domain/billing";
import { invoiceSchema } from "@customer-portal/domain/billing";
import { WhmcsBillingMapper } from "@customer-portal/domain/billing";
const invoice = WhmcsBillingMapper.transformWhmcsInvoice(data);
Benefits
- Fewer imports: Everything in one package
- Clearer intent: Mapper namespace indicates provider
- Better DX: Autocomplete shows all related exports
- Type safety: Contracts + schemas + mappers always in sync
✅ Completed Domains
- ✅ Billing (WHMCS provider)
- ✅ Subscriptions (WHMCS provider)
- ✅ Payments (WHMCS provider)
- ✅ SIM (Freebit provider)
- ✅ Orders (WHMCS + Salesforce providers)
- ✅ Catalog (Salesforce provider)
- ✅ Common (shared types)
- ✅ Toolkit (utilities)
📝 Next Steps
- Update BFF imports to use
@customer-portal/domain/* - Update Portal imports to use
@customer-portal/domain/* - Delete old packages:
contracts,schemas,integrations - Update ESLint rules to prevent imports from old packages
- Update documentation to reference new structure