In un "Modello dati revisionato", dove appartiene il codice che conserva lo stato di un record precedente?

3

Quando dico "Revisional Data Model", intendo un modello di dati in cui le informazioni non vengono mai perse: le eliminazioni non distruggono mai alcuna riga e gli aggiornamenti causano sempre un inserimento da qualche altra parte per preservare lo stato precedente di una riga prima dell'aggiornamento.

Ne ho alcuni di cui ho letto e ascoltati dai colleghi:

  1. Trigger nel database. Il motivo principale per cui non mi piace questo approccio è perché se si dipendono dai trigger per applicare l'integrità della revisione, l'applicazione diventa legata al server del database. Se si utilizza un ORM con diversi provider (Oracle, MySQL, T-SQL, ecc.), Potrebbe essere necessario modificare alcuni SQL per passare da uno all'altro.
  2. Registri di traccia leggibili dall'uomo L'anno scorso ho giocato a Business Analyst per un progetto con un nuovo sviluppatore perché uno dei nostri dipartimenti affiliati era stato sbattuto con scadenze importanti. Ha insistito sul fatto che, nonostante i requisiti che ho raccolto e gli schemi che ho modellato, avrebbe inserito una procedura memorizzata da eseguire con ogni istruzione di creazione / aggiornamento / cancellazione. Questo sproc dovrebbe semplicemente scrivere i vecchi dati come una stringa formattata e salvarlo in una tabella ERRORS_LOG nel database. Il suo obiettivo era di aiutarla a eseguire il debug dell'applicazione nel caso qualcosa fosse andato storto: voleva una traccia.
  3. Creazione di tabelle sorelle. Questo approccio è come avere 2 database che vivono nello stesso contesto. Per la tabella Person ci sarebbe una tabella PersonHistory, per Product ci sarebbe ProductHistory e così via. Gli schemi sarebbero identici e ogni volta che si verificava una modifica nella tabella delle entità, lo stato precedente sarebbe stato inserito in ProductHistory.

Se si dispone di un'applicazione in cui si desidera consentire agli utenti di "ripristinare" o "ripristinare la versione precedente" di un'entità, solo # 3 sembra una soluzione praticabile. Tuttavia, poiché ogni coppia (entità e relativa cronologia) ha lo stesso modello / schema, possono essere combinati. Aggiungendo proprietà bool / bit come IsCurrentVersion e IsDeleted, è possibile suddividere un singolo set di entità in stati diversi all'interno della clausola WHERE. Considera questo esempio # 4.

Uno svantaggio del # 4 è che le tabelle del database diventano difficili da leggere quando si selezionano tutte le righe. I dati sull'estrazione da un ide server DB richiederebbero un uso piuttosto esteso delle viste, che causa lo stesso problema dell'opzione n. 1. Guardare tutti i diversi stati insieme nella stessa tabella rende molto più difficile leggere. Con l'approccio n. 3, devi cercare in una tabella diversa per trovare versioni precedenti e amp; cancella.

Abbiamo utilizzato l'approccio n. 4 con successo. Abbiamo un supertipo di RevisableEntity con proprietà che indicano quando i dati sono stati modificati, chi l'ha modificato, se è stato cancellato, archiviato o modificato. Gli aggiornamenti si verificano sulle righe proprio come farebbero in circostanze normali. Tuttavia, come parte della stessa azione, viene inserito un inserto corrispondente nella stessa tabella / set di entità. L'inserto contiene gli stessi valori dell'aggiornamento prima che fosse aggiornato. La riga aggiornata, oltre a tutte le modifiche apportate dall'utente, aggiorna anche le proprietà when e who.

Ora per la mia domanda: A chi appartiene questo codice? nel dominio o nell'implementazione del repository? Ci sono alcuni aspetti che dipendono dallo storage, il che suggerisce di inserire il codice in cui si trova l'accoppiamento ORM. Tuttavia ci sono altri aspetti di cui i livelli dominio / applicazione dovrebbero essere in grado di fidarsi. Forse le entità di dominio dovrebbero gestire la propria integrità di revisione, o forse ci dovrebbe essere una fabbrica da coordinare?

    
posta danludwig 14.12.2011 - 06:50
fonte

5 risposte

4

Lascia che il database gestisca tutto e memorizza la cronologia in una tabella diversa.

Il database è efficace nel gestire i dati e tenerli accessibili e puliti, usa quei punti di forza! Non complicare eccessivamente le cose per dare all'applicazione un controllo extra o una maggiore flessibilità a meno che non si DEVE assolutamente averlo. Inoltre, non memorizzare una tonnellata di cronologia nelle tue tabelle live a meno che non ti piacciano i risultati rapidi, scaricarli da qualche altra parte in modo da poter ottimizzare al meglio velocità e costi.

    
risposta data 14.12.2011 - 07:08
fonte
2

I ha risposto a una domanda simile su SO qualche tempo fa. Ecco qualcosa che potrebbe funzionare.

Un metodo che viene utilizzato da alcune piattaforme wiki è quello di separare i dati di identificazione e il contenuto per il quale stai monitorando le revisioni. Aggiunge complessità, ma si finisce con una cronologia di record completi.

Quindi, ad esempio, se avessi una tabella chiamata Opportunità per monitorare le offerte di vendita, in realtà creerai due tabelle separate:

