Referenziare i valori del database nella logica aziendale

42

Credo che questa sia un'altra domanda su hard coding e best practice. Diciamo che ho un elenco di valori, diciamo frutto, memorizzato nel database (deve essere nel database come la tabella viene utilizzata per altri scopi come i rapporti SSRS), con un ID:

1 Apple 
2 Banana 
3 Grapes

Posso 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 le raccomandazioni per l'assegnazione della logica a valori specifici. Dì se l'utente ha selezionato Grapes, voglio eseguire un compito extra, qual è il modo migliore per fare riferimento al valore di Grapes:

// 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?

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

Qualcuno potrebbe decidere di volere che "Grapes" venga rinominato in "Grape" e ciò naturalmente spezzerebbe l'opzione stringa hardcoded.

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

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

Comunque, grazie in anticipo per eventuali commenti o suggerimenti.

    
posta Kate 03.12.2015 - 13:26
fonte

7 risposte

30

Evita stringhe e costanti magiche a tutti i costi. Sono completamente fuori questione, non dovrebbero nemmeno essere considerate come opzioni. Questo sembra lasciarti con una sola opzione praticabile: identificatori, cioè enumerazioni. Tuttavia, c'è anche un'altra opzione, che a mio avviso è la migliore. Chiamiamo questa opzione "Oggetti precaricati". Con gli oggetti precaricati, puoi fare quanto segue:

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

Quello che è appena successo qui è che ho ovviamente caricato l'intera riga di Grape in memoria, quindi ho il suo ID pronto all'uso nei confronti. Se ti capita di utilizzare 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 carica tutte le mie tabelle di "enumerazione" (piccoli tavoli come i giorni della settimana, i mesi dell'anno, i generi, ecc.) nella classe di dominio dell'applicazione principale. Li carico per nome, perché ovviamente, MyApplication.Grape deve 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 pericoloso di tutti gli errori di runtime.

    
risposta data 03.12.2015 - 15:22
fonte
6

Il controllo contro la stringa è il più leggibile, ma ha il doppio dovere: è usato sia come identificatore che come descrizione (che può cambiare per ragioni non correlate).

Solitamente suddivido entrambe le funzioni in campi separati:

id  code    description
 1  grape   Grapes
 2  apple   Apple

Dove la descrizione può cambiare (ma non "Grapes" 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 adatti. Se puoi scegliere liberamente gli ID, forse puoi garantire che siano sempre corretti e usarli.

Inoltre, quanto spesso qualcuno veramente modifica "Grapes" a "Grape"? Forse niente di tutto questo è necessario.

    
risposta data 03.12.2015 - 14:44
fonte
3

Quello che ti aspetti qui è che la logica di programmazione si adatta automaticamente ai dati in cambiamento. Le opzioni statiche semplici come Enum non funzionano qui perché non è possibile aggiungere enumerazioni extra nel runtime.

Alcuni pattern che ho visto:

  • Enum + predefinito per proteggere da una nuova voce del database che rovina il giorno 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 aggiuntivi nel database per contrassegnare il nuovo valore come "da ignorare" nel programma finché il programma non viene distribuito correttamente.
  • Fail meccanismi veloci attorno al percorso del codice che carica / ricarica i valori dal database. (Se l'azione corrispondente non si trova 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 solo semplificato la rappresentazione dei dati che molto probabilmente divergono e portano a bug.

    
risposta data 03.12.2015 - 15:03
fonte
3

La loro memorizzazione 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 applicare l'integrità referenziale nel database tramite chiavi esterne. Quindi quando si associa una persona o qualsiasi entità a un frutto è solo un frutto presente nella tabella del database.

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

Una cosa, una volta che è stato definito un ENUM, non cambiare il suo valore. Ad esempio, se tu avessi:

  • Apple
  • Uva

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

  • Apple
  • Uva
  • Uva

Se hai bisogno di migrare i dati, applica un aggiornamento per spostare tutti i Grape in Grapes.

    
risposta data 03.12.2015 - 15:36
fonte
1

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

Detto questo, la valutazione (le condizioni if ) non deve necessariamente essere al centro di come aggirarla. Invece, fai attenzione al modo in cui diffondi le modifiche che potrebbero causare un problema di "fuori sincronia".

Approccio per stringhe

Se è necessario utilizzare le stringhe, perché non esporre la funzionalità di modifica dell'elenco tramite l'interfaccia utente? Progetta il sistema in modo che, modificando Grape in Grapes , ad esempio, aggiorni tutti i record che fanno riferimento a Grape .

Approccio ID

Preferirei sempre fare riferimento a un ID, nonostante il compromesso di una certa leggibilità. The list may be added to può essere di nuovo qualcosa che verrebbe notificato se esponessi a una tale caratteristica dell'interfaccia utente. Se si è interessati al riordino di elementi che modificano l'id, propagare nuovamente tale modifica a tutti i record dipendenti. Allo stesso modo di sopra. Un'altra opzione (seguendo la corretta convenzione di normalizzazione, sarebbe quella di avere una colonna enum / id e fare riferimento a una tabella FruitDetail più dettagliata, che ha una colonna 'Ordine' che puoi cercare).

In ogni caso, puoi vedere che sto suggerendo di controllare l'emendamento o l'aggiornamento della tua lista. Se lo fai attraverso l'uso di un ORM o altri tipi di accesso ai dati è determinato dalle specifiche della tua tecnologia. Ciò che stai facendo, in sostanza, è richiedere alle persone che si allontanano dal DB per tali cambiamenti - che penso sia soddisfacente. La maggior parte dei CRM principali farà lo stesso requisito.

    
risposta data 03.12.2015 - 14:09
fonte
0

Un problema molto comune. Sebbene la duplicazione del lato client dei dati possa sembrare una violazione dei principi DRY , è in realtà dovuta alla differenza di paradigma tra strati.

Avere l'enum (o qualsiasi altra cosa) fuori passo con il database non è in realtà così raro. Potresti aver spostato un altro valore in una tabella di metadati per supportare una nuova funzione di report che non è ancora stata 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.

    
risposta data 03.12.2015 - 13:51
fonte
0

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

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

Per scelta faccio tutto il mio mantenimento dello schema nel codice e quindi posso mantenere una relazione tra una build dell'applicazione e una versione prevista dello schema, quindi è semplice mantenere la ricerca e l'enum in sincronia ma è qualcosa che si ha ricordarsi di fare. Sarebbe meglio se fosse più automatizzato (e anche un test di integrazione automatico per garantire che l'enumerazione e le ricerche corrispondano sarebbe d'aiuto) ma non è qualcosa che ho mai implementato.

    
risposta data 03.12.2015 - 14:16
fonte

Leggi altre domande sui tag