Costruire un database di astrazione

3

Lo scenario

Ho un progetto ASP classico con un back-end di SQL Server che devo portare su ASP.NET MVC.

Il sito legacy fa un uso liberale dell'analisi della definizione dello schema nell'applicazione stessa. Ad esempio, per eseguire un determinato report e consentire all'utente di fornire un ordinamento, l'applicazione leggerà lo schema e fornirà un elenco di campi basati su quello schema che l'utente può quindi scegliere (hey, NON il mio disegno!)

Questo fornisce uno scenario interessante. La migrazione alla nuova applicazione verrà eseguita per "Strangler Application" coniata da Fowler (costruiremo le funzionalità replicate pezzo per pezzo e lentamente la sposteremo). Tuttavia, insieme a questo ci sono alcuni requisiti per nuove funzionalità, che significano nuovi campi e nuove tabelle. A causa di ciò, sono riluttante ad andare semplicemente a lanciare nuovi campi nei tavoli esistenti, perché non voglio rovinare nulla nell'app legacy. Naturalmente, a causa del percorso di migrazione, dobbiamo essere in grado di utilizzare entrambi i sistemi contemporaneamente per un certo tempo.

Inoltre, la denominazione del database dell'app precedente è abbastanza orribile, compreso qualsiasi numero di parole riservate e caratteri speciali e così via. Inoltre, alcuni nomi di tabelle sono completamente non semantici (ad esempio, la loro tabella "Membri" primaria viene in effetti chiamata dopo il loro primo e principale client da circa 20 anni).

I miei pensieri

Quindi, abbiamo un database, chiamiamolo LegacyDB Questo non può cambiare. Eppure abbiamo bisogno di aggiungere nuove funzionalità per il nuovo sito. Il mio pensiero era di creare un secondo database, chiamandolo NewDB . Questo conterrà tutti / tutti i "nuovi" dati. Manterrei una relazione pseudo 1: 1 tra le tabelle in LegacyDB e "NewDB" nell'ID di ogni tabella.

Ciò che mi piacerebbe fare ora è creare una sorta di database di astrazione su entrambi i quali la nuova applicazione interagirà. Questo riceverebbe una query come SELECT blah FROM dbo.Members WHERE blah = blah e interrogherebbe sia da LegacyDB sia dai nuovi campi richiesti da NewDB . Allo stesso modo, tutte le altre operazioni CRUD contro il db "astrazione" o "facciata" db (per mancanza di un termine migliore) funzionerebbero allo stesso modo. Sto pensando che alcune visualizzazioni accuratamente elaborate potrebbero essere utilizzate per gestirlo.

In questo modo l'app legacy esistente poteva usare LegacyDB senza dover eseguire alcuna rielaborazione importante in quel pasticcio e la nuova applicazione avrebbe un database con nome ben funzionante e nuova funzione abilitato con cui lavorare senza dover gestire repliche tra due database separati.

Una volta che l'app legacy è stata completamente eliminata, la LegacyDB e la NewDB verrebbero unite e teoricamente avranno luogo il db dell'astrazione / facciata senza richiedere modifiche a livello di applicazione.

Essenzialmente quello che sto pensando sono le viste di livello superiore nella facciata db che a loro volta estraggono i dati dai database appropriati. Se l'applicazione invia la seguente query alla facciata:

SELECT Field1, Field2, Field3 FROM ThisTable

La facciata avrebbe ThisTable definita come una vista:

CREATE VIEW ThisTable AS (
    SELECT 
        a.LegacyField1 AS Field1, 
        a.LegacyField2 AS Field2, 
        b.NewField3 AS Field3
    FROM legacyDb.dbo.BadTableName AS a
    INNER JOIN newDb.dbo.GoodTableName AS b ON a.ID = b.ID
);

(per gli aggiornamenti, non ho ancora elaborato la fattibilità - una delle ragioni che sto chiedendo qui)

Le mie domande

Questa è un'idea sconvolta? Qualcuno l'ha provato? C'è un nome comune per questo approccio (se è anche un approccio comune). C'è qualche riferimento che posso trovare per i pro / contro / gotchyas? Sarei molto grato per una certa esperienza da cui nutrire prima di iniziare l'impresa.

    
posta jleach 13.07.2016 - 19:52
fonte

2 risposte

0

Ho deciso di non farlo. Dopo non poche revisioni e prove di concept test, ho determinato che non è abbastanza solido su vari livelli di DML, ovvero prestazioni, facilità di sviluppo e interferenze con altri possibili trigger.

Il problema principale a cui mi sono imbattuto era il tentativo di ottimizzare le prestazioni. Mentre l'applicazione principale è per lo più scritture a riga singola, esistono numerosi componenti che possono eseguire anche aggiornamenti di massa e questi trigger di INSTEAD OF dovrebbero gestire tutti i casi allo stesso modo. Questo di per sé non presentava troppi problemi, fino alla possibilità che una riga di "estensione" non esistesse ancora, nel qual caso dovevo a) controllare ogni riga su base RBAR ed eseguire INSERT o UPDATE di conseguenza oppure usa MERGE.

Qualunque cosa RBAR su questo ampio contesto è un no-go, e anche se non ho qualche particolare scrupolo con MERGE, non mi sento a mio agio ad usarlo su tutta la linea in questo modo (specialmente considerando che le tabelle di base essi stessi possono avere trigger di tipo audit su di essi, che MERGE non gestisce abbastanza bene come dovrebbe: link )

Quindi, mi manca un modo per garantire che qualsiasi operazione DML funzionerà allo stesso modo del DML diretto.

