Est-ce une mauvaise pratique d'autoriser les champs définis par l'utilisateur?


17

D'une manière générale, est-il considéré comme une mauvaise pratique d'autoriser les champs créés par l'utilisateur dans une base de données pour une webapp?

Par exemple, je crée une webapp d'inventaire de maison pour ma femme, et elle va vouloir définir ses propres champs pour différents articles. J'avais l'intention de lui permettre de créer des catégories d'articles et d'ajouter des "fonctionnalités" à ces catégories. Les fonctionnalités ne seraient que des clés / valeurs stockées sous forme de chaînes. De cette façon, si elle avait une catégorie appelée "CD audio" par exemple, elle pourrait ajouter des fonctionnalités pour des trucs comme "artiste", "pistes", etc. Mais dans une autre catégorie comme "meubles", elle pourrait ajouter des fonctionnalités pour des trucs comme "matériel" "(bois, plastique, etc.). Ensuite, n'importe quel élément peut appartenir à une (ou plusieurs) catégories, en ajoutant ces fonctionnalités à l'élément.

Je peux voir des problèmes où la recherche par ces fonctionnalités nécessite des comparaisons de chaînes, il n'y a pas de validation des données, etc. comme nous allons. Dans mon exemple, c'est une petite base d'utilisateurs (2 d'entre nous) et la quantité d'enregistrements créés serait petite, donc pas trop mal.

D'une manière générale cependant, comment les gens gèrent-ils quelque chose comme ça dans la «vraie vie»?


4
Avez-vous envisagé d'utiliser une base de données orientée document comme MongoDB? Vous pouvez stocker un document par type qui agit comme un schéma qui peut également être édité (probablement manuellement, étant donné la petite échelle du projet).
Andy Hunt

@AndyBursh l'un des bits «amusants» avec les postgres actuels est le type de données «json» ( lien ). Une telle approche permettrait de stocker des champs spécifiés par l'utilisateur dans ces données, euh, documenter, euh, peu importe, puis d'utiliser le reste des champs pour les choses que vous voulez indexer correctement et autres. Bien que tout cela dépend de l'utilisation et qu'il est difficile de dire si cela fonctionnerait bien pour une application particulière ou non. Mais c'est quelque chose à savoir.

tous: grande discussion, merci pour toutes les informations! @AndyBursh J'ai entendu parler de MongoDB mais je ne l'ai jamais vraiment lu. Cela ressemble à un autre projet de maison pour expérimenter ...
zako42

Réponses:


19

Lorsque vous commencez à accéder aux «champs définis par l'utilisateur», comme on le trouve souvent dans les suiveurs de bogues, la gestion des ressources client et les outils commerciaux similaires, ils ne sont pas sauvegardés avec une table avec des champs bajillion (s'ils le sont, alors c'est probablement un problème de sa propre).

Au lieu de cela, vous trouvez des conceptions de table de valeur d'attribut d'entité et l'outil d'administration associé pour gérer les attributs valides.

Considérez le tableau suivant:

  + -------------- +
  | chose |
  | -------------- |
  | id |
  | type |
  | desc |
  | attr1 |
  | attr2 |
  | attr3 |
  | attr4 |
  | attr5 |
  + -------------- +

C'est après avoir ajouté quelques attributs. Au lieu de attr1faire semblant, il lit artistou tracksou genreou tout ce que la chose possède. Et au lieu de 5, que se passerait-il si c'était 50. C'est clairement ingérable. Elle nécessite également une mise à jour du modèle et un redéploiement de l'application pour gérer un nouveau champ. Pas idéal.

Considérez maintenant la structure de table suivante:

  + -------------- + + --------------- + + ------------- +
  | chose | | thing_attr | | attr |
  | -------------- | | --------------- | | ------------- |
  | id | <--- + | thing_id (fk) | +> | id |
  | type | | attr_id (fk) | + - + | nom |
  | desc | | valeur | | |
  + -------------- + + --------------- + + ------------- +

Vous avez votre truc avec ses champs de base. Vous avez deux autres tables. Un avec les attributs. Chaque champ est une ligne du attrtableau. Et puis il y a le thing_attravec une paire de clés étrangères se rapportant à la thingtable et à la attrtable. Et cela a alors un champ de valeur où vous stockez quelle que soit la valeur du champ pour cette entité.

Et maintenant, vous avez une structure où la table attr peut être mise à jour au moment de l'exécution et de nouveaux champs peuvent être ajoutés (ou supprimés) à la volée sans impact significatif sur l'application globale.

