Il miglior modello per rappresentare da molte a molte relazioni con attributi in MongoDB

Qual è il modo più ‘mongo’ di rappresentare relazioni molti-a-molti che hanno attributi?

Quindi per esempio:

Intro


Tabelle MYSQL

people => firstName, lastName, ...

Movies => name, length ..

peopleMovies => movieId, personId, language, role

Soluzione 1


Incorpora le persone nei film …?

In MongoDB capisco che è bello denormalize and embed ma non voglio embed persone nei film, ma non ha alcun senso logico. Perché le persone non devono necessariamente appartenere ai film.

Soluzione 2


People e Movies saranno due raccolte separate. People => incorpora [{movieId: 12, personId: 1, language: "English", role: "Main"} ...]

Movies => embed [{movieId: 12, personId: 1, language: "English", role: "Main"} ...]

Il problema con questa soluzione è che quando si desidera aggiornare il role una persona per un movie specifico movie è necessario eseguire due query di aggiornamento per garantire che i dati siano sincronizzati in entrambe le raccolte.

Soluzione 3


Possiamo anche fare qualcosa di molto più relazionale e finire con tre collezioni

People => firstName, lastName, ... Movies => name, length .. Castings => movieId, personId, language, role

Il problema con questo è che a causa della mancanza di una dichiarazione di join in MongoDB, occorrerebbero 3 queries per andare da persone -> film e viceversa.

Ecco la mia domanda, quali sono altri modi per modellare qualcosa di simile in MongoDB e in un modo più NoSQL . In termini di soluzioni fornite, quale sarebbe il migliore in termini di prestazioni e convenzioni in mongo.

In molti modi l’API di meteora incoraggia i documenti relazionali piatti, tuttavia MongoDB è un archivio dati non relazionale. Questo conflitto, purtroppo, è stato lasciato come un esercizio da risolvere per lo sviluppatore.

La nozione di struttura dello schema e join è un argomento enorme da trattare all’interno di una singola risposta, quindi cercherò di essere il più sintetico ansible.

Ragioni per cui dovresti scegliere un modello relazionale

Supponi di avere commenti e postare dati. Considera cosa succederebbe se tu inserissi commenti nei tuoi post.

  • DDP funziona su documenti. Tutti i commenti verranno inviati ogni volta che viene aggiunto un nuovo commento nello stesso post.

  • allow e deny regole operano sui documenti. Potrebbe essere irragionevole aspettarsi che le stesse regole si applichino simultaneamente a post e commenti.

  • Le pubblicazioni tendono ad avere più senso in termini di collezioni. Nello scenario sopra riportato, non è stato facile pubblicare un elenco di commenti indipendentemente dai loro post.

  • I database relazionali esistono per buone ragioni. Uno di questi è evitare il problema di modifica multipla inerente alla seconda soluzione.

Ragioni per cui dovresti scegliere un modello incorporato

  • I join non sono supportati nativamente da MongoDB e non esiste un pacchetto principale per produrre un join reattivo.

raccomandazioni

Usa la tua terza soluzione. Nella mia esperienza, le ragioni per la scelta di un modello relazionale superano di gran lunga le restrizioni imposte dall’archivio dati. Ovviamente superare la mancanza di join non è facile, ma è probabile che il dolore sia isolato solo da una manciata di funzioni di pubblicazione. Ecco alcune risorse che consiglio vivamente:

  • Come pubblicare una relazione many-to-many su EventedMind. Chris copre dettagliatamente il tuo caso d’uso esatto, tuttavia fa manualmente il join reattivo con osservando i callback, che non consiglio.

  • Reattivo si unisce alla meteora dalla Discover Meteor Encyclopedia . Questo copre le basi di come e perché si dovrebbe fare un join reattivo.

  • Il capitolo denormalizzazione di Discover Meteor . Questo copre molti dei punti che ho fatto sopra e parla anche di quando e come denormalizzare alcuni dei tuoi dati.

  • Puoi utilizzare Pubblica con relazioni per unire i tuoi dati. I pacchetti alternativi includono: pubblicazione intelligente , pubblicazione composita e pubblicazione semplice .

Se hai bisogno di ulteriori informazioni oltre a questo, si prega di commentare qui sotto e aggiornerò la mia risposta.

Penso che dovresti denormalizzare le tue collezioni. Il punto importante quando si progettano raccolte e documenti MongoDB è pensare alle proprie opinioni. Di quali dati hai bisogno per visualizzare la tua vista? L’idea è che dovresti provare ad avere quei dati come parte del tuo documento.