L'altro motivo per cui ho deciso contro questo approccio è perché in molti casi sembrerebbe che dovrò scrivere codice specifico piuttosto che codice generico per gestirli. Al contrario, i miei trigger di controllo DML sono molto generici e una singola procedura viene utilizzata per "collegarli" a una tabella. Quindi, se ho bisogno di qualcosa, ovunque, da qualsiasi tavolo, so anche esattamente come è stato impostato senza dover esaminare i dettagli della tabella in questione.

Con INSTEAD OF trigger nelle viste della facciata, non posso farlo. Ogni coppia tabella / vista può avere conversioni di denominazione o altre stranezze specifiche che vengono considerate. Andando avanti nello sviluppo di applicazioni / componenti primari, posso vedere che questo diventa facilmente un grave incubo di manutenzione.

Tutto sommato, se potessi non dovermi preoccupare delle interferenze di prestazioni / fusione e creare un insieme di trigger generici e gestirlo come "aggiunta una tabella, crea la vista ed esegui questo sproc per generare i trigger, o aggiunto un campo, esegui lo sproc per rilasciare e ricrea i trigger ", avrei potuto farlo, ma ahimè, non in questo caso.

Probabilmente ricorrere a un livello interfacciato e mappature del modello di dominio per le cose OOP, che è dove verrà eseguita la maggior parte dello sviluppo dell'applicazione.

    
risposta data 26.07.2016 - 16:46
fonte
1

Nota:

La parte superiore di questa risposta è una risposta alla domanda prima che l'esempio di visualizzazione sia stato pubblicato dal poster originale. Per una versione aggiornata, tenendo presente il punto, controlla la parte inferiore di questa risposta, sotto la linea.

Non mi piace che tu passi una query a un livello di astrazione del database che in realtà copre due database.

Un livello di astrazione che si occupa di SQL è di livello molto basso, l'architettura è prudente e la sua responsabilità è solitamente una sola: unire piccole differenze tra i motori di database (ad esempio LIMIT clausule), convertendo internamente alcune query SQL in una nuova , in base al motore di database che si sta utilizzando.

Questo può essere fatto perché la modifica è in genere molto semplice.

Quello che stai suggerendo è in realtà scrivere una query, che viene in qualche modo trasformata in una query diversa (improvvisamente interrogando database e tabelle diversi). Sì, questo può essere fatto, ma avrete bisogno di una tabella di mappatura complicata (presumo che vorreste fare qualcosa come analizzare la query, decidere quali attributi sono presenti e basare su qualche query logica uno o entrambi i database). Riesco a malapena a vedere come sia efficiente. Trascorrerai ore a scrivere e eseguire il debug di questo livello, quando potresti semplicemente scrivere direttamente la query.

Dovresti decidere dove inizia il livello del database e dove finisce. Una volta fatto ciò, penso che sarebbe più fattibile iniziare a fornire un'astrazione al livello del database, molto probabilmente attraverso interface s, ma queste interfacce non dovrebbero sapere più nulla di SQL, potrebbero contenere metodi semplici come:

List<Members> LoadMembers(int maxAmount);
void SaveMember(Member memberToBeSaved); 

Un'interfaccia che contenga metodi come quelli menzionati sopra sarebbe implementata e contiene l'interrogazione SQL reale su entrambi i database, ma non dovresti preoccupartene. Quello di cui ti dovresti preoccupare è ottenere i dati effettivi, ma non come ottieni.

Ma la classe che implementava l'interfaccia non poteva contenere il livello di astrazione del database che accetta le query che vengono trasformate per interrogare il vecchio o il nuovo database?

Beh, ovviamente, potrebbe, ma non penso che ne valga la pena. Non dal punto di vista del business.

Quello che vuoi è creare un'astrazione che accetti la query comune. L'attuale implementazione di questa astrazione trasformerebbe questa query comune in quella speciale, interrogando non uno ma due database. In futuro vorresti sostituire l'attuale implementazione del database con una nuova, che non trasformerebbe più le query, ma le userebbe direttamente, perché il database avrebbe il formato.

Ci sono pochi problemi con questo approccio:

  • La creazione di un'implementazione ben strutturata della trasformazione richiederà molto tempo, molto più tempo rispetto alla semplice scrittura manuale delle query su entrambi i database e la sostituzione in un secondo momento quando i database verranno uniti.
  • Come fai a sapere come apparirà il database in futuro? Non puoi. Il database potrebbe anche cambiare e dovrai comunque riscrivere la query per adottare la nuova struttura. Quindi ora hai speso una buona somma di denaro per sviluppare una funzione che alla fine non è stata nemmeno utilizzata.

Quindi, mentre è ovviamente possibile avere un tale livello, a mio parere non ne vale la pena. È troppo lavoro per qualcosa che non porta molto.

Ora che hai introdotto la vista, in realtà ha più senso. In un certo senso, creando la vista si sta scrivendo la query manualmente, come ho suggerito, ma inserendola direttamente nel database, piuttosto che averla nel codice.

Non c'è davvero niente di sbagliato in questo approccio. Le visualizzazioni su più database sono piuttosto standard, basti ricordare che le prestazioni saranno leggermente peggiori rispetto a quelle su una di esse (proprio come i join sono leggermente più lenti dei dati di una singola tabella).

Anche con questo approccio, se si va fino alla creazione di viste per unificare insieme i due database, si dovrebbe prevedere il futuro e creare la vista per soddisfare le esigenze future, quindi non è necessario drasticamente cambia le tue query SQL.

    
risposta data 13.07.2016 - 20:46
fonte

Leggi altre domande sui tag