Propagazione dei dati tra componenti

1

Spesso ho componenti (servizi) relativamente contenuti che fanno cose specializzate. Questi sono anche quasi sempre immutabili per rendere il loro cambiamento meno incline agli errori. Nota che il sotto è all'interno di un progetto relativamente grande, non di piccoli servizi in processi separati, ma che non cambia molto.

Prendi questo sistema come esempio:

  1. Input: pacchetto di cartone che contiene libri rossi
  2. Lavorazione
    1. Prendi un libro
    2. Esegui alcune elaborazioni complesse come:
      1. Se il libro ha meno di 50 pagine, rifiuta con il messaggio "Troppo corto"
      2. Altrimenti, leggi le pagine 15, 21 e 38
      3. Trova tutte le parole che iniziano con X alle pagine 15 e 21
      4. Se abbiamo più X-words a pagina 15, trova il numero di occorrenze della parola "play" a pagina 38, altrimenti leggi l'ultima parola su quella pagina. Fai che sia A
      5. Scrivi un nuovo libro in cui cambi tutte le parole "alcuni" in A. Se non ci sono parole "alcune", invia un messaggio di avviso "Nessuna parola trovata trovata"
      6. Stampa e colora le copertine blu
  3. Output: pacchetto plastico che ora contiene libri blu e vari messaggi di rifiuto o di avviso, se necessario

Considera che ora disponiamo di molti servizi che eseguono operazioni nella parte 2.2 precedente. Per esempio. ci sarebbe un servizio che scrive un libro nel passaggio 2.2.5.

Considera anche che i messaggi di rifiuto o di avvertimento devono essere propagati attraverso tutti i servizi. Nel caso semplice sopra riportato che potrebbe non sembrare tanto, ma considera che questo è di solito molto più profondo dell'esempio sopra. Per esempio. potremmo avere qualcosa di simile:

[one thread]
book service
-> chapter service
--> title service
--> paragraph service
---> sentence service
----> word service
--> footnote service

[another thread]
printing service
-> font chooser service
-> cover coloring service

Ciò significa che o manteniamo lo stato globale (yuck) o creiamo miliardi di oggetti separati (SentenceServiceOutput, PrintingServiceOutput, ...) e facciamo in modo che ognuno dei servizi restituisca tutti i pezzi, cioè il libro di scrittura restituirebbe (book, reject messages, warning messages) tuple e quindi collegali come necessario in alcuni servizi parent.

La seconda soluzione presenta alcuni inconvenienti:

  • Inquina il codice in modo significativo e rende molto difficile la lettura e la tracciabilità. Per esempio. invece di un servizio (2.2.3 sopra) che restituisce un elenco di parole trovate, restituirebbe un oggetto ([words], [reject message], [warning message])
  • Rende il codice significativamente più lungo e più complicato - praticamente ovunque abbiamo [words] ora dobbiamo decomprimere ([words], [reject message], [warning message]) , cambiare un pezzettino e poi reimballare in un nuovo ([words], [reject message], [warning message]) .
  • Tutto ciò rende molto più difficile mantenere la struttura complessiva del progetto nella tua testa, poiché il numero di nomi (ad esempio i nomi delle classi) aumenta drasticamente e a causa del loro numero di nomi si allunga (non puoi chiamarlo solo Output , dato che mescolerai SentenceServiceOutput e FontChoosingServiceOutput ), rendendo il codice ancora più lungo e difficile da leggere
  • Anche se rende ancora più semplice ragionare su pezzi più piccoli rispetto all'utilizzo dello stato globale, rende le modifiche al codice notevolmente più lente e difficili da controllare, testare & ad esempio

Esistono modelli che consentano di mitigare uno o tutti i problemi sopra riportati?

    
posta levant pied 04.08.2018 - 00:11
fonte

1 risposta

0

Quindi, se ho capito bene, hai dati (rifiuti e avvertenze) che possono accumularsi mentre i tuoi servizi gestiscono una richiesta. In qualche modo dovrai rendere disponibile una sorta di struttura dati con ambito richiesta ai tuoi servizi in cui possono inserire i loro messaggi.

