🕐 Tempo di lettura: 2 minuti
Hai mai orchestrato servizi… che poi falliscono? Se ti capita di gestire chiamate a sistemi esterni (trascrizione, storage, AI, ecc.), sai che prima o poi qualcosa potrebbe smettere di funzionare. Ma invece di annidare try/catch, puoi costruire qualcosa di più robusto e pulito.
In Athanatos, "progettino" personale cui sto lavorando ultimamente, ho usato una combinazione di:
- orchestrator pattern per coordinare i diversi servizi (trascrizione, OpenAI, salvataggio, ecc.)
- strategy pattern per gestire il fallback dinamico in caso di errore
📌 Struttura del flusso
- orchestratore principale: coordina le chiamate ai vari servizi
- sotto-orchestratore specializzato:
TranscriberService, si occupa solo della trascrizione. Prova prima unPrimaryTranscriberServicee, in caso di errore, delega il compito ad unFallbackTranscriberService - fallback strategy: tutte le strategie di trascrizione (Whisper, AssemblyAI, Google STT) implementano la stessa interfaccia (
ITranscriberService). Cambia l'implementazione, ma la firma rimane identica, consentendo di variare il comportamento in modo trasparente
L'organizzazione dei file riflette esattamente questa logica:
service/
├── transcriber/
│ ├── BaseTranscriberService
│ ├── FallbackTranscriberService
│ ├── ITranscriberService
│ ├── PrimaryTranscriberService
│ └── TranscriberService
├── OpenAIService
├── OrchestratorService
└── StorageService
Il cuore del meccanismo è davvero poche righe: il TranscriberService prova il primary e, se questo fallisce, passa al fallback grazie a onErrorResume:
@Slf4j
@Service
@RequiredArgsConstructor
public class TranscriberService implements ITranscriberService {
protected final PrimaryTranscriberService primaryTranscriberService;
protected final FallbackTranscriberService fallbackTranscriberService;
public Mono<TranscriptionResponse> transcribe(final PsychometricFileData fileData) {
return primaryTranscriberService.transcribe(fileData)
.doOnError(throwable -> log.error("<-- primary service failed, trying fallback implementation"))
.onErrorResume(throwable -> fallbackTranscriberService.transcribe(fileData));
}
}
🔍 Perché funziona?
- decoupling tra orchestrazione e fallback
- separazione delle responsabilità chiara (ogni servizio fa una cosa sola, in un solo modo)
- facilità di estensione: 1 nuovo trascriber = 1 nuova classe
- uniformità dell'interfaccia grazie allo strategy pattern
- pulizia e testabilità migliorate
Lo stesso approccio si adatta bene anche ad altri contesti reali, come i pagamenti multi-provider (PayPal, Apple Pay, ecc.) o le notifiche multi-canale (email, SMS, push), dove fallback e comportamento intercambiabile sono fondamentali!
Alla prossima pillola! ☕