diff --git a/docs/decisions/007-service-classification.md b/docs/decisions/007-service-classification.md new file mode 100644 index 00000000..034eb9ba --- /dev/null +++ b/docs/decisions/007-service-classification.md @@ -0,0 +1,236 @@ +# ADR-007: Service Classification (Facades, Orchestrators, Aggregators) + +**Date**: 2025-01-15 +**Status**: Accepted + +## Context + +The BFF layer communicates with multiple external systems (WHMCS, Salesforce, Freebit, JapanPost) and coordinates complex workflows. Without clear naming conventions, services become: + +- Difficult to understand at a glance +- Hard to know what they're responsible for +- Inconsistent in their patterns + +Many "services" were actually orchestrating multiple systems but named generically (e.g., `SalesforceService`, `FreebitOperationsService`). + +## Decision + +**Classify services by their architectural role** using clear naming conventions: + +| Type | Suffix | Purpose | Dependencies | +| ---------------- | --------------- | ------------------------------------------------ | ----------------------------------------- | +| **Facade** | `*Facade` | Unified entry point for an integration subsystem | Multiple services within same integration | +| **Orchestrator** | `*Orchestrator` | Coordinate workflows across multiple systems | Multiple services/integrations | +| **Aggregator** | `*Aggregator` | Combine read-only data from multiple sources | Multiple services (read-only) | +| **Service** | `*Service` | Single-responsibility operations | 1-2 integrations max | + +## Rationale + +### Why Facades? + +Integration modules (WHMCS, Salesforce, Freebit) contain multiple internal services. A **Facade** provides: + +- **Single entry point**: Consumers don't need to know internal structure +- **Consistent interface**: Unified error handling, logging, queueing +- **Encapsulation**: Internal service changes don't affect consumers + +```typescript +// ✅ GOOD: Facade abstracts internal complexity +@Injectable() +export class WhmcsConnectionFacade { + // Single entry point for ALL WHMCS API operations + // Handles queueing, error handling, request prioritization +} + +// Consumers use the facade +constructor(private readonly whmcs: WhmcsConnectionFacade) {} +``` + +### Why Orchestrators? + +Complex workflows span multiple systems. An **Orchestrator**: + +- Coordinates multi-step operations +- Manages transaction boundaries +- Handles cross-system error recovery + +```typescript +// ✅ GOOD: Orchestrator for cross-system workflow +@Injectable() +export class OrderFulfillmentOrchestrator { + constructor( + private readonly salesforce: SalesforceFacade, + private readonly whmcs: WhmcsOrderService, + private readonly freebit: FreebitFacade + ) {} + + async executeFulfillment(orderId: string) { + // Coordinates SF → WHMCS → Freebit workflow + } +} +``` + +### Why Aggregators? + +Dashboard and profile endpoints combine data from multiple sources. An **Aggregator**: + +- Is **read-only** (no mutations) +- Combines data from multiple services +- Handles partial failures gracefully + +```typescript +// ✅ GOOD: Aggregator for read-only data composition +@Injectable() +export class MeStatusAggregator { + constructor( + private readonly users: UsersFacade, + private readonly orders: OrderOrchestrator, + private readonly payments: WhmcsPaymentService + ) {} + + async getStatusForUser(userId: string): Promise { + // Combines user, order, payment data for dashboard + } +} +``` + +### Alternatives Considered + +| Approach | Pros | Cons | +| ----------------------------------------------- | ---------------------- | -------------------------------------- | +| **Generic `*Service`** | Familiar | No architectural clarity | +| **Layer-based (Repository/Service/Controller)** | Simple | Doesn't capture orchestration patterns | +| **Role-based naming** | Clear responsibilities | More files, learning curve | + +## Consequences + +### Positive + +- Clear responsibilities from class name +- Easier onboarding (developers know what each type does) +- Consistent patterns across modules +- Better testability (mock entire facades) + +### Negative + +- Refactoring existing services required +- Developers must learn classification rules +- More specific naming conventions to follow + +## Implementation + +### Integration Facades + +Located in `integrations/{provider}/facades/`: + +``` +integrations/ +├── whmcs/ +│ ├── facades/ +│ │ └── whmcs.facade.ts # WhmcsConnectionFacade +│ └── services/ # Internal services +├── salesforce/ +│ ├── facades/ +│ │ └── salesforce.facade.ts # SalesforceFacade +│ └── services/ +└── freebit/ + ├── facades/ + │ └── freebit.facade.ts # FreebitFacade + └── services/ +``` + +### Module Orchestrators + +Located in module services or dedicated orchestrators folder: + +```typescript +// modules/orders/services/order-fulfillment-orchestrator.service.ts +@Injectable() +export class OrderFulfillmentOrchestrator {} + +// modules/subscriptions/sim-management/services/sim-orchestrator.service.ts +@Injectable() +export class SimOrchestrator {} +``` + +### Aggregators + +Currently in their modules, planned for dedicated `aggregators/` folder: + +```typescript +// modules/me-status/me-status.service.ts +@Injectable() +export class MeStatusAggregator {} + +// modules/users/infra/user-profile.service.ts +@Injectable() +export class UserProfileAggregator {} +``` + +### Dependency Rules + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ALLOWED IMPORTS │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Controllers │ +│ ↓ (can import) │ +│ Orchestrators / Aggregators / Facades │ +│ ↓ (can import) │ +│ Services │ +│ ↓ (can import) │ +│ Integration Facades │ +│ ↓ (can import) │ +│ Integration Entity Services │ +│ │ +├─────────────────────────────────────────────────────────────────┤ +│ FORBIDDEN │ +│ │ +│ ✗ Controllers → Integration Services (use facades) │ +│ ✗ Services → Orchestrators (wrong direction) │ +│ ✗ Aggregators → Mutation methods │ +│ ✗ Integration Services → Module Services │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Naming Conventions + +### DO + +```typescript +// Integration entry points +export class WhmcsConnectionFacade {} +export class SalesforceFacade {} +export class FreebitFacade {} + +// Cross-system workflows +export class OrderFulfillmentOrchestrator {} +export class SimOrchestrator {} + +// Read-only data composition +export class MeStatusAggregator {} +export class UserProfileAggregator {} + +// Single-responsibility operations +export class WhmcsInvoiceService {} +export class SalesforceOrderService {} +``` + +### DON'T + +```typescript +// ❌ Generic names that hide complexity +export class SalesforceService {} // What does it do? +export class FreebitOperationsService {} // Operations = everything? +export class MeStatusService {} // Service doing aggregation + +// ❌ Orchestrators named as services +export class SimOrchestratorService {} // Drop the Service suffix +``` + +## Related + +- [ADR-006: Thin Controllers](./006-thin-controllers.md) +- [BFF Integration Patterns](../development/bff/integration-patterns.md)