Come hai eloquentemente indicato te stesso, lo stato globale sembra una brutta soluzione a questo problema.

Contesto

In sostanza, vedo due approcci generali.

Opzione A Hai una struttura di dati di contesto generica accettata da tutti i tuoi servizi. Ciò significa anche che hai un formato di messaggio comune ed è responsabilità del servizio in qualche modo corrispondere a quel formato.

Opzione B Ogni servizio definisce la propria interfaccia di contesto. Questo sposta il problema di conformarsi a un formato di messaggio fisso dal servizio a un'implementazione di contesto che deve in qualche modo gestire tutti i diversi tipi di messaggi.

Se le funzionalità che il contesto deve fornire ai servizi sono relativamente simili, stabili e hanno un'interfaccia abbastanza semplice (ad esempio aggiungi questa stringa di messaggi con questo id di servizio a un elenco), allora penso che fare i servizi dipenda dal l'oggetto contesto è l'approccio migliore. Quindi, sceglierei l'opzione A.

Nell'opzione B, il contesto del servizio deve dipendere da tutti i servizi (che non è eccezionale) e la parte della logica specifica del servizio che si occupa della gestione dei messaggi è ora in un altro modulo. Potrebbe essere ancora meglio che forzare i servizi a utilizzare un oggetto di contesto che semplicemente non funziona per loro, ad es. perché hanno bisogno di fare cose molto diverse. Il vantaggio è che i servizi non hanno alcuna dipendenza aggiuntiva che li lega tutti insieme.

Se è possibile portare i messaggi nello stesso formato, ma richiede una logica complessa, è possibile combinare l'opzione A e l'opzione B: si ha un'implementazione generica del contesto e si utilizzano adattatori tra i messaggi dei servizi e il generico Messaggio. In questo modo puoi mantenere quella logica più vicina al servizio ed evitare comunque l'accoppiamento e mantenere l'interfaccia di contesto pulita.

Flusso di controllo

Mentre si accumulano i messaggi di rifiuto e di avviso nell'oggetto contesto, i dati effettivi che vengono manipolati e su cui si prendono le decisioni del flusso di controllo non dovrebbero farne parte.

Idealmente, i servizi non dovrebbero mai dover interrogare il contesto, basta aggiungere ad esso. Altrimenti, stai nascondendo importanti input / output nell'oggetto contesto, oscurando l'interfaccia del servizio.

Usa tipi di opzioni, tuple o qualsiasi altra cosa che ti serve per mantenere i dati e le informazioni rilevanti del flusso di controllo nei tuoi parametri e valori di ritorno. Se il tuo flusso di controllo dipende da un motivo di rifiuto, codificalo separatamente per il messaggio di rifiuto.

Note finali

  • Mantenere i messaggi separati dagli input / output dovrebbe tenere le firme facili da capire, una volta che hai dato un'occhiata all'interfaccia di contesto (si spera semplice). Rendere la sola interfaccia di contesto in sola scrittura dovrebbe inoltre chiarire che non contiene alcun input.

  • Se non utilizzi un'interfaccia comune di context , potresti finire con AbcContext , XyzContext . Molto probabilmente, lo farai perché sono diversi in modi importanti. Eventuali complessità inerenti al dominio finiranno per apparire nel tuo codice. Un buon naming (e namespace), oltre a una buona struttura (che sembra avere) aiuta a mantenere le cose gestibili.

  • Mantenere le cose facili da capire a livello locale dovrebbe aiutare a mantenerle facilmente comprensibili a livello globale. Questo è il motivo per cui vuoi esprimere chiaramente input e amp; uscite del tuo servizio. Ciò rende anche più facile il test (non sono sicuro di come lo stato globale potrebbe mai esserci d'aiuto)

  • Se i tuoi servizi sono intrinsecamente gerarchici, può valere la pena considerare una gerarchia di contesti corrispondente.

P.S .: Ti dispiacerebbe se ti spedissi un pacco con i libri rossi? Quei libri blu sembrano davvero interessanti ...; -)

    
risposta data 11.08.2018 - 12:28
fonte

Leggi altre domande sui tag