Skip to content
DraftAudienceDevOwner@platform-teamDernière revue2026-05-22

Orchestrator — migration et plan de refactoring

Structure actuelle vs cible, analyse détaillée des écarts, plan de refactoring, checklist de conformité, priorités recommandées et FAQ. Pour la vue d'ensemble, voir Orchestrator — overview.

Migration Progressive

Étape 1 : Créer l'Orchestrator Service

  • Scaffold le service
  • Implémenter les adapters HTTP vers les services existants
  • Implémenter CreateOrganizationUseCase

Étape 2 : Router via API Gateway

  • Configurer le routing vers l'Orchestrator
  • Tester le workflow complet

Étape 3 : Fermer les accès directs

  • Retirer l'exposition publique des services métier
  • Configurer le réseau Docker

Étape 4 : Migrer les autres workflows

  • Identifier tous les workflows multi-services
  • Les migrer un par un vers l'Orchestrator

Questions Fréquentes

Pourquoi pas de message broker (Kafka/RabbitMQ) ?

Pour l'instant, la communication synchrone HTTP est suffisante et plus simple à debugger. On pourra migrer vers des events pour les workflows qui nécessitent une résilience accrue (ex: notifications asynchrones).

Comment gérer les échecs partiels ?

Le pattern Saga avec compensation : on annule les opérations réussies dans l'ordre inverse si une étape échoue.

Performance ?

L'Orchestrator ajoute un hop réseau. C'est acceptable pour les workflows de création. Pour les lectures simples (get wallet balance), on peut autoriser un accès direct via l'API Gateway.


Document à présenter à l'équipe - Version 1.0


Annexe — Audit architecture Orchestrator

Résumé Exécutif

CritèreStatusNote
Interfaces définies✅ BonInterfaces pour tous les services externes
Injection de dépendances✅ BonTokens utilisés correctement
Séparation des responsabilités⚠️ PartielOrchestrators existent mais mal positionnés
Clean Architecture Layers❌ Non conformePas de séparation domain/application/infrastructure
Compensation (Saga)❌ AbsentPas de rollback en cas d'échec
Naming conventions⚠️ PartielOrchestrators ≠ Use Cases

Score global : 50% - Bonnes fondations, restructuration nécessaire.


Structure Actuelle vs Cible

Actuelle

src/
├── core/
│   ├── common/                    # ✅ OK
│   ├── config/                    # ✅ OK
│   ├── dto/                       # ⚠️ Mal placé
│   ├── interfaces/                # ⚠️ Devrait être dans domain/
│   └── services/                  # ⚠️ Devrait être dans infrastructure/
├── modules/
│   └── transactions/
│       ├── controllers/           # ✅ OK (mais devrait être dans presentation/)
│       ├── dto/                   # ⚠️ Mélange request/response/internal
│       ├── orchestrators/         # ⚠️ Devrait être use-cases dans application/
│       └── services/              # ⚠️ Application services mal positionnés
└── app.module.ts

Cible

src/
├── core/                          # Config, constants, tokens
│   ├── config/
│   └── constants/
│       └── injection-tokens.ts
├── common/                        # Cross-cutting concerns
│   ├── decorators/
│   ├── filters/
│   ├── interceptors/
│   └── middleware/
├── domain/                        # COUCHE DOMAIN
│   ├── interfaces/
│   │   └── services/              # ILedgerService, ICustomerService...
│   └── exceptions/                # Domain exceptions
├── application/                   # COUCHE APPLICATION
│   ├── use-cases/
│   │   ├── transactions/
│   │   │   ├── process-transfer.use-case.ts
│   │   │   └── process-recharge.use-case.ts
│   │   └── organizations/
│   │       └── create-organization.use-case.ts
│   └── services/                  # Application services partagés
│       ├── account-resolution.service.ts
│       ├── fees-calculation.service.ts
│       └── transaction-validation.service.ts
├── infrastructure/                # COUCHE INFRASTRUCTURE
│   └── adapters/
│       └── http/
│           ├── base-http.adapter.ts
│           ├── ledger-http.adapter.ts
│           ├── customer-http.adapter.ts
│           └── notification-http.adapter.ts
├── presentation/                  # COUCHE PRESENTATION
│   ├── controllers/
│   │   └── transactions.controller.ts
│   └── dto/
│       ├── requests/
│       └── responses/
└── modules/                       # Composition DI
    └── transactions.module.ts

