L'orientamento agli oggetti è prezioso in particolare perché si presentano questi tipi di scenari e offre strumenti per progettare ragionevolmente astrazioni che consentono di incapsulare la complessità.
La vera domanda qui è, dove incapsuli quella complessità?
Vorrei quindi fare un passo indietro e parlare a quale "complessità" mi riferisco qui. Il tuo problema (come ho capito; correggimi se sbaglio) è un modello di persistenza che non è un modello efficacemente utilizzabile per le attività che devi completare con i dati. Può essere efficace e utilizzabile per altre attività, ma non per le tue attività.
Quindi cosa facciamo quando disponiamo di dati che non presentano un buon modello per i nostri mezzi?
Tradurre. È l'unica cosa che puoi fare. Tale traduzione è la "complessità" a cui mi riferisco sopra. Quindi ora che accettiamo di tradurre il modello, dobbiamo decidere un paio di fattori.
Dobbiamo tradurre entrambe le direzioni? Le due direzioni verranno tradotte allo stesso modo, come in:
(Tbl A, Tbl B) -> Obj X (leggi)
Obj X -> (Tbl A, Tbl B) (scrivere)
oppure le attività di inserimento / aggiornamento / eliminazione rappresentano un diverso tipo di oggetto in modo tale da leggere i dati come Obj X, ma i dati vengono inseriti / aggiornati da Obj Y? Quale di questi due modi in cui desideri andare o se non è possibile aggiornare / inserire / eliminare sono fattori importanti in cui desideri inserire la traduzione.
Dove traduci?
Torna alla prima affermazione che ho fatto in questa risposta; OO ti consente di incapsulare la complessità e ciò a cui mi riferisco qui è il fatto che non solo dovresti, ma devi incapsulare quella complessità se desideri assicurarti che non fuoriesca e penetri in tutto il tuo codice. Allo stesso tempo, è importante riconoscere che non puoi avere un'astrazione perfetta, quindi preoccupati meno di quello che di averne uno molto efficace e utilizzabile.
Ancora adesso; il tuo problema è: dove metti questa complessità? Bene hai delle scelte.
Puoi farlo nel database usando le procedure memorizzate. Questo ha l'inconveniente di non giocare molto bene con gli ORM ma non è sempre vero. Le procedure memorizzate offrono alcuni vantaggi, tra cui le prestazioni spesso. Tuttavia, le procedure memorizzate possono richiedere molta manutenzione, ma spetta a te analizzare il tuo scenario particolare e dire se la manutenzione sarà più o meno rispetto ad altre scelte. Personalmente sono molto abile con le procedure memorizzate e come tale questo talento disponibile riduce le spese generali; mai sottovalutare il valore di prendere decisioni in base a ciò che fai sapere. A volte la soluzione non ottimale può essere più ottimale della soluzione corretta perché tu o il tuo team sapete come crearla e mantenerla meglio della soluzione ottimale.
Un'altra opzione nel database sono le viste.A seconda del server di database, questi possono essere altamente ottimali o non ottimali o addirittura non efficaci, uno degli svantaggi può essere il tempo di query a seconda delle opzioni di indicizzazione disponibili nel database. Le viste diventano una scelta ancora migliore se non hai mai bisogno di apportare modifiche ai dati (inserisci / aggiorna / cancella).
Superando il database si ha il vecchio standby di utilizzare il modello di repository. Questo è un approccio testato nel tempo che può essere molto efficace. Gli svantaggi tendono a includere la piastra della caldaia, ma i repository ben ponderati possono evitare una certa quantità di questo, e anche quando questi si traducono in quantità sfavorevoli della piastra della caldaia, il repository tende ad essere un codice semplice che è facile da capire e mantenere oltre a presentare una buona API /astrazione. Anche i repository possono essere utili per la loro testabilità dell'unità che si perde con le opzioni nel database.
Esistono strumenti come l'auto-mapper là fuori che possono rendere plausibile l'uso di un ORM in cui possono fare la traduzione tra il modello di database da orm a modelli utilizzabili, ma alcuni di questi strumenti possono essere difficili da mantenere / comprendere comportandosi più come per magia; sebbene creino un minimo di codice ambientale con conseguente riduzione dei costi di manutenzione quando ben compreso.
Successivamente stai uscendo sempre più dal database , il che significa che ci saranno maggiori quantità di codice che affronteranno il modello di persistenza non tradotto, che sarà davvero spiacevole. In questi scenari parli di mettere il livello di traduzione nella tua UI, che sembra che tu stia facendo ora. Questa è generalmente una pessima idea e decade nel tempo.
Ora cominciamo a parlare pazzi .
L' Object
astrazione non è l'unica fine che esiste. C'è stata una profondità di astrazioni sviluppate nel corso dei molti anni in cui l'informatica è stata studiata e anche prima di allora dallo studio della matematica. Se inizieremo a diventare creativi, iniziamo a parlare delle astrazioni conosciute disponibili che sono state studiate.
C'è il modello dell'attore.Questo è un approccio interessante perché dice che tutto ciò che fai è inviare messaggi ad altro codice che deleghi effettivamente tutto il lavoro a quell'altro codice, che è molto efficace nell'incapsulare la complessità lontano da tutto il tuo codice. Questo potrebbe funzionare nella misura in cui invii un messaggio a un attore che dice "Ho bisogno che Obj X sia inviato a Y" e hai un ricettacolo in attesa di una risposta nella posizione Y che quindi elabora Obj X. Puoi persino inviare un messaggio che indica "Ho bisogno di Obj X e calcolo Y, Z fatto" e quindi non devi nemmeno aspettare; la traduzione avviene dall'altra parte di quel messaggio e puoi semplicemente andare avanti se non hai bisogno di leggere il risultato. Questo può essere un leggero abuso del modello dell'attore per i tuoi scopi, ma tutto dipende;
Un altro limite di incapsulamento sono i confini del processo. Questi possono essere usati per separare la complessità in modo molto efficace. È possibile creare il codice di traduzione come servizio Web in cui la comunicazione è semplice HTTP, utilizzando SOAP, REST o se si desidera davvero il proprio protocollo (non consigliato). STOMP non è del tutto un cattivo protocollo più recente. Oppure utilizza un normale servizio daemon con una pipe di memoria pubblicizzata locale del sistema per comunicare di nuovo molto rapidamente utilizzando qualsiasi protocollo tu scelga. Questo in realtà ha alcuni vantaggi piuttosto buoni:
- È possibile eseguire più processi che eseguono la traduzione per il supporto della versione precedente e più recente allo stesso tempo, consentendo di aggiornare il servizio di traduzione per pubblicizzare un modello a oggetti V2, e quindi separatamente in un secondo momento aggiornare il codice che consuma per lavorare con il nuovo oggetto modello.
- Puoi fare cose interessanti come fissare il processo a un core per le prestazioni, puoi anche ottenere una quantità di sicurezza di sicurezza in questo approccio rendendo l'unico processo in esecuzione con i privilegi di sicurezza di toccare quei dati.
- Otterrai un limite molto forte quando parli di limiti di processo che rimarranno fissi garantendo una perdita minima dell'astrazione per lungo tempo perché la scrittura di codice nello spazio di traduzione non potrà essere chiamata al di fuori dello spazio di traduzione poiché non condividerà l'ambito del processo, garantendo un set fisso di scenari di utilizzo per contratto.
- Possibilità di semplificare gli aggiornamenti asincroni / non bloccanti.
Gli svantaggi sono ovviamente più manutenzioni di quanto sia comunemente necessario, le spese generali di comunicazione influiscono sulle prestazioni e sulla manutenzione.
Esiste una grande varietà di modi per incapsulare la complessità che può consentire a quella complessità di essere collocata in luoghi sempre più strani e curiosi nel tuo sistema. Usando forme di funzioni di ordine superiore (spesso volte simulate usando il modello di strategia o varie altre forme strane di modelli di oggetti), puoi fare alcune cose molto interessanti.
Esatto, iniziamo a parlare di una monade. È possibile creare questo livello di traduzione in modo molto indipendente di piccole funzioni specifiche che eseguono le traduzioni indipendenti necessarie, ma nascondono tutte quelle funzioni di traduzione non visibili in modo che siano difficilmente accessibili al codice esterno. Ciò ha il vantaggio di ridurre la dipendenza da essi, consentendo loro di cambiare facilmente senza influenzare molto codice esterno. Quindi si crea una classe che accetta funzioni di ordine superiore (funzioni anonime, funzioni lambda, oggetti di strategia, tuttavia è necessario strutturarle) che funzionano su uno dei simpatici oggetti del tipo di modello OO. Quindi si lascia che il codice sottostante che accetta quelle funzioni esegua l'esecuzione letterale usando i metodi di traduzione appropriati.
Questo crea un confine in cui tutta la traduzione non esiste solo dall'altra parte del confine lontano da tutto il codice; è usato solo da quel lato consentendo al resto del codice di non sapere nulla al di fuori di dove si trova il punto di ingresso per quel confine.
Ok, sì, sta davvero parlando di matti, ma chi lo sa; potresti essere semplicemente pazzo (sul serio, non intraprendere monadi con un livello di follia inferiore all'88%, c'è il rischio reale di lesioni fisiche).