Assist_Design/docs/architecture/domain-layer.md
barsa 0f8435e6bd Update Documentation and Refactor Service Structure
- 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.
2025-12-25 15:48:57 +09:00

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

  1. Single Source of Truth: One place for all domain contracts, schemas, and transformations
  2. Provider Isolation: Raw provider types and mappers co-located within each domain
  3. Clean Exports: Simple, predictable import paths
  4. Type Safety: Runtime validation with Zod + TypeScript inference
  5. 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 zod for 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

  1. Update BFF imports to use @customer-portal/domain/*
  2. Update Portal imports to use @customer-portal/domain/*
  3. Delete old packages: contracts, schemas, integrations
  4. Update ESLint rules to prevent imports from old packages
  5. Update documentation to reference new structure