Les requêtes sont un peu plus complexes et la validation devient aussi plus complexe (soit des procédures stockées géniales ou tout côté client). C'est un compromis dans la conception.

Considérez également la situation où un jour vous devez effectuer une migration et vous revenez à l'application pour constater qu'il existe maintenant une demi-douzaine d'attributs de plus que le schéma que vous avez distribué à l'origine. Cela rend les migrations et les mises à niveau moches où la table de valeur d'attribut d'entité, lorsqu'elle est utilisée correctement, peut être plus propre. (Pas toujours, mais peut l'être.)


Existe-t-il des inconvénients à simplement modifier le schéma lors de l'exécution? Si l'utilisateur pense qu'une chose a besoin d'un nouvel attribut, il suffit d'ajouter dynamiquement une colonne à la table?

Si vous travaillez avec la saveur appropriée de la base de données nosql, vous pourriez probablement le faire (notez que la saveur appropriée du nosql pour cela serait probablement un magasin de valeurs-clés qui est, eh bien, la table EAV pour les relationnelles décrites ci-dessus) sans trop de peine. Cependant, il est livré avec tous les compromis pour nosql qui sont décrits ailleurs en détail.

Si vous travaillez à la place sur une base de données relationnelle - vous devez avoir le schéma. L'ajout dynamique de la colonne signifie que certains sous-ensembles des éléments suivants sont vrais:

  • Vous faites de la programmation de métadonnées. Au lieu de pouvoir mapper proprement cette colonne à ce champ avec un ORM sympa, vous faites probablement des choses comme select *, puis faites du code complexe pour découvrir ce que sont réellement les données (voir le ResultSetMetaData de Java ), puis stockez cela dans une carte ( ou un autre type de données - mais pas de beaux champs dans le code). Cela jette alors un peu de sécurité de type et de faute de frappe que vous avez avec l'approche traditionnelle.
  • Vous avez probablement abandonné l'ORM. Cela signifie que vous écrivez SQL brut pour tout le code au lieu de laisser le système faire le travail pour vous.
  • Vous avez renoncé à faire des mises à niveau propres. Que se passe-t-il lorsque le client ajoute un champ avec un nom que votre prochaine version utilise également? Dans le site de matchmaking, la mise à niveau qui souhaite ajouter un hasdatechamp pour stocker un horodatage a déjà été définie comme hasdateavec un booléen pour une correspondance réussie ... et votre mise à niveau s'arrête.
  • Vous êtes confiant que le client ne casse pas le système en utilisant un mot réservé qui casse aussi vos requêtes ... quelque part.
  • Vous vous êtes lié à une seule marque de base de données. Le DDL de différentes bases de données est différent. Les types de bases de données en sont l'exemple le plus simple. varchar2vs textet similaires. Votre code pour ajouter la colonne fonctionnerait sur MySQL mais pas Postgres ou Oracle ou SQL Server.
  • Est - ce que vous faites confiance au client d'ajouter réellement les données bien ? Bien sûr, l'EAV est loin d'être idéal, mais maintenant vous avez des noms de table obscurs horribles que vous, le développeur, n'avez pas ajoutés, avec le mauvais type d'index (le cas échéant), sans aucune contrainte ajoutée dans le code là où il le faut être et ainsi de suite.
  • Vous avez accordé des privilèges de modification de schéma à l'utilisateur exécutant l'application. Little Bobby Drop Tables n'est pas possible lorsque vous êtes limité à SQL plutôt qu'à DDL (bien sûr, vous pouvez le faire à la delete * from studentsplace, mais vous ne pouvez pas vraiment gâcher la base de données de mauvaises manières). Le nombre de choses qui peuvent mal tourner avec l'accès au schéma à la suite d'un accident ou d'une activité malveillante.

Cela se résume vraiment à «ne pas le faire». Si vous le voulez vraiment, optez pour un modèle connu de la structure de la table EAV ou une base de données entièrement dédiée à cette structure. Ne laissez pas les gens créer des champs arbitraires dans une table. Les maux de tête n'en valent pas la peine.


4
Vous avez également réinventé la base de données.
user253751

1
@immibis a ajouté une couche dans laquelle l'utilisateur peut administrer sans modifier le reste de la base de données ou nécessiter un redéploiement pour mettre à jour le modèle.

1
@immibis EAV fait débat depuis des années dans les cercles de bases de données relationnelles. En théorie, ce n'est pas nécessaire, mais en pratique, vous ne pouvez pas faire certaines choses sans cela.
Ross Patterson

