Riferimenti ai valori del database nella logica aziendale


43

Immagino che questa sia un'altra domanda sulla codifica e le migliori pratiche. Supponiamo di avere un elenco di valori, diciamo frutta, memorizzato nel database (deve essere nel database poiché la tabella viene utilizzata per altri scopi come i report SSRS), con un ID:

1 Apple 
2 Banana 
3 Grapes

Potrei presentarli all'utente, ne seleziona uno, viene memorizzato nel suo profilo come FavouriteFruit e l'ID memorizzato nel suo record nel database.

Quando si tratta di regole aziendali / logica di dominio, quali sono i consigli per assegnare la logica a valori specifici. Di 'se l'utente ha selezionato Uva Voglio svolgere qualche compito extra, qual è il modo migliore per fare riferimento al valore Uva:

// Hard coded name
if (user.FavouriteFruit.Name == "Grapes")

// Hard coded ID
if (user.FavoriteFruit.ID == 3) // Grapes

// Duplicate the list of fruits in an enum
if (user.FavouriteFruit.ID == (int)Fruits.Grapes)

o qualcos'altro?

Poiché, naturalmente, FavouriteFruit verrà utilizzato in tutta l'applicazione, l'elenco può essere aggiunto o modificato.

Qualcuno potrebbe decidere di voler rinominare 'Uva' in 'Uva' e questo ovviamente spezzerebbe l'opzione di stringa codificata.

L'ID hardcoded non è completamente chiaro, sebbene, come mostrato, potresti semplicemente aggiungere un commento per identificare rapidamente quale elemento è.

L'opzione enum comporta la duplicazione dei dati dal database che sembra errato in quanto potrebbe non essere sincronizzato.

Comunque, grazie in anticipo per eventuali commenti o suggerimenti.


1
Grazie a tutti: i suggerimenti e i consigli generali sono davvero utili. @RemcoGerlich la tua idea di separare le preoccupazioni di una stringa utilizzata per scopi di visualizzazione e una separata come codice di ricerca per un codice più leggibile è molto buona.
Kate,

1
Ho intenzione di dare a @Mike Nakis la tua idea di oggetti precaricati, dato che questo sembra il migliore dei due mondi.
Kate,

1
Vorrei suggerire una variazione sulla tua prima soluzione. Chiedi alla tua tabella di contenere una terza colonna per come verrà elaborata e usi quel campo per determinare quale codice eseguire. Non è un campo di visualizzazione e può essere condiviso tra più frutti.
Calcio d'

1
L'opzione enum comporta la duplicazione dei dati dal database che sembra errato in quanto potrebbe non essere sincronizzato. Mi piace questo, in realtà. È come la contabilità a doppia entrata. Se entrambi i lati del libro mastro non si bilanciano, saprai che qualcosa non va. Rende le cose più deliberate.
radarbob,

1
Hmmm ... Se esiste una relazione 1: 1 tra ID e una stringa, questa è ridondante e avere entrambe è inutile. Una stringa può servire sia come chiave DB che come numero intero. MyApplication.Grape.IDbalbetta, per così dire. Una "Apple" non è una "Red_Apple" non più che l'ID di 3 è anche 4. Quindi il potenziale per rinominare "Apple" in "Red_Apple" non ha più senso che dichiarare che 3 è 4 (e forse anche 3). Il punto di un enum è di sottrarre il suo DNA numerico. Quindi, forse è il momento di veramente disaccoppiare arbitrarie chiavi DB relazionali che letteralmente non hanno significato in modelli di business di uno.
radarbob,

Risposte:


31

Evita le stringhe e le costanti magiche a tutti i costi. Sono completamente fuori questione, non dovrebbero nemmeno essere considerati come opzioni. Questo sembra lasciarti con una sola opzione praticabile: identificatori, cioè enum. Tuttavia, c'è anche un'altra opzione, che secondo me è la migliore. Chiamiamo questa opzione "Oggetti precaricati". Con Oggetti precaricati, è possibile effettuare le seguenti operazioni:

if( user.FavouriteFruit.ID == MyApplication.Grape.ID )

Quello che è appena successo qui è che ho ovviamente caricato l'intera riga di Grapein memoria, quindi ho il suo ID pronto per l'uso nei confronti. Se stai utilizzando Object-Relational Mapping (ORM), sembra ancora meglio:

if( user.FavouriteFruit == MyApplication.Grape )

(Ecco perché lo chiamo "Oggetti precaricati".)