Analyse Détaillée

1. Points Positifs ✅

1.1 Interfaces bien définies

typescript
// core/interfaces/ledger-wallet.interface.ts
export interface ILedgerWalletService {
  updateWallet(request: WalletUpdateRequestDto): Promise<WalletUpdateResponseDto>;
  getWalletByUserId(userId: string): Promise<{ accountId: string; balance?: number }>;
  createP2PTransaction(request: any): Promise<{ id: string; balance?: number }>;
}

Verdict : Les interfaces sont présentes et permettent le découplage.

1.2 Injection de dépendances via tokens

typescript
// transfer.orchestrator.ts
constructor(
  @Inject('LEDGER_WALLET_SERVICE')
  private readonly ledgerWalletClient: ILedgerWalletService,
  @Inject('RISK_ENGINE_SERVICE')
  private readonly riskEngineClient: IRiskEngineService,
)

Verdict : Pattern correct, permet de changer d'implémentation facilement.

1.3 Services applicatifs spécialisés

  • AccountResolutionService - Résolution des comptes
  • TransactionValidationService - Validations métier
  • FeesCalculationService - Calcul des frais
  • TransactionMapperService - Mapping des données

Verdict : Bonne séparation des responsabilités.


2. Points à Améliorer ⚠️

2.1 Orchestrator vs Use Case

Problème : Les "orchestrators" font le travail des "use cases" mais :

  • Sont dans modules/transactions/orchestrators/ au lieu de application/use-cases/
  • Ne suivent pas le naming convention *.use-case.ts

Impact : Confusion sur les responsabilités, non-conformité avec le CLAUDE.md.

Solution :

# Renommer et déplacer
modules/transactions/orchestrators/transfer.orchestrator.ts
→ application/use-cases/transactions/process-transfer.use-case.ts

2.2 Controller → Service → Orchestrator (couche inutile)

Problème : Le controller passe par TransactionsService qui délègue à l'orchestrator.

typescript
// Actuel : Controller → Service → Orchestrator
@Post('send')
async send(@Body() dto) {
  return this.transactionsService.send(dto, ...);  // Couche intermédiaire
}

// transactionsService.send() appelle juste transferOrchestrator.execute()

Impact : Couche TransactionsService ajoute de la complexité sans valeur.

Solution : Controller appelle directement le Use Case.

typescript
// Cible : Controller → Use Case
@Post('send')
async send(@Body() dto) {
  return this.processTransferUseCase.execute(dto, ...);
}

2.3 DTOs mal organisés

Problème :

  • core/dto/ contient des DTOs internes
  • modules/transactions/dto/ mélange request, response, et internal

Solution :

presentation/dto/requests/send-transaction.dto.ts    # Request du client
presentation/dto/responses/send-response.dto.ts      # Response au client
application/dto/internal/transaction-request.dto.ts  # DTOs internes

3. Points Critiques ❌

3.1 Pas de couche Domain

Problème : Aucune entité de domaine, value objects, ou exceptions métier.

Impact :

  • Logique métier dispersée dans les services
  • Validation métier difficile à maintenir
  • Pas de richesse comportementale des entités

Solution : Créer la couche domain avec :

typescript
// domain/exceptions/insufficient-balance.exception.ts
export class InsufficientBalanceException extends DomainException {
  readonly code = 'INSUFFICIENT_BALANCE';
  readonly statusCode = 400;
}

// domain/exceptions/transaction-blocked.exception.ts
export class TransactionBlockedException extends DomainException {
  readonly code = 'TRANSACTION_BLOCKED';
  readonly statusCode = 403;
}

3.2 Pas de compensation (Saga Pattern)

Problème : L'orchestrator Transfer ne gère pas les échecs partiels.