1
@ShivanDragon qui va à l'approche NoSQL. Le magasin de documents stocke simplement des documents et n'impose pas de schéma. En tant que tel, l'ajout et la suppression de champs et l'analyse des documents sont complètement en dehors de la portée de la base de données elle-même (et vous avez écrit votre modèle pour s'adapter à cela). Il s'agit d'un ensemble de compromis complètement différent des compromis de la base de données relationnelle pour une structure EAV.


5

Il est difficile de bien faire cela.

Pour une application unique comme celle que vous planifiez, vous pouvez bien sûr simplement ajouter une colonne pour chaque champ et fournir une interface utilisateur qui rend la définition de champ par des utilisateurs non formés plus sûre que de leur donner une ligne de commande SQL. Ou vous pourriez suivre le modèle redouté Entity-Attribute-Value , qui est une réponse classique, quoique quelque peu effrayante, à ce genre de problème. La construction de l'interface utilisateur pour définir les champs EAV est généralement beaucoup plus complexe que pour les colonnes de base de données, et les requêtes peuvent devenir assez floues, mais pour un grand nombre de champs ( c'est -à- dire , les schémas à matrice très clairsemée), cela peut être le seul moyen d'obtenir le travail fait.


En résumé: petit projet == KISS. Agile jusqu'au sol.
Encaitar

Le problème avec les mises à jour de table de base de données est que, selon la quantité de données et les index requis (les champs personnalisés nécessitent souvent des fonctionnalités de recherche), la requête de modification de table peut prendre un temps gigantesque. Pour faire court, MySQL et les autres bases de données relationnelles ne sont tout simplement pas un bon support pour ce type d'exigence.
Oddman

0

Je suis venu une croix quelque chose de similaire récemment.

J'ai fait 2 tableaux.

1: table Objects 
    Id , name, type

Il est tous vos objets. Vous en définissez le nom.

Et un type de cet objet: - pour moi, les types disponibles étaient inventaire, inventaire_item, bureau.

Et la configuration habituelle était n éléments sont un enfant ou un inventaire qui est également un enfant de bureau et j'ai utilisé une table de jointure pour joindre des objets les uns aux autres

2 table settings 
     organization_Id , title, value , type

Le tableau des paramètres contient chaque nom de champ pour ce type d'objet spécifique et la valeur en valeur.

Exemple de propriétés de bureau

Lieu, téléphone, horaires

Et pour les articles

  • Montant
  • Prix
  • code à barre

Etc, toutes ces propriétés sont appliquées par votre modèle et enregistrées dans le tableau des paramètres en tant que lignes distinctes (mais utilisez remplacer et non insérer pour éviter plusieurs lignes pour le même champ)

Donc, quand je veux un bureau, je le charge facilement avec toutes ses relations et ses paramètres dans lesquels les paramètres object_I'd (objets demandés)

Après cela, je fais pivoter toutes les lignes des paramètres et c'est tout.

Et dans le cas où je voulais qu'un paramètre soit spécifique à un article dans un inventaire (non global), je définis object_I'd = je serais dans la table des relations object_objects et je définirai settings.type = relation_setting

J'espère que vous comprenez ce que je veux dire je vais essayer de reformater la réponse quand j'arriverai à un ordinateur portable


2
Conseil de pro - ne postez pas sur ce forum depuis votre téléphone. La correction automatique rend les parties de votre message illisibles.
BobDalgleish

Haha belle observation :)
Zalaboza

0

Est-ce une mauvaise pratique d'autoriser les champs définis par l'utilisateur?

Non, ce n'est pas une mauvaise pratique. C'est assez courant. En termes OO, cela s'appelle l'héritage. Vous avez un inventaire de classe de base et deux classes héritées AudioCD et meubles.

D'une manière générale cependant, comment les gens gèrent-ils quelque chose comme ça dans la «vraie vie»?

Vous devez décider de la façon dont InventoryItem, AudioCD et les meubles sont stockés dans la base de données.

Si easy-query est le plus important pour vous et que db-space / normalisation n'a pas d'importance, vous implémenterez le schéma "table par hiérarchie".

Si l'espace / la normalisation est le plus important pour vous et que les requêtes plus compliquées ne posent aucun problème, vous implémenterez le schéma "table par type".

Pour plus de détails, reportez-vous à la section dotnet table-by-type-vs-table-per-hierarchy-inheritance ou java hibernate héritage .


Je ne sais pas si cela répond à la question. L'utilisateur ne modifie pas le code pour créer de nouvelles classes
Colin D
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.