Ad esempio, nel tuo caso, probabilmente hai una visione per i Movies cui desideri visualizzare le informazioni su un film. Ma quella pagina di un film ha probabilmente bisogno solo di informazioni di base su ogni persona (nome, cognome, URL della foto). Non tutte le altre cose. E viceversa, la pagina di una persona probabilmente elenca tutti i film, ma anche di nuovo è necessario solo un sottoinsieme di informazioni su ciascun film, come titolo, anno e URL della foto del poster.

Quindi un’opzione sarebbe avere due raccolte, ma poi incorporare (denormalizzare) solo quei pochi campi necessari tra le raccolte. Quindi, ad esempio, la raccolta di Movies avrebbe una people campo che sarebbe una serie di documenti secondari. E la raccolta People avrebbe un campo di movies che sarebbe una serie di documenti secondari, con quei campi aggiuntivi che si desidera specificare il ruolo e così via.

Quindi i documenti potrebbero essere qualcosa come il seguente. Per i film:

 { _id: "AAA", title: "...", year: 2015, length: 120, posterURL: "...", people: [ { person: { _id: "BBB", firstName: "...", lastName: "...", photoURL: "..." }, role: "..." } ] } 

Per le persone:

 { _id: "BBB", firstName: "...", lastName: "...", photoURL: "...", movies: [ { _id: "AAA", title: "...", year: 2015, posterURL: "..." } ] } 

Naturalmente, la domanda è come mantenere quei campi sincronizzati. Cosa succede se si aggiorna l’URL della foto del poster di un film, si desidera che venga aggiornato anche in tutti i documenti Persona. Per risolvere questo problema, abbiamo sviluppato PeerDB , un pacchetto per definire le relazioni tra le raccolte che poi assicura che siano mantenute in sincronia.

Quindi nel tuo caso, avrei definito tali raccolte in PeerDB, in CoffeeScript:

 class People extends Document @Meta name: 'People' class Movies extends Document @Meta name: 'Movies' fields: => people: [ person: @ReferenceField People, ['firstName', 'lastName', 'photoURL'], true, 'movies', ['title', 'year', 'posterURL'] ] 

In breve, questa definizione dice che il campo people.person dovrebbe essere un riferimento alla collezione People e mantenuto sincronizzato per firstName , lastName , photoURL . Inoltre, un campo di riferimento inverso dovrebbe essere posterURL nei documenti People sotto i movies campo con title , year , posterURL .

Abbastanza semplice. Ma ci sono alcuni aspetti negativi. Gli array potrebbero diventare molto grandi (forse non nel caso di film e persone, ma per altri dati) che potrebbero rendere i documenti troppo grandi per i limiti di documenti per MongoDB (al momento 16 MB). Inoltre, se osservi, vedrai che per i documenti di People non ci sono informazioni sul ruolo nell’elenco dei film. Questo perché il ruolo non fa parte del documento di riferimento, ma è qualcosa che si trova accanto al riferimento. Cosa succede se si desidera visualizzare il ruolo del film in cui si trovava una persona nella pagina / vista persona?

Quindi, forse sarebbe meglio avere tre raccolte, una per le informazioni di base sui film, un’altra per le persone e quindi una raccolta per la relazione tra persone e film. Quindi i dati potrebbero essere qualcosa di simile, per i film:

 { _id: "AAA", title: "...", year: 2015, length: 120, posterURL: "..." } 

Per le persone:

 { _id: "BBB", firstName: "...", lastName: "...", photoURL: "..." } 

Per il casting:

 { _id: "...", movie: { _id: "AAA", title: "...", year: 2015, posterURL: "..." }, person: { _id: "BBB", firstName: "...", lastName: "...", photoURL: "..." }, role: "..." } 

E definizioni PeerDB:

 class People extends Document @Meta name: 'People' class Movies extends Document @Meta name: 'Movies' class Casting extends Document @Meta name: 'Casting' fields: => person: @ReferenceField People, ['firstName', 'lastName', 'photoURL'] movie: @ReferenceField Movies, ['title', 'year', 'posterURL'] 

PeerDB si assicurerebbe quindi che le cose fossero mantenute sincronizzate. Rimuoverà anche il documento di casting se un film o una persona viene cancellato dal database.

Ciò consente quindi di creare una pubblicazione Meteor che sia efficiente e non richiede alcuna costruzione dynamic di query correlate. Basta pubblicare la collezione Casting e questo è quanto. Puoi anche eseguire una query in determinate condizioni. Ad esempio, vuoi visualizzare tutti i registi ordinati per firstName e lastName e i loro filmati? Possibile con una sola query.