Référencement des valeurs de base de données dans la logique métier

43

Je suppose que ceci est une autre question sur le codage dur et les meilleures pratiques. Supposons que j'ai une liste de valeurs, disons fruits, stockées dans la base de données (elle doit figurer dans la base de données car la table est utilisée à d'autres fins, telles que les rapports SSRS), avec un ID:

1 Apple 
2 Banana 
3 Grapes

Je peux les présenter à l'utilisateur, il en sélectionne un, il est stocké dans son profil sous le nom FavouriteFruit et l'identifiant stocké dans son enregistrement dans la base de données.

En ce qui concerne la logique des règles métier / domaine, quelles sont les recommandations pour attribuer une logique à des valeurs spécifiques. Si l'utilisateur a sélectionné Grapes, je souhaite effectuer une tâche supplémentaire. Quel est le meilleur moyen de référencer la valeur 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)

ou autre chose?

Bien sûr, le FavouriteFruit sera utilisé dans toute l'application, la liste peut être ajoutée ou modifiée.

Quelqu'un peut décider qu'il veut renommer 'Grapes' en 'Grape', ce qui casse l'option de chaîne codée en dur.

L'ID codé en dur n'est pas tout à fait clair, cependant, comme indiqué, vous pouvez simplement ajouter un commentaire pour identifier rapidement l'élément.

L'option enum implique la duplication de données de la base de données, ce qui semble faux, car elles risquent de ne pas être synchronisées.

Quoi qu'il en soit, merci d'avance pour vos commentaires ou suggestions.

Kate
la source
1
Merci à tous: les suggestions et les conseils généraux sont vraiment utiles. @RemcoGerlich votre idée de séparer les préoccupations d'une chaîne utilisée à des fins d'affichage et une autre en tant que code de recherche pour un code plus lisible est très bonne.
Kate
1
Je vais donner à Mike Nakis votre idée d’objets préchargés, car cela semble être le meilleur des deux mondes.
Kate
1
Je suggérerais une variante de votre première solution. Faites en sorte que votre table contienne une 3ème colonne expliquant comment elle sera traitée et utilisez ce champ pour déterminer le code à exécuter. Pas un champ d'affichage, et peut être partagé entre plusieurs fruits.
Kickstart
1
L'option enum implique la duplication de données de la base de données, ce qui semble faux, car elles risquent de ne pas être synchronisées. J'aime ça, en fait. C'est comme une comptabilité en partie double. Si les deux côtés du grand livre ne s'équilibrent pas, vous saurez que quelque chose ne va pas. Cela rend les choses plus délibérées.
radarbob
1
Hmmm ... S'il existe une relation 1: 1 entre ID et une chaîne, elle est redondante et le fait de posséder les deux est inutile. Une chaîne peut servir à la fois de clé de base de données et d’entier. MyApplication.Grape.IDest le bégaiement, pour ainsi dire. Un "Apple" n'est pas un "Red_Apple", pas plus qu'un ID de 3, c'est aussi 4. Donc, le potentiel de renommer "Apple" en "Red_Apple" n'a pas plus de sens que de déclarer que 3 est 4 (et peut-être même 3). Le but d'une énumération est d'extraire son ADN numérique. Alors peut-être qu'il est temps de vraiment dissocier les clés de base de données relationnelles arbitraires qui n'ont littéralement aucune signification dans les modèles commerciaux.
radarbob

Réponses:

31

Évitez les cordes et les constantes magiques à tout prix. Ils sont complètement hors de question, ils ne devraient même pas être considérés comme des options. Cela semble ne vous laisser qu’une seule option viable: les identificateurs, c’est-à-dire les énumérations. Cependant, il existe aussi une option supplémentaire qui, à mon avis, est la meilleure. Appelons cette option "Objets préchargés". Avec les objets préchargés, vous pouvez effectuer les opérations suivantes:

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

Ce qui vient de se passer ici, c’est que j’ai évidemment chargé toute la ligne de Grapemémoire dans la mémoire, et j’ai donc son identifiant prêt à être utilisé pour des comparaisons. Si vous utilisez un mappage relationnel-objet (ORM), il est encore meilleur:

if( user.FavouriteFruit == MyApplication.Grape )

(C'est pourquoi je l'appelle "Objets préchargés".)

