Assist_Design/docs/decisions/007-service-classification.md
barsa b885e9c34b docs: add ADR-007 for service classification patterns
Document the architectural decision for classifying BFF services:
- Facades: unified entry points for integration subsystems
- Orchestrators: coordinate workflows across multiple systems
- Aggregators: read-only data composition from multiple sources
- Services: single-responsibility operations

Includes dependency rules, naming conventions, and implementation examples.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 18:53:37 +09:00

8.6 KiB

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
// ✅ 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
// ✅ 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
// ✅ 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<MeStatus> {
    // 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:

// 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:

// 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

// 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

// ❌ 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