Quindi, quello che faccio è che durante l'avvio carico tutte le mie tabelle di "enumerazione" (piccole tabelle come i giorni della settimana, i mesi dell'anno, i sessi, ecc.) Nella classe di dominio dell'applicazione principale. Li carico per nome, perché ovviamente MyApplication.Grapedevo ricevere la riga chiamata "Uva", e asserisco che ognuno di essi è stato trovato. In caso contrario, abbiamo un errore di runtime garantito durante l'avvio, che è il meno maligno di tutti gli errori di runtime.


17
Non sono in disaccordo con la risposta, ma penso che l'imperativo di "Evitare stringhe e costanti magiche a tutti i costi" non è d'accordo con il resto della risposta, che in realtà richiede che tu abbia almeno un posto dove vengono utilizzate costanti o stringhe magiche nel popolare i tuoi "oggetti precaricati". È notevole, penso, perché ci sono modi per evitare del tutto "stringhe e costanti magiche", anche se di solito è più offuscante di quanto valga la pena ...
svidgen,

2
@svidgen non saresti d'accordo sul fatto che esiste una differenza fondamentale tra lo scattering per nome dappertutto sul posto rispetto al legame per nome una sola volta, per caricare il contenuto di un record con lo stesso nome e farlo solo all'avvio, dove gli errori di runtime sono quasi altrettanto positivi degli errori di compilazione? Ad ogni modo, i modi per evitare anche il minimo legame per nome sono sempre interessanti, nonostante l'offuscamento che hai citato, quindi sarei curioso di sentire quello che hai in mente.
Mike Nakis,

Oh, sono completamente d'accordo. E, data la natura del PO, suggerirei solo che questa risposta potrebbe trarre vantaggio dal cambiare "a tutti i costi" in "ogni volta che è possibile e fattibile" o qualcosa di simile. ... Se avessi più tempo, solo per completezza, scriverei una risposta che affronti una sorta di assurdità di metaprogrammazione ... ma, non è questo ciò di cui l'OP (o chiunque nella maggior parte dei casi) probabilmente ha bisogno . Ma una soluzione di metaprogamming si allinea maggiormente con la tua prima affermazione così com'è.
svidgen,

1
@utente469104 la differenza è che gli ID possono cambiare e l'applicazione caricherà comunque tutte le righe correttamente ed eseguirà tutti i confronti correttamente. Inoltre, sei libero di refactificare il codice e rinominare le righe nel modo che preferisci, e l'unico posto dove devi cercare elementi da sistemare è l'avvio dell'applicazione e tende ad essere molto ovvio: Grape = fetchRow( Fruit.class, NameColumn, "Grape" ); E se tu fai qualcosa di sbagliato, AssertionErrorti farò sapere.
Mike Nakis,

1
@grahamparks non più di quanto enumsarebbe stata una stringa magica. Il punto è la concentrazione di tutto il legame per nome in un solo posto , la convalida di tutto durante l'avvio e la sicurezza del tipo .
Mike Nakis,

7

Il controllo rispetto alla stringa è il più leggibile, ma fa il doppio dovere: viene utilizzato sia come identificativo che come descrizione (che può cambiare per motivi non correlati).

Di solito divido entrambi i compiti in campi separati:

id  code    description
 1  grape   Grapes
 2  apple   Apple

Dove la descrizione può cambiare (ma non "Uva" in "Banana"), ma il codice non può cambiare, mai.

Anche se questo è principalmente dovuto al fatto che i nostri ID sono quasi sempre generati automaticamente, e quindi non sono adatti. Se puoi scegliere liberamente gli ID, forse puoi garantire che siano sempre corretti e utilizzarli.

Inoltre, con quale frequenza qualcuno modifica veramente "Uva" in "Uva"? Forse nulla di tutto ciò è necessario.


8
Non credo che ancora più ridondanza sia la risposta ...
Robbie Dee,

4
Ho preso in considerazione anche questa opzione e l'ho provata, ma questo è quello che è successo: a un certo punto, "apple" ha dovuto essere differenziata in "green_apple" e "red_apple". Ma poiché "apple" era già utilizzato in una miriade di posizioni nel codice, non potevo rinominarlo, quindi dovevo avere "apple" e "green_apple". E di conseguenza, lo Sheldon in me mi ha impedito di dormire per diverse notti fino a quando non sono entrato e ho rifatto il tutto con "Oggetti precaricati". (vedi la mia risposta.)
Mike Nakis,

