Come creare un codice OO migliore in un'applicazione basata su database relazionale in cui il database non è progettato correttamente


19

Sto scrivendo un'applicazione Web Java che consiste principalmente in un gruppo di pagine simili in cui ogni pagina ha diverse tabelle e un filtro che si applica a quelle tabelle. I dati su queste tabelle provengono da un database SQL.

Sto usando myBatis come ORM, che potrebbe non essere la scelta migliore nel mio caso, dal momento che il database è mal progettato e mybatis è uno strumento più orientato al database.

Sto scoprendo che sto scrivendo un sacco di codice duplicato perché, a causa della cattiva progettazione del database, devo scrivere query diverse per cose simili in quanto quelle query possono essere molto diverse. Cioè, non posso facilmente parametrizzare le query. Questo si propaga nel mio codice e invece di popolare righe su colonne nella mia tabella con un semplice ciclo ho un codice come:

ottenere i dati A (p1, ..., pi);

ottenere i dati B (p1, ..., pi);

ottenere C dati (p1, ..., pi);

ottenere i dati D (p1, ..., pi); ...

E questo esplode presto quando abbiamo diverse tabelle con colonne diverse.

Aggiunge anche alla complessità il fatto che sto usando "wicket", che è, in effetti, una mappatura di oggetti su elementi html nella pagina. Quindi il mio codice Java diventa un adattatore tra il database e il front-end, il che mi fa creare un sacco di cavi, codice boilerplate con qualche logica mescolata in esso.

La soluzione corretta sarebbe quella di avvolgere i mapper ORM con uno strato esterno che presenta un'interfaccia più omogenea al db o esiste un modo migliore per gestire questo codice spaghetti che sto scrivendo?

EDIT: maggiori informazioni sul database

Il database contiene principalmente informazioni sulle telefonate. Il design scadente è costituito da:

Tabelle con un ID artificiale come chiave primaria che non ha nulla a che fare con la conoscenza del dominio.

Nessun unico, trigger, assegni o chiavi esterne di sorta.

Campi con un nome generico che corrispondono a concetti diversi per record diversi.

Record che possono essere classificati solo incrociando altre tabelle con condizioni diverse.

Colonne che dovrebbero essere numeri o date memorizzati come stringhe.

Per riassumere, un design disordinato / pigro tutt'intorno.


7
Correggere il design del database è un'opzione?
RMalke,

1
Spiegare come è mal progettato il database.
Tulains Córdova,

@Renan Malke Stigliani Sfortunatamente no, poiché esiste un software legacy che dipende da questo, tuttavia ho rispecchiato alcuni dei tavoli con un design leggermente diverso e li ho popolati, il che semplifica il codice. Tuttavia non ne vado fiero e preferirei non duplicare le tabelle indiscriminatamente
DPM,

1
Questo libro potrebbe darti alcune idee su come iniziare a risolvere il problema della base di dati e far funzionare il codice legacy: amazon.com/…
HLGEM

4
La maggior parte dei problemi che elenchi. . . non lo sono. L'uso di chiavi surrogate piuttosto che di chiavi naturali è in realtà una raccomandazione piuttosto standard al giorno d'oggi; per niente "design scadente". La mancanza di vincoli e l'uso di tipi di colonna inappropriati è un esempio migliore per quanto riguarda la "cattiva progettazione", ma non dovrebbe in effetti influire sul codice dell'applicazione (a meno che non si preveda di abusare di questi problemi?).
Ruakh,

Risposte:


53

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' Objectastrazione 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).


4
Caspita, che risposta straordinariamente completa. Voterei questo più di una volta se solo SE me lo permettesse.
Marjan Venema,

11
Quando uscirà la versione del film?
yannis,

3
@JimmyHoffa Bravo signore !!! Aggiungerò questa risposta ai segnalibri e mostrerò a mia figlia quando invecchia.
Tombatron,

4

Il mio consiglio:

Creare viste del database che:

  1. Dai nomi significativi alle colonne
  2. Fai "l'incrocio con altri tavoli con condizioni diverse" in modo da poter nascondere quella complessità.
  3. Converti numeri o date memorizzati come stringhe in numeri e date rispettivamente.
  4. Crea unicità dove non ce n'è, secondo alcuni criteri.

L'idea è quella di creare una facciata che emuli un design migliore sopra a quello cattivo.

Quindi fai in modo che l'ORM si colleghi a quella facciata anziché ai tavoli reali.

Ciò non semplifica però gli inserimenti.


Usare le viste del database sembra un'ottima idea e il modo più elegante di fare azioni per sottrarre la bruttezza al livello più basso, per qualche ragione che non avevo considerato. Grazie.
DPM,

3

Posso vedere come il tuo schema di database esistente ti induca a scrivere codice e query più specifici per attività che potrebbero altrimenti essere astratte con uno schema meglio progettato, ma non dovrebbe ostacolare la tua capacità di scrivere un buon codice orientato agli oggetti.

  • Ricorda i principi SOLIDI .
  • Scrivi un codice che può essere facilmente testato dall'unità (che spesso viene seguito seguendo i principi SOLID).
  • Mantieni la tua logica aziendale separata dalla logica di visualizzazione.
  • Leggi la documentazione di Apache Wicket e gli esempi di : questo framework può probabilmente farti risparmiare più codice di quanto pensi, quindi impara a usarlo in modo efficace.
  • Mantieni la logica che ha a che fare con il database in un livello separato che fornisce un'interfaccia pulita con cui la tua logica aziendale può funzionare. In questo modo, se tu (o un futuro manutentore) hai mai la possibilità di migliorare lo schema, possono farlo senza troppe modifiche alla logica aziendale.

Quando ti ritrovi a lavorare con uno schema di database che non è perfetto, è facile capire tutti i modi in cui rende il tuo lavoro più difficile, ma a un certo punto devi mettere da parte quei reclami e trarne il meglio.

Pensala come un'opportunità per usare la tua creatività per scrivere codice pulito, riutilizzabile e facilmente gestibile nonostante lo schema imperfetto.


1

Rispondendo alla tua domanda iniziale su un migliore codice orientato agli oggetti, suggerirei di usare oggetti che parlano SQL . L'ORM va intrinsecamente contro i principi orientati agli oggetti, poiché opera su un oggetto e l'oggetto in OOP è un'entità autosufficiente, che ha tutte le risorse per raggiungere il suo obiettivo. Sono sicuro che questo approccio potrebbe semplificare il tuo codice.

Parlando di spazio problematico, cioè del tuo dominio, proverei a identificare le radici aggregate . Questi sono i limiti di coerenza del tuo dominio. Limiti che devono assolutamente mantenere la sua coerenza in ogni momento. Gli aggregati comunicano tramite eventi di dominio. Se hai un sistema abbastanza grande, probabilmente dovresti iniziare a dividerlo su sottosistemi (chiamalo SOA, Microservice, Sistemi autonomi, ecc.)

Considererei anche l'uso di CQRS: può semplificare notevolmente sia il tuo lato di scrittura che quello di lettura. Assicurati di leggere l' articolo di Udi Dahan su questo argomento.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.