Principes de modélisation des documents CouchDB

J’ai une question à laquelle je tente de répondre depuis un certain temps, mais je n’arrive pas à comprendre:

Comment concevez-vous ou divisez-vous des documents CouchDB?

Prenez un article de blog par exemple.

La façon semi “relationnelle” de le faire serait de créer quelques objects:

  • Poster
  • Utilisateur
  • Commentaire
  • Marque
  • Fragment

Cela fait beaucoup de sens. Mais j’essaie d’utiliser couchdb (pour toutes les raisons, c’est génial) de modéliser la même chose et cela a été extrêmement difficile.

La plupart des articles publiés sur le blog vous donnent un exemple simple de la manière de procéder. Ils le divisent essentiellement de la même manière, mais disent que vous pouvez append des propriétés “arbitraires” à chaque document, ce qui est vraiment bien. Donc, vous auriez quelque chose comme ça dans CouchDB:

  • Post (avec des balises et des snippets “pseudo” modèles dans la doc)
  • Commentaire
  • Utilisateur

Certaines personnes diraient même que vous pourriez lancer le commentaire et l’utilisateur, vous auriez donc ceci:

post { id: 123412804910820 title: "My Post" body: "Lots of Content" html: "

Lots of Content

" author: { name: "Lance" age: "23" } tags: ["sample", "post"] comments { comment { id: 93930414809 body: "Interesting Post" } comment { id: 19018301989 body: "I agree" } } }

Cela a l’air très bien et est facile à comprendre. Je comprends également comment vous pouvez écrire des vues qui n’extraient que les commentaires de tous vos documents Post, pour les intégrer aux modèles de commentaires, même avec les utilisateurs et les balises.

Mais alors je pense, “pourquoi ne pas simplement mettre tout mon site dans un seul document?”:

 site { domain: "www.blog.com" owner: "me" pages { page { title: "Blog" posts { post { id: 123412804910820 title: "My Post" body: "Lots of Content" html: "

Lots of Content

" author: { name: "Lance" age: "23" } tags: ["sample", "post"] comments { comment { id: 93930414809 body: "Interesting Post" } comment { id: 19018301989 body: "I agree" } } } post { id: 18091890192984 title: "Second Post" ... } } } } }

Vous pourriez facilement faire des vues pour trouver ce que vous vouliez avec cela.

Ensuite, la question que je me pose est la suivante: comment déterminez-vous le moment de diviser le document en documents plus petits ou quand faire des “RELATIONS” entre les documents?

Je pense qu’il serait beaucoup plus “orienté object”, et plus facile à mapper aux objects de valeur, si elle était divisée comme suit:

 posts { post { id: 123412804910820 title: "My Post" body: "Lots of Content" html: "

Lots of Content

" author_id: "Lance1231" tags: ["sample", "post"] } } authors { author { id: "Lance1231" name: "Lance" age: "23" } } comments { comment { id: "comment1" body: "Interesting Post" post_id: 123412804910820 } comment { id: "comment2" body: "I agree" post_id: 123412804910820 } }

… mais ça commence à ressembler plus à une firebase database relationnelle. Et souvent, j’hérite de quelque chose qui ressemble au “site entier dans un document”, il est donc plus difficile de le modéliser avec des relations.

J’ai lu beaucoup de choses sur comment / quand utiliser les bases de données relationnelles et les bases de données de documents, ce n’est donc pas le problème principal ici. Je me demande simplement quelle est la bonne règle / le principe à appliquer lors de la modélisation des données dans CouchDB.

Un autre exemple concerne les fichiers / données XML. Certaines données XML ont des niveaux d’imbrication supérieurs à 10+ et j’aimerais visualiser cela en utilisant le même client (Ajax on Rails ou Flex, par exemple) que je voudrais rendre JSON à partir d’ActiveRecord, de CouchRest ou de tout autre object. Parfois, je reçois d’énormes fichiers XML qui sont la structure entière du site, comme celui ci-dessous, et je devrais les mapper sur les objects de valeur à utiliser dans mon application Rails. :

              

Les questions générales sur CouchDB sont donc les suivantes:

  1. Quelles règles / principes utilisez-vous pour répartir vos documents (relations, etc.)?
  2. Est-il correct de mettre le site entier en un seul document?
  3. Si c’est le cas, comment gérez-vous la sérialisation / désérialisation des documents avec des niveaux de profondeur arbitraires (comme l’exemple de json ci-dessus ou l’exemple xml)?
  4. Ou ne les transformez-vous pas en VO, décidez-vous simplement que “celles-ci sont trop nestedes dans Object-Relational Map, alors je vais simplement y accéder en utilisant les méthodes XML / JSON brutes”?

Merci beaucoup pour votre aide, la question de la répartition de vos données avec CouchDB a été difficile pour moi de dire “voici comment je devrais le faire à partir de maintenant”. J’espère y arriver bientôt.

J’ai étudié les sites / projets suivants.

  1. Données hiérarchiques dans CouchDB
  2. CouchDB Wiki
  3. Sofa – App CouchDB
  4. CouchDB Le Guide Définitif
  5. PeepCode CouchDB Screencast
  6. CouchRest
  7. CouchDB README

… mais ils n’ont toujours pas répondu à cette question.

    Il y a déjà eu de très bonnes réponses à cette question, mais je voulais append quelques fonctionnalités plus récentes de CouchDB au mélange d’options pour travailler avec la situation originale décrite par viatropos.

    Le point clé sur lequel diviser les documents est l’endroit où il peut y avoir des conflits (comme mentionné précédemment). Vous ne devez jamais conserver des documents massivement enchevêtrés dans un seul document car vous obtiendrez un chemin de révision unique pour les mises à jour sans aucun lien (ajout de commentaires ajoutant par exemple une révision à l’intégralité du document de site). La gestion des relations ou des connexions entre divers documents plus petits peut être source de confusion au début, mais CouchDB offre plusieurs options pour combiner des éléments disparates en réponses uniques.

    Le premier grand est la collation de vues. Lorsque vous émettez des paires clé / valeur dans les résultats d’une requête map / Reduce, les clés sont sortingées en fonction du classement UTF-8 (“a” est précédé de “b”). Vous pouvez également générer des clés complexes à partir de votre carte / réduire en tant que tableaux JSON: ["a", "b", "c"] . Faire cela vous permettrait d’inclure un “arbre” de sortes construit à partir de clés de tableau. En utilisant votre exemple ci-dessus, nous pouvons générer le post_id, puis le type de chose que nous référençons, puis son identifiant (si nécessaire). Si nous affichons ensuite l’id du document référencé dans un object de la valeur renvoyée, nous pouvons utiliser le paramètre de requête ‘include_docs’ pour inclure ces documents dans la sortie map / Reduce:

     {"rows":[ {"key":["123412804910820", "post"], "value":null}, {"key":["123412804910820", "author", "Lance1231"], "value":{"_id":"Lance1231"}}, {"key":["123412804910820", "comment", "comment1"], "value":{"_id":"comment1"}}, {"key":["123412804910820", "comment", "comment2"], "value":{"_id":"comment2"}} ]} 

    Demander cette même vue avec ‘? Include_docs = true’ appenda une clé ‘doc’ qui utilisera le ‘_id’ référencé dans l’object ‘value’ ou si celui-ci n’est pas présent dans l’object ‘value’, il utilisera le ‘_id’ du document à partir duquel la ligne a été émise (dans ce cas le document ‘post’). Veuillez noter que ces résultats incluraient un champ “id” faisant référence au document source à partir duquel l’émission a été effectuée. Je l’ai laissé pour l’espace et la lisibilité.

    Nous pouvons alors utiliser les parameters ‘start_key’ et ‘end_key’ pour filtrer les résultats en une seule donnée:

      ? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] 

    Ou même extraire spécifiquement la liste pour un certain type:

      ? start_key = ["123412804910820", "comment"] & end_key = ["123412804910820", "commentaire", {}] 

    Ces combinaisons de parameters de requête sont possibles car un object vide (” {} “) est toujours en bas du classement et null ou “” en haut.

    La deuxième addition utile de CouchDB dans ces situations est la fonction _list. Cela vous permettrait d’exécuter les résultats ci-dessus via un système de template (si vous voulez HTML, XML, CSV ou autre), ou de générer une structure JSON unifiée si vous souhaitez pouvoir demander le contenu d’un article entier (y compris auteur et données de commentaire) avec une seule requête et renvoyé sous la forme d’un document JSON unique correspondant à ce dont votre code client / interface utilisateur a besoin. Cela vous permettrait de demander le document de sortie unifié de la publication de cette manière:

      / db / _design / app / _list / posts / unified ?? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] & include_docs = true 

    Votre fonction _list (dans ce cas nommée “unifiée”) prendrait les résultats de la vue map / Reduce (dans ce cas-ci nommée “posts”) et les exécuterait via une fonction JavaScript qui renverrait la réponse HTTP dans le type de contenu que vous besoin (JSON, HTML, etc.).

    En combinant ces éléments, vous pouvez diviser vos documents à tous les niveaux que vous trouvez utiles et «sûrs» pour les mises à jour, les conflits et la réplication, puis les regrouper au besoin.

    J’espère que cela pourra aider.

    Le livre dit, si je me souviens bien, de dénormaliser jusqu’à ce que “ça fasse mal”, tout en gardant à l’esprit la fréquence à laquelle vos documents pourraient être mis à jour.

    1. Quelles règles / principes utilisez-vous pour répartir vos documents (relations, etc.)?

    En règle générale, j’inclus toutes les données nécessaires pour afficher une page concernant l’élément en question. En d’autres termes, tout ce que vous imprimeriez sur un bout de papier réel que vous transmetsortingez à quelqu’un. Par exemple, un document de cotation d’actions inclurait le nom de la société, l’échange, la devise, en plus des numéros; un document contractuel inclurait les noms et adresses des contreparties, toutes les informations sur les dates et les signataires. Mais les cotations boursières à partir de dates distinctes formeraient des documents distincts, des contrats distincts formeraient des documents distincts.

    1. Est-il correct de mettre le site entier en un seul document?

    Non, ce serait idiot, car:

    • il faudrait lire et écrire tout le site (le document) sur chaque mise à jour, ce qui est très inefficace;
    • vous ne bénéficieriez d’aucun cache de vue.

    Je sais que c’est une vieille question, mais je l’ai trouvée en essayant de trouver la meilleure approche à ce même problème. Christopher Lenz a écrit un article sur les méthodes de modélisation des “jointures” dans CouchDB . Un de mes points forts était: “La seule façon d’append des données connexes sans conflit consiste à les mettre dans des documents distincts.” Donc, par souci de simplicité, vous voudrez vous pencher vers la “dénormalisation”. Mais vous rencontrerez une barrière naturelle en raison d’écritures contradictoires dans certaines circonstances.

    Dans votre exemple de publications et commentaires, si un seul article et tous ses commentaires étaient regroupés dans un seul document, deux personnes essayant de publier un commentaire en même temps (contre la même révision du document) provoqueraient un conflit. Cela serait encore pire dans votre scénario “site entier dans un seul document”.

    Donc, je pense que la règle de base serait de “dénormaliser jusqu’à ce que cela fasse mal”, mais le point où cela va “nuire” est la probabilité élevée que plusieurs modifications soient publiées contre la même révision d’un document.

    Je pense que la réponse de Jake est l’un des aspects les plus importants du travail avec CouchDB, qui peut vous aider à prendre la décision concernant la scope: les conflits.

    Dans le cas où vous avez des commentaires en tant que propriété de tableau du message lui-même, et que vous avez simplement une firebase database avec un tas de documents “post” énormes, comme Jake et d’autres l’ont fait remarquer, vous pouvez imaginer un scénario sur un article de blog très populaire où deux utilisateurs soumettent des modifications au document de publication simultanément, entraînant une collision et un conflit de version pour ce document.

    ASIDE: Comme le souligne cet article , considérez également que chaque fois que vous demandez / mettez à jour ce document, vous devez obtenir / définir le document dans son intégralité, en faisant circuler des documents massifs représentant le site entier ou un article avec beaucoup de documents. des commentaires à ce sujet peut devenir un problème que vous voudriez éviter.

    Dans le cas où les messages sont modélisés séparément des commentaires et que deux personnes soumettent un commentaire sur une histoire, celles-ci deviennent simplement deux documents de «commentaire» dans cette firebase database, sans conflit. juste deux opérations PUT pour append deux nouveaux commentaires à la firebase database “comment”.

    Ensuite, pour écrire les vues qui vous renvoient les commentaires pour une publication, vous devez passer le postID et émettre tous les commentaires faisant référence à cet identifiant de publication parent, sortingés dans un ordre logique. Peut-être que vous passez même quelque chose comme [postID, byUsername] comme clé de la vue «commentaires» pour indiquer la publication parent et comment vous voulez que les résultats soient sortingés ou quelque chose du genre.

    MongoDB gère les documents un peu différemment, ce qui permet de créer des index sur des sous-éléments d’un document. Vous pouvez donc voir la même question sur la liste de diffusion MongoDB et quelqu’un qui dit “ne fait que les commentaires de la publication parent”.

    En raison du locking en écriture et de la nature de maître unique de Mongo, le problème de révision conflictuel de deux personnes qui ajoutent des commentaires ne surgit pas et la capacité d’interrogation du contenu, comme mentionné, n’est pas trop faible à cause de index.

    Cela étant dit, si vos sous-éléments dans l’une ou l’autre des bases de données vont être énormes (par exemple, des dizaines de milliers de commentaires), je crois que les deux camps recommandent de créer ces éléments séparés; J’ai certainement vu cela être le cas avec Mongo, car il existe des limites supérieures à la taille d’un document et de ses sous-éléments.