Servizi transazionali semplici e compositi: domande sulla separazione di preoccupazioni e transazioni

4

Credo di conoscere la risposta a questo, ma sto cercando eventuali buchi o qualsiasi cosa possa mancare.

Questo è focalizzato su Spring e Java, ma potrebbe davvero applicarsi a qualsiasi stack di programmazione.

Ad ogni modo, abbiamo un tipico livello di servizio annotato con @Transactional in primavera. Ad esempio, potremmo avere:

EmailService
OrderService
HistoryService

Ora l'utente esegue un'azione che chiama un servizio RESTful che svolge le seguenti operazioni:

orderService.create(....);    // wrapped in @Transactional
historyService.create(....);  // wrapped in @Transactional
emailService.emailUser(....); // wrapped in @Transactional (also annotated with @Async)

Mentre tutti questi sono chiamati da un controller (che NON è @Transactional), se il servizio di ordine o il servizio di cronologia falliscono, voglio che entrambi vengano sottoposti a rollback e che il servizio di posta elettronica venga interrotto.

Non mi piace mescolare i servizi. Penso che sarebbe brutto che il servizio ordini chiamasse il servizio di cronologia solo in modo che entrambi si trovassero negli stessi limiti di transazione.

Il mio primo istinto è stato quello di creare un servizio ibrido che avvolgesse entrambi i servizi. Qualcosa come:

@Transactional
public void orderEntryService.create(....) {
    orderService.create(....);    // STILL wrapped in @Transactional
    historyService.create(....);  // STILL wrapped in @Transactional
}

In questo modo, il mio controller potrebbe essere:

public String create(...) {
    orderEntryService.create(...);

    emailService.emailUser(...);    // this is @Async 
                                    // and will never be called if previous 
                                    // orderEntryService.create fails
}

Anche se penso che questo mantenga il mio livello di servizio più pulito, può iniziare rapidamente a sommarsi a classi di servizi "aggregati" ingombranti e smemorati. Come gestirlo?

    
posta cbmeeks 26.11.2014 - 21:41
fonte

2 risposte

2

Comprendo i tuoi dubbi, perché anche questi mix di servizio non mi sembrano naturali. Ma perché? A rigor di termini, anche nella completa architettura SOA la composizione del servizio non è proibita. Ma il tuo caso è diverso. I tuoi servizi non sono unità indipendenti situate in processi propri con gestione delle transazioni indipendente. I tuoi servizi sono di livello nella tua architettura a processo singolo, più i metodi dei tuoi servizi sono racchiusi nelle transazioni del database. Quindi, la composizione del servizio potrebbe portare a transazioni nidificate che, di per sé, non rappresentano un problema per Spring, ma per me appare ancora innaturale. Inoltre, una tale composizione potrebbe portare a dipendenze circolari tra i tuoi servizi, che è il male senza dubbio.

Potresti risolvere il tuo problema aggiungendo un altro livello (diciamo DataAccess) nella tua architettura, che conterrà repository: EmailRepository, OrderRepository, HistoryRepository. Qui troverai i metodi per la gestione di e-mail, ordini, cronologia, ecc.: Aggiunta, aggiornamento, eliminazione, interrogazione. E potresti condividere questi repository nei tuoi diversi servizi, disponendo di wrapper di transazione dove sono ora - attorno ai tuoi servizi.

È semplicemente la soluzione più semplice che potrebbe aiutarti, non parlando di approcci più sofisticati come il DDD.

    
risposta data 28.11.2014 - 12:37
fonte
1

Non ho una risposta definitiva, ma qui ci sono i miei punti:

A favore:

  • Non mi piace la logica di business nel controller . Hanno già un sacco di codice boilerplate relativo alla vista, al formato, alle trasformazioni, alle associazioni, alla mappatura, ecc. Tre chiamate con la propria gestione degli errori (e potrebbero essere alcuni casi speciali) possono diventare abbastanza complesse da portare bug. La logica di come creare un ordine potrebbe essere abbastanza complessa da essere separata da come la chiami.
  • I controller sono difficili da testare . I servizi sono facili. Evitate di deridere il WebApplicationContext, il json de / serialization, la gestione degli errori del controller, SpringSecurityFilterChain, ecc. Potreste persino dover prendere in giro l'autorizzazione. Naturalmente cercherete di testare il controller in ogni caso, ma quelli sono test più pesanti che si concentrano sulla comunicazione tra server e client. Con localizzazione e input santiation hanno abbastanza complessità.

  • Attività che devono essere eseguite a livello transazionale . Diciamo che è necessario eseguire due servizi: shipGoods e chargeCreditCard . shipGoods viene eseguito correttamente e quindi chargeCreditCard non riesce. Si desidera ripristinare l'ordine di spedizione. Alternativa: è possibile creare un livello "componente" tra DAO e servizio quando viene chiamata solo la logica aziendale completa nei servizi. E tu non vuoi DAO transactionall.

  • Consenti transazioni più piccole quando non hai bisogno di una grande . Nel caso di opossite potresti chiamare molta funzionalità come servizi di ricerca o chiamate di servizi web. Se non è necessario eseguire il rollback di una transazione, è consigliabile eseguirla il prima possibile. Le transazioni aperte possono bloccare i record del database (o anche le tabelle complete) riducendo le prestazioni. Esempio: aggiornare l'entità nel database, chiamare il servizio web remoto lento aggiungere creare un'entità diversa. In un grande approccio di transizione l'entità non sarà impegnata fino alla fine dell'ultima creazione e nessuno sarà in grado di aggiornare il record originale.

  • Riutilizzo del codice : è difficile riutilizzare il codice senza servizi che chiamano altri servizi. Diciamo che hai un metodo "chargeCreditCard" molto testato. Potresti riutilizzarlo anche nei casi in cui "shipGoods" è cambiato in "preorderItem". Se si considera l'invio di un servizio di posta elettronica (e sembra così giudicare da emailService), allora sembra che "forgotPasswordService", "newsLetterService", "shippingService" possano davvero beneficiare del riutilizzo della funzionalità.

Contro:

  • Transazioni complesse : se crei una grande transazione che include i tre servizi, dovrai pensare a cosa succede quando ognuno fallisce. Nothings proibisce la creazione di un servizio non transazionale che chiama quelli transazionali. Questo è il caso facile. Le transazioni nidificate e le transazioni autonome possono essere potenti ma difficili e pericolose.
  • Complessità : puoi terminare con molti servizi che chiamano servizi di chiamata servizi. Hai davvero una logica di business così complessa? Attenzione alla creazione di servizi come DAO transazionali con solo operazioni CRUD ma quasi nessuna logica: Ad esempio: OrderService , HistoryService . In questo caso puoi considerare che OrderService dovrebbe chiamare historyDAO e includere le cronologie automaticamente quando crei un ordine. (Evita il Antipattern del modello di dominio anemico ).
risposta data 21.01.2016 - 20:24
fonte

Leggi altre domande sui tag