2009-08-27 8 views
11

Supponiamo di disporre di un metodo di business logic in grado di eseguire alcune operazioni su un numero di oggetti. Forse vuoi chiamare un servizio di raccolta di numeri della lotteria, una volta per ogni persona selezionata da una lista. In Java, il codice potrebbe essere simile a questo:Gestione di errori e feedback durante l'esecuzione di operazioni bulk in un'architettura a più livelli

Set<Person> selectedPeople = ... // fetch list of people 
for (Person person : selectedPeople) { 
    String lotteryNumber = callLotteryNumberWebService(person); 
    // ... 
} 

Si noti che il numero di servizio web della lotteria può avere effetti collaterali, come la registrazione che la persona ha richiesto un numero della lotteria (carica forse proprio account), quindi anche se la chiamata al servizio web non riesce per una persona, potrebbe avere successo per altri. Queste informazioni (i numeri della lotteria) dovranno essere riportate a un livello superiore (la vista).

Se questo è il caso in cui è stata eseguita una singola operazione, il metodo della logica di business potrebbe restituire un singolo valore (ad esempio il numero della lotteria) o generare un'eccezione con qualsiasi dettaglio dell'errore. Ma per le operazioni di massa, sarebbe possibile che alcune delle operazioni riuscissero e alcune fallissero.

Questo sembra un tipo di problema che si verificherebbe in molte applicazioni e ci dovrebbe essere un modo pulito per gestirlo. Quindi, qual è il modo migliore per alimentare questo tipo di informazioni dal livello della business logic a un altro livello in un'applicazione (come una vista), preferibilmente in un modo generico che può essere riutilizzato per diversi tipi di dati e operazioni?

risposta

1

Se ho capito, si ha una situazione in cui alcune richieste possono passare e alcune possono fallire. Non sono sicuro di dove vuoi che l'errore ritorni ma potresti avere una delle seguenti (o una variante o una combinazione):

  • Un elenco di errori e oggetti di dominio interessati. Un oggetto di dominio di base o qualcosa con un ID persistente potrebbe essere utile per il riutilizzo. Per esempio. una raccolta di errori che si riferiscono agli oggetti del dominio.
  • È possibile inserire (AOP, DI) nell'oggetto Persona una sorta di oggetto/messaggio di errore. Per esempio. if (person. Errors) {...}
  • È possibile racchiudere la raccolta Person in un messaggio con intestazione, corpo, informazioni di errore
  • Tutti gli oggetti dominio possono includere una raccolta errori accessibile tramite un'interfaccia; o Person supporta l'interfaccia IHasErrors. È possibile rendere questo generico e utilizzare un oggetto Error di base che supporta gli avvisi e la convalida e tutti i tipi di cose.

Se si dispone di un sistema a più livelli (anziché a più livelli), è possibile che si disponga di un'architettura basata su messaggi che possa facilmente ospitare un tipo di meccanismo di errore/avviso/convalida generico. I sistemi SOA/Ajax si prestano a questo.

Felice di approfondire un po 'se avete domande specifiche.

1

Preferirei restituire una raccolta di oggetti di errore personalizzati che identificano l'oggetto, che viene effettuata dall'errore, dal codice di errore e dalla descrizione. In questo modo è possibile provare a rimediare agli errori o visualizzarli ulteriormente all'utente.

0

Probabilmente restituirei una mappa dei risultati di tipo Map<Person,Future<String>> dal mio servizio getLotteryNumbers<Collection<Person>>.

Vorrei quindi scorrere la mappa e utilizzare Future.get() per ottenere il numero della lotteria o l'eccezione generata.

In alcuni dei miei servizi mi piace implementare tutte le chiamate come chiamate singole e quindi avere la logica nel mio servizio per raggrupparle ed elaborarle come gruppo. Il batching viene implementato utilizzando un LinkedBlockingQueue e un thread di polling.

In questo scenario restituisco un Future<Thing> che attende che i risultati batch siano disponibili utilizzando un CountdownLatch.

Date un'occhiata a Java concorrenza in pratica per vedere come questi componenti possono lavorare tutti insieme http://jcip.net/

0

Mi piacerebbe guardare in DTOs per questo tipo di compito. Il DTO potrebbe anche includere informazioni sulla persistenza del successo o meno e altri tipi di "metadati".

1

Penso che tu stia davvero esagerando con le eccezioni se stai pensando in questi termini!

È perfettamente ok restituire valori che significano errore, piuttosto che generare un'eccezione. Spesso è meglio. Le eccezioni si usano al meglio quando non puoi recuperare al livello di astrazione a cui sei, ma non dovresti usarle come mezzo principale di controllo del flusso o i tuoi programmi diventeranno molto difficili da leggere.

Il servizio Web non restituisce eccezioni, restituisce codici di ritorno e messaggi. Memorizzerei qualche utile rappresentazione che presenti le informazioni restituite, e restituirei l'elenco di quelle per la vista o qualsiasi altra cosa la guardi.

10

Questa domanda mette in evidenza importanti differenze tra usi appropriati di gestione delle eccezioni, le transazioni, e l'idea flusso di lavoro "compensazione" che è ciò che il richiedente sta cercando di ottenere in, se correttamente affermando:

Questo sembra come un tipo di problema che si verificherebbe in molte applicazioni e ci dovrebbe essere un modo pulito per gestirlo.

Si tratta di un problema comune, prima un po 'di fondo su un approccio transazionale che si sta tentando:

transazioni dati sono stati modellati contabilità in partita doppia - un singolo di credito e un corrispondente di debito dovevano essere registrati insieme o per niente. Man mano che le transazioni diventano più grandi di queste diventano sempre più problematiche da implementare correttamente e più difficile da gestire con un fallimento. Quando inizi a portare l'idea di una singola transazione oltre i confini del sistema, molto probabilmente ti stai avvicinando. Può essere fatto, ma richiede coordinatori di transazioni complessi e necessariamente con latenza più elevata. Ad una certa scala le transazioni sono la mentalità sbagliata, e il risarcimento inizia ad avere molto più senso.

Qui è dove si torna indietro e si guarda a ciò che l'azienda effettivamente fa. Una singola grande transazione non è probabilmente il modo in cui gli uomini d'affari la vedono. Di solito vedono un passo completato e, in base ai risultati successivi, possono essere necessarie diverse azioni per compensare. Questo è dove l'idea di un flusso di lavoro e il risarcimento entra. Here's one introduction to those concepts

Per esempio, se si ordina un libro da Amazon, probabilmente non "bloccare" il disco, mentre è nel vostro carrello della spesa, o addirittura utilizzare transazioni rigorose per determinare se il libro è ancora disponibile quando l'ordine è confermato. Lo venderanno comunque e lo spediranno quando potranno. Se non sono riusciti a farlo entrare in magazzino entro poche settimane, probabilmente ti invieranno un'e-mail che ti dice che stanno cercando di soddisfare le tue esigenze, e puoi continuare ad aspettare che li ottengano in magazzino, oppure tu puoi cancellare il tuo ordine. Questo è chiamato compensazione ed è necessario in molti processi aziendali reali.

Infine, c'è niente di eccezionale su tutto ciò. Aspettatevi che questo possa accadere e usate il normale flusso di controllo. Non utilizzare le funzioni di gestione delle eccezioni della lingua qui (good rules for when to throw an exception). Né si dovrebbe fare affidamento sui meccanismi specifici degli strumenti (WCF?) Per vedere o gestire le eccezioni che si verificano all'interno dell'implementazione del servizio. Comunicare i guasti dovrebbe essere una parte normale del contratto dati (contratti di guasto).

Sfortunatamente, con "un modo pulito per gestirlo" non ci sono flag da impostare che si prenderanno cura di esso magicamente, devi continuare a scomporre il problema e gestire tutti i pezzi risultanti. Speriamo che questi concetti ti colleghino con ciò che le altre persone hanno fatto quando hanno a che fare con questo problema.

Sommario:

  • Il tuo problema è superato il concetto di una transazione -> guardare in compensazione del flusso di lavoro.

Buona fortuna -

+0

Ottima risposta! –

0

Un altro modo, in particolare per i sistemi ad alto rendimento, sarebbe quella di utilizzare un design basato su code in cui un'entità di trasformazione sarebbe eseguire operazioni su un oggetto e poi sulla base dei risultati mettere l'oggetto in diverse code per l'elaborazione aggiuntiva da parte di altre entità e quindi proseguire. Ciò ridurrebbe i colli di bottiglia che si presenterebbero a causa di un'ulteriore elaborazione che sarebbe richiesta, ad es. elaborazione dell'ordine per prodotti esauriti

0

Idealmente, la chiamata al tuo servizio web dovrebbe essere così.

List<Person> selectedPeople = ... //fetch list of people 
callLotteryNumberWebService(selectedPeople.ToArray); 

Effettuare una chiamata di servizio Web per ogni persona è costoso. Alla fine del server, è necessario scorrere l'elenco ed eseguire l'operazione. Il codice lato server può generare 2 eccezioni: BulkOperationFailedException - se si verifica un errore irreversibile dovuto a db down o al file di configurazione mancante. Ulteriore elaborazione non possibile. BulkOperationException - questo contiene una serie di eccezioni relative a una persona. È possibile mantenere alcuni ID per fare riferimento in modo univoco a ciascun oggetto. Il codice sarà simile a questo:

List<Person> selectedPeople = ... // fetch list of people 

try{ 
    callLotteryNumberWebService(selectedPeople.ToArray); 
}catch (BulkOperationFailedException e) { 
    SOP("Some config file missing/db down.No person records processed") 
}catch(BulkOperationException e) { 
    UserDefinedExceptions us = e.getExceptions() 
    foreach(exception ein us) { 
     // read unique id to find which person object failed 
    } 
} 

construct msg based on which personobject succeeded and which failed 

funzionamento è considerato successo quando non vengono generate eccezioni. È possibile avere codici di errore personalizzati per errori invece di utilizzare eccezioni UserDefined. La creazione di BulkOperationException sul lato server è complicata. In secondo luogo è necessario classificare gli errori lanciati dal lato server in BulkOperationFailedException e BulkOperationException. Era così che avevo gestito uno dei miei progetti