1
Mi piacciono sicuramente i tuoi oggetti precaricati, ma se la tua "mela" è differenziata, non devi comunque esaminare tutto, qualunque metodo tu scelga?
Remco Gerlich,

Potresti anche avere una tabella separata per il nome della descrizione, a supporto dell'internazionalizzazione.
Erik Eidt,

1
@MikeNakis e il Refactoring sono essenzialmente una ricerca e sostituzione sull'intera base di codice che sostituisce Fruit.Apple con Fruit.GreenApple. Se uso valori di stringa hardcoded farei una ricerca e sostituisci su tutta la base di codice per sostituire "apple" con "green_apple" che è più o meno la stessa cosa. - Il refactoring è semplicemente migliore, perché l'IDE sta effettuando la sostituzione.
Falco,

4

Quello che ti aspetti qui è che la logica di programmazione sia automaticamente adattabile alla modifica dei dati. Le semplici opzioni statiche come Enum non funzionano qui perché non è possibile aggiungere enumerazioni extra in fase di esecuzione.

Alcuni schemi che ho visto:

  • Enums + default per la protezione da una nuovissima voce di database che rovina la giornata del tuo programma.
  • Codifica delle azioni da eseguire (logica biz) nel database stesso. In molti casi questo è molto possibile perché molte logiche vengono riutilizzate. L'implementazione della logica dovrebbe essere nel programma.
  • Attributi / colonne extra nel database per contrassegnare il nuovo valore come "da ignorare" nel programma fino a quando il programma non viene distribuito correttamente.
  • Meccanismi rapidi non riusciti attorno al percorso del codice che carica / ricarica i valori dal database. (Se l'azione corrispondente non è nel programma E non è contrassegnata per essere ignorata, non eseguire l'aggiornamento).

In generale, mi piace che i dati siano completi in termini di riferimento alle azioni che implica - anche se le azioni stesse potrebbero essere implementate altrove. Qualsiasi codice che determina azioni indipendenti dai dati ha appena diviso la rappresentazione dei dati che molto probabilmente divergerà e porterà a bug.


4

Memorizzarli in entrambi i posti (in una tabella e in un ENUM) non è poi così male. Il ragionamento è il seguente:

Memorizzandoli in una tabella di database, possiamo imporre l'integrità referenziale nel database tramite chiavi esterne. Pertanto, quando si associa una persona o qualsiasi entità a un frutto, nella tabella del database esiste solo un frutto.

Memorizzarli come ENUM ha senso anche perché possiamo scrivere codice senza stringhe magiche e rende il codice più leggibile. Sì, devono rimanere sincronizzati, ma davvero quanto sarebbe difficile aggiungere una riga all'ENUM e una nuova istruzione insert al database.

Una cosa, una volta definito un ENUM, non cambia il suo valore. Ad esempio, se avessi:

  • Mela
  • Uva

NON rinominare Uva in Uva. Aggiungi semplicemente un nuovo ENUM.

  • Mela
  • Uva
  • Uva

Se è necessario migrare i dati, applicare un aggiornamento per spostare tutta l'Uva in Uva.


Come ulteriore passaggio ho lavorato nei negozi in cui i valori dei metadati hanno un flag di eliminazione nella tabella per indicare che non dovrebbero essere utilizzati (sono stati deprecati o esiste una versione più recente).
Robbie Dee,

1

Hai ragione a porre questa domanda, in realtà è una bella domanda mentre stai tentando di difenderti dalla valutazione di condizioni imprecise.

Detto questo, la valutazione (le tue ifcondizioni) non deve necessariamente essere al centro di come aggirarla. Prestare invece attenzione al modo in cui si propagano le modifiche che potrebbero causare un problema "non sincronizzato".

Approccio alla stringa

Se è necessario utilizzare stringhe, perché non esporre la funzionalità di modifica dell'elenco tramite l'interfaccia utente? Progettare il sistema in modo che su di cambiare Grapea Grapes, per esempio, di aggiornare tutti i record attualmente riferimento Grape.

Approccio ID

Preferirei sempre fare riferimento a un ID, nonostante il compromesso di una certa leggibilità. The list may be added topuò di nuovo essere qualcosa di cui verrai avvisato se esponessi tale funzionalità dell'interfaccia utente. Se ti preoccupi del riordino degli elementi che cambiano l'id, propaga nuovamente tale modifica a tutti i record dipendenti. Analogamente a quanto sopra. Un'altra opzione (seguendo la convenzione di normalizzazione corretta, sarebbe quella di avere una colonna enum / id - e fare riferimento a una FruitDetailtabella più dettagliata , che ha una colonna 'Ordine' che puoi cercare).