Par conséquent, lors du démarrage, je charge tous mes tableaux "d'énumération" (petits tableaux tels que les jours de la semaine, les mois de l'année, les genres, etc.) dans la classe de domaine d'application principale. Je les charge par leur nom car, bien évidemment, il MyApplication.Grapefaut recevoir la ligne "Grape" et j'affirme que chacun d'entre eux est trouvé. Sinon, nous avons une erreur d'exécution garantie au démarrage, qui est la moins maligne de toutes les erreurs d'exécution.

Mike Nakis
la source
17
Je ne suis pas en désaccord avec la réponse, mais je pense que l'impératif "Éviter les cordes et les constantes magiques à tout prix" est en désaccord avec le reste de la réponse, ce qui nécessite en fait que vous ayez au moins un endroit où des constantes ou des cordes magiques sont utilisées. en remplissant vos "objets préchargés". C'est remarquable, je pense, car il existe des moyens d'éviter complètement les "chaînes et les constantes magiques", bien que cela soit généralement plus obscur qu'il ne vaut la peine ...
svidgen
2
@svidgen ne diriez-vous pas qu'il existe une différence fondamentale entre la diffusion obligatoire de reliure par nom partout vs la reliure par nom une seule fois, pour charger le contenu d'un enregistrement portant le même nom et le faire uniquement au démarrage, où les erreurs d’exécution sont presque aussi bénignes que les erreurs de compilation? Quoi qu’il en soit, les moyens d’éviter le moindre lien contraignant par leur nom sont toujours intéressants, malgré l’obscurcissement que vous avez mentionné, alors je serais curieux d’entendre ce que vous avez en tête.
Mike Nakis
Oh, je suis complètement d'accord. Et, étant donné la nature du PO, je suggérerais simplement que cette réponse pourrait tirer avantage de remplacer "à tout prix" par "chaque fois que cela est possible et réalisable" ou quelque chose de similaire. ... Si j'avais plus de temps, par souci d'exhaustivité seulement, j'écrirais une réponse qui traite d'une sorte de non-sens de métaprogrammation ... mais ce n'est pas ce dont le PO (ou n'importe qui dans la plupart des cas) a probablement besoin . Cependant, une solution de métaprogamme s'alignerait davantage sur votre première déclaration en l'état.
svidgen
1
@ user469104 la différence réside dans le fait que les identifiants peuvent changer et que l'application continuera à charger toutes les lignes correctement et à effectuer toutes les comparaisons correctement. Aussi, vous êtes libre de code réusiner et les lignes renommage de quelque manière que vous aimez, et le seul endroit où il faut aller chercher des choses à fixer est dans le démarrage de l'application, et il a tendance à être très évident: Grape = fetchRow( Fruit.class, NameColumn, "Grape" ); Et si vous faites quelque chose de mal, AssertionErrorvous le saurez.
Mike Nakis
1
@Grahamparks pas plus qu'un enumaurait été une chaîne magique. Le point essentiel est la concentration de toutes les liaisons par nom dans un seul endroit , la validation de toutes ces informations lors du démarrage et la sécurité .
Mike Nakis
7

La vérification par rapport à la chaîne est la plus lisible, mais elle remplit une double fonction: elle est utilisée à la fois comme identifiant et comme description (qui peut changer pour des raisons non liées).

Je divise généralement les deux tâches en champs distincts:

id  code    description
 1  grape   Grapes
 2  apple   Apple

Où description peut changer (mais pas "Grapes" à "Banana"), mais le code n'est pas autorisé à changer, jamais.

Bien que cela soit principalement dû au fait que nos identifiants sont presque toujours générés automatiquement, ils ne conviennent donc pas. Si vous pouvez choisir les identifiants librement, vous pouvez peut-être garantir qu'ils sont toujours corrects et les utiliser.

Aussi, combien de fois quelqu'un modifie-t-il réellement "Grapes" en "Grape"? Peut-être que rien de tout cela n'est nécessaire.

RemcoGerlich
la source
8
Je ne pense pas que la solution soit encore plus redondante ...
Robbie Dee
4
J'ai également envisagé cette option et je l'ai essayée, mais c'est ce qui a fini par se produire: à un moment donné, "pomme" a dû être différenciée en "green_apple" et "red_apple". Mais parce que "pomme" était déjà utilisé dans une multitude de lieux dans le code, je ne pouvais pas le renommer et je devais donc avoir "pomme" et "pomme verte". Et en conséquence, le Sheldon en moi m'a empêché de dormir pendant plusieurs nuits jusqu'à ce que j'y aille et que tout ait été transformé en "Objets préchargés". (Voir ma réponse.)
Mike Nakis
1
J'aime vraiment vos objets préchargés, mais si votre "pomme" est différenciée, n'avez-vous pas à tout revoir de toute façon, quelle que soit la méthode que vous choisissez?
RemcoGerlich
Vous pourriez même avoir un tableau séparé pour le nom de la description, afin de prendre en charge l'internationalisation.
Erik Eidt
1
@MikeNakis et le refactoring est essentiellement une recherche et remplacement sur l'ensemble de votre base de code remplaçant Fruit.Apple par Fruit.GreenApple. Si j'utilise les valeurs Hardcoded String, je ferais une recherche et un remplacement sur l'ensemble du code afin de remplacer "apple" par "green_apple", ce qui revient à peu près au même. - Le refactoring ne fait que se sentir mieux, car l'EDI effectue le remplacement.
Falco
4

Ce que vous attendez ici, c'est que la logique de programmation s'adapte automatiquement à l'évolution des données. Les options statiques simples telles qu'Enum ne fonctionnent pas ici car vous ne pouvez pas ajouter d'énums supplémentaires au runtime.

Quelques motifs que j'ai vus:

  • Enums + default pour se protéger contre une toute nouvelle entrée de base de données qui gâcherait la journée de votre programme.
  • Encodage des actions à effectuer (logique biz) dans la base de données elle-même. Dans de nombreux cas, cela est très possible car de nombreuses logiques sont réutilisées. La mise en œuvre de la logique devrait être dans le programme.
  • Attributs / colonnes supplémentaires dans la base de données pour marquer la nouvelle valeur comme "à ignorer" dans le programme jusqu'à ce que le programme soit correctement déployé.
  • Échec des mécanismes rapides autour du chemin de code qui charge / recharge les valeurs de la base de données. (Si l'action correspondante n'est pas dans le programme ET qu'elle n'est pas marquée pour être ignorée, n'effectuez pas l'actualisation).

En général, j'aime bien que les données soient complètes en termes de référence aux actions que cela implique - même si les actions elles-mêmes peuvent être mises en œuvre ailleurs. Tout code déterminant des actions indépendantes des données vient de fragmenter votre représentation de données, ce qui risque fort de diverger et de conduire à des bogues.

Subu Sankara Subramanian
la source
4

Les stocker aux deux endroits (dans une table et dans un ENUM) n'est pas si mal. Le raisonnement est le suivant:

En les stockant dans une table de base de données, nous pouvons appliquer l'intégrité référentielle dans la base de données via des clés étrangères. Ainsi, lorsque vous associez une personne ou une entité quelconque à un fruit, il ne s'agit que d'un fruit qui existe dans la table de la base de données.

Les stocker en tant que ENUM est également logique car nous pouvons écrire du code sans chaînes magiques et le rendre plus lisible. Oui, ils doivent rester synchronisés, mais ce serait vraiment difficile d'ajouter une ligne à ENUM et une nouvelle instruction insert à la base de données.

Une chose, une fois qu'un ENUM est défini, ne change pas sa valeur. Par exemple, si vous aviez:

  • Pomme
  • Grain de raisin

NE PAS renommer Grape en Grapes. Ajoutez simplement un nouvel ENUM.

  • Pomme
  • Grain de raisin
  • les raisins

Si vous avez besoin de migrer des données, appliquez une mise à jour pour déplacer tout le raisin vers le raisin.

Jon Raynor
la source
Dans une étape ultérieure, j'ai travaillé dans des magasins où les valeurs de métadonnées ont un indicateur de suppression dans le tableau pour indiquer qu'elles ne doivent pas être utilisées (elles ont été obsolètes ou une version plus récente existe).
Robbie Dee
1

Vous avez raison de poser cette question, c’est une bonne question alors que vous essayez de vous défendre contre l’évaluation de conditions inexactes.

Cela dit, l’évaluation (vos ifconditions) ne doit pas nécessairement être au centre de la façon dont vous la contournez. Faites plutôt attention à la manière dont vous propagez les modifications susceptibles de générer un problème de "désynchronisation".

Approche de la chaîne

Si vous devez utiliser des chaînes, pourquoi ne pas exposer la fonctionnalité de modification de la liste via l'interface utilisateur? Concevez le système de manière à mettre Grapeà jour Grapes, par exemple, tous les enregistrements en cours de référencement Grape.

Approche d'identification

Je préférerais toujours faire référence à un identifiant, malgré la compromission d'une certaine lisibilité. The list may be added topeut être à nouveau quelque chose dont vous seriez averti si vous exposiez une telle fonctionnalité de l'interface utilisateur. Si vous êtes préoccupé par la réorganisation d'éléments modifiant l'identifiant, propagez à nouveau une telle modification vers tous les enregistrements dépendants. De même à ci-dessus. Une autre option (suivant la convention de normalisation appropriée, serait d’avoir une colonne enum / id et de faire référence à un FruitDetailtableau plus détaillé , qui contient une colonne 'Order' que vous pouvez rechercher).

De toute façon, vous pouvez voir que je suggère de contrôler l’amendement ou la mise à jour de votre liste. Que vous le fassiez par le biais d'un ORM ou de tout autre accès aux données, il est déterminé par les spécificités de votre technologie. Ce que vous êtes essentiellement en train de faire est d’exiger que les personnes qui s’éloignent de la DB pour de tels changements - ce qui me convient. La plupart des principaux CRM feront la même exigence.

JᴀʏMᴇᴇ
la source
1
Dans la base de données , l'identifiant numérique est en cours de stockage pour les enregistrements enfants, afin d'éviter ce problème. Cette question concerne l’interface avec un langage de programmation.
Clockwork-Muse
1
@ Clockwork-Muse - pour éviter quel problème? Cela n'a pas de sens.
JᴀʏMᴇᴇ
J'utilise assez souvent l'approche de l'ID, mais l'ID est verrouillé et ne peut pas changer. Bien sûr, la chaîne attachée le peut parce que les gens aiment souvent renommer les choses "camion" devient "camion", etc., alors que la chose elle-même (représentée par l'ID) ne change pas.
Brian Knoblauch
Si vous optez pour l’ID, comment gérez-vous les bases de données développement / production? Avec les ID auto-incrémentés, l'ajout d'éléments aux deux DB dans un ordre différent entraînera des ID différents.
Protecteur un
Ne doit pas être automatique cependant? Cela ne devrait pas être le cas, surtout s'il s'agit de la valeur entière de l'énum sous-jacent que nous utilisons.
JᴀʏMᴇᴇ
0

Un problème très commun. La duplication du côté client de données peut sembler violer les principes de DRY , mais cela est dû à la différence de paradigme entre les couches.

Avoir l'énumération (ou quoi que ce soit) en décalage avec la base de données n'est pas si rare en plus. Vous avez peut-être transféré une autre valeur dans une table de métadonnées pour prendre en charge une nouvelle fonctionnalité de rapport qui n'est pas encore utilisée dans le code côté client.

Cela se produit parfois aussi dans l'autre sens. Une nouvelle valeur enum est ajoutée côté client, mais la mise à jour de la base de données ne peut avoir lieu tant que l'administrateur de base de données n'a pas appliqué les modifications.

Robbie Dee
la source
Oui, vous avez décrit le problème. Quelle est votre solution?
Protecteur un
1
@Protectorone Vous supposez qu'il existe une solution miracle, hypothèse erronée selon mon expérience. Le mieux que vous puissiez espérer, c’est que certaines entités commerciales possèdent le domaine en question de sorte que vous puissiez au moins savoir quel côté est en retard - côté client ou côté base de données. La banque et la finance sont généralement très efficaces à cet égard, le secteur de la vente au détail étant nettement moins ...
Robbie Dee
0

En supposant que nous parlions de ce qui est essentiellement une recherche statique, la troisième option - l'énumération - est fondamentalement le seul choix sensé. C'est ce que vous feriez si la base de données n'était pas impliquée, alors c'est logique.

La question devient alors celle de savoir comment garder synchronisés les énumérations et les tables statiques / de recherche dans la base de données. Malheureusement, ce n’est pas un problème pour lequel je dispose d’une réponse complète.

Par choix, je fais toute la maintenance de mon schéma en code et je peux donc garder une relation entre la construction de l'application et la version attendue du schéma, il est donc simple de garder la recherche et l'enum synchronisés, faire. Ce serait mieux s'il était plus automatisé (et également un test d'intégration automatisé pour s'assurer que les énumérations et les correspondances aideraient), mais ce n'est pas quelque chose que j'ai implémenté.

Murph
la source
1
Je ne crois pas qu'il s'agisse de recherches statiques, sinon elles pourraient simplement être extraites de la base de données et utilisées telles quelles. Si j'ai bien compris, le problème réside dans l'application de la logique applicative en fonction de la valeur de recherche utilisée. Mais, à part cela, les enums sont généralement utilisés à cette fin.
Robbie Dee
Ok, j'ai besoin d'un meilleur terme "pour la recherche statique", le contexte que vous décrivez est ce que je voulais dire :) La clé est "statique" - ce sont des valeurs qui ne changent pas le problème mais pas l'intention) pour les valeurs existantes.
Murph