Orchestrator — architecture interne
Détail de la structure du service Orchestrator, ses responsabilités, ses interfaces externes et sa configuration réseau. Pour la vue d'ensemble du rôle, voir Orchestrator — overview.
Orchestrator Service
Responsabilités
- Orchestrer les workflows multi-services
- Gérer les transactions distribuées (Saga pattern)
- Implémenter les compensations en cas d'échec
- Centraliser la logique des processus métier
Structure du Service
services/orchestrator/src/
├── core/
│ └── constants/
│ └── injection-tokens.ts
│
├── domain/
│ └── interfaces/services/
│ ├── customer.service.interface.ts # ICustomerService
│ ├── ledger.service.interface.ts # ILedgerService
│ └── notification.service.interface.ts # INotificationService
│
├── application/
│ └── use-cases/
│ ├── organizations/
│ │ ├── create-organization.use-case.ts
│ │ ├── add-organization-member.use-case.ts
│ │ └── onboard-organization.use-case.ts
│ ├── individuals/
│ │ ├── create-individual-account.use-case.ts
│ │ └── complete-kyc.use-case.ts
│ └── transactions/
│ ├── process-transfer.use-case.ts
│ ├── process-deposit.use-case.ts
│ └── process-withdrawal.use-case.ts
│
├── infrastructure/
│ └── adapters/http/
│ ├── base-http.adapter.ts
│ ├── customer-http.adapter.ts
│ ├── ledger-http.adapter.ts
│ └── notification-http.adapter.ts
│
└── presentation/
└── controllers/
├── organizations.controller.ts
├── individuals.controller.ts
└── transactions.controller.tsExemple Concret : Création d'une Organization
Workflow
Implémentation
typescript
// application/use-cases/organizations/create-organization.use-case.ts
@Injectable()
export class CreateOrganizationUseCase {
constructor(
@Inject(CUSTOMER_SERVICE)
private readonly customerService: ICustomerService,
@Inject(LEDGER_SERVICE)
private readonly ledgerService: ILedgerService,
@Inject(NOTIFICATION_SERVICE)
private readonly notificationService: INotificationService,
) {}
async execute(input: CreateOrganizationInput): Promise<CreateOrganizationResult> {
let corporate: Corporate | null = null;
let adminPerson: Person | null = null;
let account: Account | null = null;
try {
// ═══════════════════════════════════════════════════════════
// STEP 1: Créer l'entité Corporate
// ═══════════════════════════════════════════════════════════
corporate = await this.customerService.createCorporate({
name: input.companyName,
registrationNumber: input.registrationNumber,
country: input.country,
industry: input.industry,
});
// ═══════════════════════════════════════════════════════════
// STEP 2: Créer l'Admin Person et le lier au Corporate
// ═══════════════════════════════════════════════════════════
adminPerson = await this.customerService.createPerson({
firstName: input.admin.firstName,
lastName: input.admin.lastName,
email: input.admin.email,
phone: input.admin.phone,
corporateId: corporate.id,
role: CorporateRole.ADMIN,
});
// ═══════════════════════════════════════════════════════════
// STEP 3: Créer le compte Nex pour le Corporate
// ═══════════════════════════════════════════════════════════
account = await this.ledgerService.createAccount({
entityId: corporate.id,
entityType: EntityType.CORPORATE,
currency: input.currency || 'XAF',
name: `Compte Principal - ${input.companyName}`,
});
// ═══════════════════════════════════════════════════════════
// STEP 4: Créer le Wallet principal
// ═══════════════════════════════════════════════════════════
const wallet = await this.ledgerService.createWallet({
accountId: account.id,
type: WalletType.CORPORATE_MAIN,
name: 'Wallet Principal',
});
// ═══════════════════════════════════════════════════════════
// STEP 5: Envoyer les notifications
// ═══════════════════════════════════════════════════════════
await this.notificationService.send({
type: NotificationType.EMAIL,
template: 'CORPORATE_WELCOME',
recipient: input.admin.email,
data: {
companyName: input.companyName,
adminName: `${input.admin.firstName} ${input.admin.lastName}`,
accountNumber: account.accountNumber,
},
});
// ═══════════════════════════════════════════════════════════
// SUCCESS: Retourner le résultat
// ═══════════════════════════════════════════════════════════
return {
success: true,
data: {
corporateId: corporate.id,
adminPersonId: adminPerson.id,
accountId: account.id,
walletId: wallet.id,
accountNumber: account.accountNumber,
},
};
} catch (error) {
// ═══════════════════════════════════════════════════════════
// COMPENSATION: Rollback en cas d'échec
// ═══════════════════════════════════════════════════════════
await this.compensate({ corporate, adminPerson, account }, error);
throw error;
}
}
/**
* Compensation (rollback) en cas d'échec
* Supprime les entités créées dans l'ordre inverse
*/
private async compensate(
created: { corporate: Corporate | null; adminPerson: Person | null; account: Account | null },
originalError: Error,
): Promise<void> {
const compensationErrors: Error[] = [];
// Rollback Account (si créé)
if (created.account) {
try {
await this.ledgerService.deleteAccount(created.account.id);
} catch (e) {
compensationErrors.push(e);
}
}
// Rollback Person (si créé)
if (created.adminPerson) {
try {
await this.customerService.deletePerson(created.adminPerson.id);
} catch (e) {
compensationErrors.push(e);
}
}
// Rollback Corporate (si créé)
if (created.corporate) {
try {
await this.customerService.deleteCorporate(created.corporate.id);
} catch (e) {
compensationErrors.push(e);
}
}
// Log les erreurs de compensation pour investigation
if (compensationErrors.length > 0) {
console.error('Compensation errors:', compensationErrors);
// TODO: Alerter l'équipe ops pour intervention manuelle
}
}
}Controller
typescript
// presentation/controllers/organizations.controller.ts
@Controller('organizations')
@ApiTags('Organizations')
export class OrganizationsController {
constructor(
private readonly createOrganizationUseCase: CreateOrganizationUseCase,
private readonly addMemberUseCase: AddOrganizationMemberUseCase,
) {}
@Post()
@ApiOperation({ summary: 'Créer une nouvelle organization' })
@ApiResponse({ status: 201, type: CreateOrganizationResponseDto })
async create(
@Body() dto: CreateOrganizationDto,
@CurrentUser() user: AuthenticatedUser,
): Promise<ApiResponse<CreateOrganizationResponseDto>> {
const result = await this.createOrganizationUseCase.execute({
...dto,
createdBy: user.userId,
});
return {
success: true,
code: 'ORGANIZATION_CREATED',
data: result.data,
};
}
@Post(':corporateId/members')
@ApiOperation({ summary: 'Ajouter un membre à l\'organization' })
async addMember(
@Param('corporateId', ParseUUIDPipe) corporateId: string,
@Body() dto: AddMemberDto,
@CurrentUser() user: AuthenticatedUser,
): Promise<ApiResponse<AddMemberResponseDto>> {
const result = await this.addMemberUseCase.execute({
corporateId,
...dto,
addedBy: user.userId,
});
return {
success: true,
code: 'MEMBER_ADDED',
data: result.data,
};
}
}Interfaces des Services Externes
typescript
// domain/interfaces/services/customer.service.interface.ts
export interface ICustomerService {
// Corporate
createCorporate(input: CreateCorporateInput): Promise<Corporate>;
getCorporate(id: string): Promise<Corporate | null>;
updateCorporate(id: string, input: UpdateCorporateInput): Promise<Corporate>;
deleteCorporate(id: string): Promise<void>;
// Person
createPerson(input: CreatePersonInput): Promise<Person>;
getPerson(id: string): Promise<Person | null>;
deletePerson(id: string): Promise<void>;
// KYC
submitKycDocuments(personId: string, documents: KycDocument[]): Promise<KycSubmission>;
getKycStatus(personId: string): Promise<KycStatus>;
}
// domain/interfaces/services/ledger.service.interface.ts
export interface ILedgerService {
// Account
createAccount(input: CreateAccountInput): Promise<Account>;
getAccount(id: string): Promise<Account | null>;
getAccountsByEntity(entityId: string, entityType: EntityType): Promise<Account[]>;
deleteAccount(id: string): Promise<void>;
// Wallet
createWallet(input: CreateWalletInput): Promise<Wallet>;
getWallet(id: string): Promise<Wallet | null>;
// Transactions
transfer(input: TransferInput): Promise<Transaction>;
getTransaction(id: string): Promise<Transaction | null>;
}
// domain/interfaces/services/notification.service.interface.ts
export interface INotificationService {
send(input: SendNotificationInput): Promise<NotificationResult>;
sendBulk(inputs: SendNotificationInput[]): Promise<NotificationResult[]>;
}Configuration Réseau
Docker Compose (Services internes)
yaml
# Les services métier ne sont PAS exposés publiquement
services:
customer-profiles-kyc:
# Pas de "ports:" = non accessible de l'extérieur
networks:
- nxpay-internal
ledger-wallets:
networks:
- nxpay-internal
notifications:
networks:
- nxpay-internal
# Seuls ces services sont exposés
api-gateway:
ports:
- "3000:3000"
networks:
- nxpay-internal
- nxpay-public
orchestrator:
# Accessible uniquement via API Gateway
networks:
- nxpay-internal
networks:
nxpay-internal:
internal: true # Pas d'accès internet
nxpay-public:Variables d'Environnement (Orchestrator)
env
# Services internes (noms Docker)
CUSTOMER_SERVICE_URL=http://customer-profiles-kyc:3000
LEDGER_SERVICE_URL=http://ledger-wallets:3000
NOTIFICATION_SERVICE_URL=http://notifications:3000
CONFIGURATION_SERVICE_URL=http://configuration:3000