Ad ogni modo, puoi vedere che sto suggerendo di controllare la modifica o l'aggiornamento del tuo elenco. Se lo fai attraverso l'uso di un ORM o altri accessi ai dati è determinato dalle specifiche della tua tecnologia. In sostanza, ciò che stai facendo è richiedere a queste persone di allontanarsi dal DB per tali cambiamenti, cosa che penso vada bene. La maggior parte dei CRM principali farà lo stesso requisito.


1
Nel database , l'id numerico viene archiviato per i record figlio, in particolare per evitare questo problema. Questa domanda riguarda come interfacciarsi con un linguaggio di programmazione.
Clockwork-Muse

1
@ Clockwork-Muse - per evitare quale problema? Non ha senso.
JᴀʏMᴇᴇ,

Uso un po 'l'approccio ID, ma l'ID è bloccato e non può cambiare. La stringa allegata ovviamente può essere perché alle persone spesso piace rinominare le cose "camion", diventa "camion", ecc., Mentre la cosa stessa (rappresentata da ID) non cambia.
Brian Knoblauch,

Se segui l'approccio ID, come gestisci i database di sviluppo e di produzione? Con gli ID a incremento automatico, l'aggiunta di elementi a entrambi i DB in ordine diverso comporterà ID diversi.
Protector un

Tuttavia, non deve essere un incremento automatico? Non dovrebbe essere in questo caso, specialmente se è il valore intero dell'enum sottostante che stiamo usando.
JᴀʏMᴇᴇ, il

0

Un problema molto comune Anche se la duplicazione del lato client dei dati sembra violare i principi DRY , è in realtà dovuta alla differenza di paradigma tra i livelli.

Avere l'enum (o qualsiasi altra cosa) al passo con il database non è neanche così raro. È possibile che sia stato inviato un altro valore a una tabella di metadati per supportare una nuova funzionalità di report non ancora utilizzata nel codice lato client.

A volte succede anche nell'altro modo. Un nuovo valore enum viene aggiunto al lato client ma l'aggiornamento del DB non può avvenire fino a quando il DBA non può applicare le modifiche.


Sì, hai descritto il problema. Qual è la tua soluzione?
Protector un

1
@Protectorone utente si assume non è una soluzione pallottola d'argento che è un erroneo convincimento nella mia esperienza. Il meglio che puoi sperare è che qualche entità aziendale possieda il dominio problematico, così almeno puoi vedere da che parte è in ritardo - lato client o lato database. Le attività bancarie e finanziarie sono in genere molto efficienti in questo senso con il settore della vendita al dettaglio notevolmente inferiore ...
Robbie Dee,

0

Supponendo che stiamo parlando di ciò che è essenzialmente una ricerca statica, la terza opzione - l'enum - è sostanzialmente l'unica scelta sana. È quello che faresti se il database non fosse coinvolto, quindi ha senso.

La domanda diventa quindi quella su come mantenere sincronizzati enum e tabelle statiche / di ricerca nel database e sfortunatamente non è un problema a cui ho ancora una risposta completa.

Per scelta faccio tutto il mio schema di manutenzione nel codice e quindi posso mantenere una relazione tra una build dell'applicazione e una versione dello schema prevista, quindi è semplice mantenere sincronizzati la ricerca e l'enum, ma è qualcosa che bisogna ricordare fare. Sarebbe meglio se fosse più automatizzato (e anche un test di integrazione automatizzato per garantire che gli enumerazioni e le ricerche corrispondessero aiutassero) ma non è qualcosa che io abbia mai implementato.


1
Non credo che si tratti solo di ricerche statiche, altrimenti potrebbero essere semplicemente estratte dal database e consumate così come sono. Il problema a quanto ho capito è quando la logica aziendale deve essere applicata in base al valore di ricerca utilizzato. Ma, a parte questo, sì - gli enumeratori sono generalmente impiegati per questo scopo.
Robbie Dee,

Ok, ho bisogno di un termine migliore "per la ricerca statica", il contesto che descrivi è quello che intendevo :) La chiave è "statica" - questi sono valori che non cambiano il problema è l'aggiunta di nuovi valori e la modifica dell'etichetta "( ma non l'intento) per i valori esistenti.
Murph,
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.