Opportunità
Opportunities_Content (o qualcosa del genere)

La tabella Opportunità contiene le informazioni che utilizzeresti per identificare in modo univoco il record e ospiterà la chiave primaria a cui fare riferimento per le tue relazioni con le chiavi esterne. La tabella Opportunities_Content contiene tutti i campi che gli utenti possono modificare e per i quali desideri mantenere una traccia di controllo. Ogni record nella tabella Contenuto includerebbe il proprio PK e i dati di data modifica e data di modifica. La tabella Opportunità includerebbe un riferimento alla versione corrente e informazioni su quando il record principale è stato originariamente creato e da chi.

Ecco un semplice esempio basato sullo ScrewTurn schema dati:

CREATE TABLE dbo.Page(  
    ID int PRIMARY KEY,  
    Name nvarchar(200) NOT NULL,  
    CreatedByName nvarchar(100) NOT NULL, 
    CurrentRevision int NOT NULL, 
    CreatedDateTime datetime NOT NULL

E il contenuto:

CREATE TABLE dbo.PageContent(
    PageID int NOT NULL,
    Revision int NOT NULL,
    Title nvarchar(200) NOT NULL,
    User nvarchar(100) NOT NULL,
    LastModified datetime NOT NULL,
    Comment nvarchar(300) NULL,
    Content nvarchar(max) NOT NULL,
    Description nvarchar(200) NULL

Probabilmente renderei il PK della tabella dei contenuti una chiave a più colonne da PageID e Revision forniti. Revision era un tipo di identità. Dovresti usare la colonna Revisione come FK. Quindi si tira il record consolidato tramite JOINing in questo modo:

SELECT * FROM Page
JOIN PageContent ON CurrentRevision = Revision AND ID = PageID

Potrebbero esserci degli errori lassù ... questo è fuori di testa. Tuttavia, dovrebbe darti un'idea di un modello alternativo.

Josh

    
risposta data 14.12.2011 - 13:01
fonte
1

Non ho una risposta chiara a questo temo. Dipende da cosa vuoi fare, da quanto sono rigidi i tuoi requisiti e quali strumenti hai a disposizione o vuoi usare. Per dare un esempio estremo, le banche non scrivono semplicemente un registro su un file da qualche parte, ma in realtà stampano quella linea di log su carta reale ...

Quello che ho fatto per una delle mie applicazioni è stato il tuo suggerimento n. 3, in cui i dati live si trovano in tabelle ottimizzate per l'accesso che risiedono interamente in memoria mentre i dati per "undeletes" / "rollback" sono memorizzati sulle tabelle tradizionali che hanno solo un indice sul entry-id.

Ma sono solo io, perché ho un sacco di ricerche al secondo, ma solo poche modifiche e rollback. Le tue esigenze potrebbero essere diverse. Forse la tua applicazione è più simile a GoogleDocs in cui ti aspetti che i cambiamenti accadano spesso e anche contemporaneamente, ma non hai bisogno di ricerche / ricerche veloci. Quindi potrebbe essere meglio archiviare il documento di base e tenere traccia dei cambiamenti-delta che si verificano nel tempo.

Altri ancora, che stanno già utilizzando un DBMS aziendale come Oracle, potrebbero sentirsi molto più a loro agio "esternalizzando" questo al database. Che si tratti di trigger o di add-on specializzati per il controllo delle versioni disponibili direttamente dal produttore DBMS. Questa potrebbe anche essere la scelta giusta per te personalmente se il controllo delle versioni e la tracciabilità sono vitali per i dati e vuoi scaricare la responsabilità di farlo correttamente a qualcun altro.

E così via ...

Quindi vedi, in realtà dipende dalle tue esigenze

    
risposta data 14.12.2011 - 10:53
fonte
1

Cancellazioni soft come queste sono un killer delle prestazioni. Il motivo principale per questo è che ogni tabella deve avere una colonna "IsDeleted" e poiché questo contiene solo 1 di 2 valori possibili, l'indicizzazione di questa colonna è inutile in quanto la selettività è troppo bassa. Ciò significa che ogni query rispetto alla tabella si trasforma in una scansione della tabella per trovare tutte le righe "Correnti".

Vedi il seguente articolo per ulteriori dettagli sul motivo per cui questa è una cattiva idea.

link

    
risposta data 14.12.2011 - 12:46
fonte
0

In primo luogo, per rispondere alla tua domanda: a quale livello appartiene quel codice?

Vorrei andare con il livello del tuo repository di codice attuale e non con il tuo database (procedure, eventi, ecc.). Quel pensiero mi sembra una logica di business, ed è buono per mantenere tutte le logiche di business nello stesso livello (ora parliamo del livello di codice rispetto al livello di database)

Come farò?

Hai davvero bisogno di mantenere i tuoi dati storici allo stesso modo? normalizzato?

Potresti avere una singola tabella come questa

  • id
  • table_pm_key
  • tabella
  • oggetto serializzato (puoi definire ciò che significa sempre: attributi, relazioni)
  • REVISION_ID
  • revision_data
  • last_mark - opzionale
  • parent_revision -optional

Nel tuo codice ORM, puoi ascoltare l'aggiornamento / eliminazione sulle tabelle che desideri e aggiungere automaticamente a questa tabella

    
risposta data 11.09.2015 - 23:41
fonte