typescript
// Actuel - Pas de rollback
async execute(dto, ...) {
  // Si une étape échoue après la vérification PIN,
  // aucune compensation n'est effectuée
  const pinVerification = await this.authClient.verifyPin(...);
  const [senderContext, receiverContext] = await Promise.all([...]);
  // ... Si ça échoue ici, pas de rollback
  const transaction = await this.ledgerWalletClient.createP2PTransaction(...);
}

Impact : En cas d'échec partiel, données incohérentes possibles.

Solution : Implémenter le pattern Saga avec compensation.

typescript
async execute(input) {
  let createdTransaction = null;

  try {
    // Steps...
    createdTransaction = await this.ledgerService.createTransaction(...);
    // ...
  } catch (error) {
    // Compensation
    if (createdTransaction) {
      await this.ledgerService.reverseTransaction(createdTransaction.id);
    }
    throw error;
  }
}

3.3 Adapters HTTP dans le mauvais dossier

Problème : core/services/ledger-wallet/ledger-wallet-http.service.ts

Solution : Déplacer vers infrastructure/adapters/http/ledger-http.adapter.ts

3.4 Interfaces dans core au lieu de domain

Problème : core/interfaces/ devrait être domain/interfaces/services/


Plan de Refactoring

Phase 1 : Structure des dossiers (sans casser le code)

bash
# 1. Créer la nouvelle structure
mkdir -p src/domain/interfaces/services
mkdir -p src/domain/exceptions
mkdir -p src/application/use-cases/transactions
mkdir -p src/application/use-cases/organizations
mkdir -p src/application/services
mkdir -p src/infrastructure/adapters/http
mkdir -p src/presentation/controllers
mkdir -p src/presentation/dto/requests
mkdir -p src/presentation/dto/responses

# 2. Déplacer les interfaces (avec alias temporaires)
mv src/core/interfaces/*.interface.ts src/domain/interfaces/services/

# 3. Déplacer les adapters HTTP
mv src/core/services/*/*.service.ts src/infrastructure/adapters/http/
# Renommer en *.adapter.ts

# 4. Déplacer les orchestrators → use-cases
mv src/modules/transactions/orchestrators/*.orchestrator.ts src/application/use-cases/transactions/
# Renommer en *.use-case.ts

Phase 2 : Renommage et refactoring

Fichier ActuelFichier Cible
transfer.orchestrator.tsprocess-transfer.use-case.ts
recharge.orchestrator.tsprocess-recharge.use-case.ts
fees.orchestrator.tscalculate-fees.use-case.ts
ledger-wallet-http.service.tsledger-http.adapter.ts
customer-http.service.tscustomer-http.adapter.ts

Phase 3 : Ajouter Domain Exceptions

typescript
// domain/exceptions/base.exception.ts
export abstract class DomainException extends Error {
  abstract readonly code: string;
  abstract readonly statusCode: number;
}

// domain/exceptions/insufficient-balance.exception.ts
// domain/exceptions/transaction-blocked.exception.ts
// domain/exceptions/wallet-not-found.exception.ts
// domain/exceptions/invalid-pin.exception.ts

Phase 4 : Ajouter Compensation (Saga)

Modifier les use-cases pour gérer les rollbacks.


Checklist de Conformité

RègleActuelAction
Interfaces dans domain/interfaces/services/Déplacer
Use Cases dans application/use-cases/Déplacer + renommer
Adapters HTTP dans infrastructure/adapters/http/Déplacer + renommer
Controllers dans presentation/controllers/⚠️Déplacer
DTOs séparés (requests/responses)Réorganiser
Domain ExceptionsCréer
Compensation (Saga)Implémenter
Injection tokens centralisés⚠️Consolider dans core/constants/
Suffixes de fichiers corrects⚠️Renommer

Priorités Recommandées

  1. P0 - Critique : Ajouter compensation dans les use-cases existants
  2. P1 - Important : Créer domain exceptions + les utiliser
  3. P2 - Moyen : Restructurer les dossiers
  4. P3 - Low : Renommer les fichiers

Audit réalisé le 22/01/2026

Nex — Plateforme fintech CEMAC