Principes

et

Bonnes pratiques

MongoDB vs mySQL

Base NOSQL

orientée document sans relationnel

Vocabulaire

  • Base <=> Base
  • Collection <=> Table
  • Document / Objet <=> Ligne / Enregistrement
  • Champ <=> Colonne
  • Sous document ou Lien <=> Jointure

Orientée document

Document <=> JSON avec un "_id"

{
    "_id" : ObjectId("5811bac48b0cf22ea39e16ec"),
    ...
}
{
    "_id" : "toto",
    ...
}
{
    "_id" : 42,
    ...
}

Orientée document

  • Pas de relationnel
  • Pas de schéma
    (enfin pas obligatoire)
  • Pas de transactionnel
    (mais ça va venir dans certaines conditions)

Pas de relationnel

Pourtant l’agrégation et l'association sont des concepts de base des relations entre objets

Pas de relationnel

Mais on peut avoir des objets composés

{
    _id: "564f36c87dcbd96cf6f3e2ea",
    name: "Tactilia",
    address: {
        city: "Saint Grégoire"
        ...
    },
    ...
}

Pas de relationnel

Mais on peut mettre des références vers un objet

{
    _id: "564f36c87dcbd96cf6f3e2ea",
    name: "Tactilia",
    employees: [
        "/employees/564f36c87dcbd07cf6f3e2ea",
        ...
    ],
    logo: "564f36c87dcbd97cf6f3e2ea",
    ...
}

L'aggrégation est à

gérer à la main

Voir annexes

Pas de schéma

On peut y mettre n'importe quel JSON (de moins de 16Mo)

=> mongo ne gère aucun schéma

=> c'est à l'utilisateur de gérer

Pas de schéma

Du coup on peut avoir plusieurs types d'objets qui cohabitent dans la même collection !

{
    "name": "Voiture",
    "plate": "AR-456-YI",
    "nextControlDate": ISODate("2017-10-01T00:00:00.000Z"),
    "nbSeats": 5
}
{
    "name": "Camion",
    "plate": "AD-123-PS",
    "nextControlDate": "none",
    "capacity": 20
}

Pas de schéma

Règles de bon sens :

  • On met une collection par type
    => On sait a priori ce qui est stocké
  • On identifie les types
    => explications ci-après

Les requêtes

Find & Aggregate

Les requêtes

les bases

Le langage de requête est l'ecmascript

(alias javascript)

Insert

  INSERT INTO Contacts (First_Name, Last_Name)
  VALUES ('John','Doe');
  db.Contacts.insert(
    { 
      First_Name: "John", 
      Last_Name : "Doe" 
    }
  )

Find

  SELECT *
  FROM Contacts 
  WHERE First_Name = 'John';
  db.Contacts.find(
    { First_Name: "John" }
  )
{ 
  "_id": ObjectId("58776b8643ab721e65d744ba"), 
  "First_Name": "John",
  "Last_Name": "Doe"
}

Premier argument :
la requête

Findone

  SELECT *
  FROM Contacts 
  WHERE First_Name = 'John';
  db.Contacts.findone(
    { First_Name: "John" }
  )
{ 
  "_id": ObjectId("58776b8643ab721e65d744ba"), 
  "First_Name": "John",
  "Last_Name": "Doe"
}

Find (dans un sous objet)

  SELECT Company.Name
  FROM Company
  LEFT JOIN Address
  ON Address.OwnerId = Company.ID
  WHERE City REGEXP 'Saint.*';
  db.Contacts.find(
    { "address.city": { $regex: /Saint.*/g } }, 
    { name: 1 }
  )
{ 
  "_id": ObjectId("58776b8643ab721e65d744ba"), 
  "name": "Yieloo"
}

Second argument :
la projection (select)

Find (avec projection)

  SELECT Last_Name 
  FROM Contacts 
  WHERE First_Name = 'John';
  db.Contacts.find(
    { First_Name: "John" }, 
    { Last_Name: 1 }
  )
{ 
  "_id": ObjectId("58776b8643ab721e65d744ba"), 
  "Last_Name": "Doe"
}

Find (opérateurs)

db.Contacts.find(
    { First_Name: { $in: ["John", "Jane"] } }
)
db.Equipments.find(
    { $or: [ { nextControlDate: "none" }, { nextControlDate: { $gt: new Date() } } ] }
)
db.Equipments.find(
    { nbSeats: { $exists: true } }
)

Update

    UPDATE Contacts
    SET First_Name = 'Jane'
    WHERE Last_Name = 'Doe';
    db.Contacts.update(
        { Last_Name: "Doe" }, 
        { $set: { First_Name: "Jane" } }
    )
    db.Contacts.update(
        { Last_Name: "Doe" }, 
        { First_Name: "Jane" }
    )

Second argument :
la modification à effectuer

Premier argument :
la requête

Update (opérateurs)

  • Champs :  $set, $inc, $rename, $currentDate...
  • Tableaux : $addToSet, $push, $pop, $each, $sort,...
  • Bits : $bit​
db.Contacts.update(
    {},
    { $rename: { First_Name: "firstName", Last_Name: "lastName" } },
    { multi: true }
)
db.Contacts.update(
    { "_id": ObjectId("58776b8643ab721e65d744ba") },
    { $push: { score: 42 } },
    { upsert: true }
)
db.Contacts.update(
    { "_id": ObjectId("58776b8643ab721e65d744ba") },
    { 
        $push: { score: 42, game: "slot" },
        $currentDate: { "last.play": true },
        $set: { "last.result": "win" },
        $inc: { credits: -1 }
    }
)

Delete

    DELETE FROM Contacts
    WHERE Last_Name = 'Doe';
    db.Contacts.delete(
        { Last_Name: "Doe" }
    )

L'opération d'agrégation

principe

Enchaîner les opérations

Exemple

{
    "_id" : ObjectId("584159ba741d85f3df9147d4"),
    "mod_id" : ObjectId("584158d4e4b07592382ba74b"),
    "modVersion" : 0,
    "startDate" : ISODate("2016-12-02T11:12:54.837Z"),
    "submitDate" : ISODate("2016-12-02T11:14:36.643Z"),
    "data" : {
        "CITY" : {
            "date" : NumberLong(1480677182830),
            "value" : {
                "zip" : "35830",
                "city" : "Betton"
            }
        },
        ...
    },
    ...
}
x 2468

Etape #1 : Récupérer les données

db.formData_582b2b5ae4b0a55f3d0f436f.aggregate( [
    { $match: { 
        mod_id: ObjectId("5840597be4b07592382acc4c"), 
        modVersion: 1, 
        submitDate: { $exists: 1 } 
} } ] )
{
    "_id" : ObjectId("584159ba741d85f3df9147d4"),
    "mod_id" : ObjectId("584158d4e4b07592382ba74b"),
    "modVersion" : 0,
    "startDate" : ISODate("2016-12-02T11:12:54.837Z"),
    "submitDate" : ISODate("2016-12-02T11:14:36.643Z"),
    "data" : {
        "CITY" : {
            "date" : NumberLong(1480677182830),
            "value" : {
                "zip" : "35830",
                "city" : "Betton"
            }
        },
        ...
    },
    ...
}
x 2468
{
    "result" : [ 
        {
            "_id" : ObjectId("58418252741d85f3df914805"),
            "mod_id" : ObjectId("5840597be4b07592382acc4c"),
            "modVersion" : 1,
            "startDate" : ISODate("2016-12-02T14:06:49.758Z"),
            "submitDate" : ISODate("2016-12-02T14:07:47.871Z"),
            "data" : {
                ...
                "CITY" : {
                    "date" : NumberLong(1480687625106),
                    "value" : {
                        "zip" : "35000",
                        "city" : "Rennes"
                    }
                },
                ...
            },
        },
        ... x 1655
    ],
    "ok" : 1
}

Etape #2 : Élaguer les données

db.formData_582b2b5ae4b0a55f3d0f436f.aggregate( [ 
  { $match: { ... } },
  { $project: { "data.CITY": 1 } }
] )
{
    "_id" : ObjectId("584159ba741d85f3df9147d4"),
    "mod_id" : ObjectId("584158d4e4b07592382ba74b"),
    "modVersion" : 0,
    "startDate" : ISODate("2016-12-02T11:12:54.837Z"),
    "submitDate" : ISODate("2016-12-02T11:14:36.643Z"),
    "data" : {
        "CITY" : {
            "date" : NumberLong(1480677182830),
            "value" : {
                "zip" : "35830",
                "city" : "Betton"
            }
        },
        ...
    },
    ...
}
x 2468
{
    "result" : [ 
        {
            "_id" : ObjectId("58418252741d85f3df914805"),
            "data" : {
                "CITY" : {
                    "date" : NumberLong(1480687625106),
                    "value" : {
                        "zip" : "35000",
                        "city" : "Rennes"
                    }
                }
            }
        }, 
        ... x 1655
    ],
    "ok" : 1
}

Etape #3 : Grouper les résultats

db.formData_582b2b5ae4b0a55f3d0f436f.aggregate( [ 
  { $match: { ... } },
  { $project: { "data.CITY": 1 } },
  { $group: { _id: "$data.CITY.value.city", count: { $sum:1 } } }
] )
{
    "_id" : ObjectId("584159ba741d85f3df9147d4"),
    "mod_id" : ObjectId("584158d4e4b07592382ba74b"),
    "modVersion" : 0,
    "startDate" : ISODate("2016-12-02T11:12:54.837Z"),
    "submitDate" : ISODate("2016-12-02T11:14:36.643Z"),
    "data" : {
        "CITY" : {
            "date" : NumberLong(1480677182830),
            "value" : {
                "zip" : "35830",
                "city" : "Betton"
            }
        },
        ...
    },
    ...
}
x 2468
{
    "result" : [ 
        {
            "_id" : "Bourges",
            "count" : 12
        }, 
        {
            "_id" : "Angers",
            "count" : 2
        }, 
        ... x 57
    ],
    "ok" : 1
}

Etape #4 : Trier les résultats

db.formData_582b2b5ae4b0a55f3d0f436f.aggregate( [ 
  { $match: { ... } },
  { $project: { "data.CITY": 1 } },
  { $group: { _id: "$data.CITY.value.city", count: { $sum:1 } } },
  { $sort: { "count" : -1 } }
] )
{
    "_id" : ObjectId("584159ba741d85f3df9147d4"),
    "mod_id" : ObjectId("584158d4e4b07592382ba74b"),
    "modVersion" : 0,
    "startDate" : ISODate("2016-12-02T11:12:54.837Z"),
    "submitDate" : ISODate("2016-12-02T11:14:36.643Z"),
    "data" : {
        "CITY" : {
            "date" : NumberLong(1480677182830),
            "value" : {
                "zip" : "35830",
                "city" : "Betton"
            }
        },
        ...
    },
    ...
}
x 2468
{
    "result" : [ 
        {
            "_id" : "Vannes",
            "count" : 707
        }, 
        {
            "_id" : "Theix",
            "count" : 89
        }, 
        ... x 57
    ],
    "ok" : 1
}
        

Etape #5 : Limiter le nombre

db.formData_582b2b5ae4b0a55f3d0f436f.aggregate( [ 
  { $match: { ... } },
  { $project: { "data.CITY": 1 } },
  { $group: { _id: "$data.CITY.value.city", count: { $sum:1 } } },
  { $sort: { "count" : -1 } },
  { $limit: 3 }
] )
{
    "_id" : ObjectId("584159ba741d85f3df9147d4"),
    "mod_id" : ObjectId("584158d4e4b07592382ba74b"),
    "modVersion" : 0,
    "startDate" : ISODate("2016-12-02T11:12:54.837Z"),
    "submitDate" : ISODate("2016-12-02T11:14:36.643Z"),
    "data" : {
        "CITY" : {
            "date" : NumberLong(1480677182830),
            "value" : {
                "zip" : "35830",
                "city" : "Betton"
            }
        },
        ...
    },
    ...
}
x 2468
{
    "result" : [ 
        {
            "_id" : "Vannes",
            "count" : 707
        }, 
        {
            "_id" : "Theix",
            "count" : 89
        }, 
        {
            "_id" : "Saint-Avé",
            "count" : 67
        }
    ],
    "ok" : 1
}

Bonnes pratiques

Les principes de base

Typer les documents

Obligatoire en cas de polymorphisme

Pourquoi ?

  • Parce qu'il n'y a pas de schéma !
    => plusieurs types différents peuvent cohabiter
    • on peut mélanger les choux et les carottes
    • mais on ne sait pas quel type on manipule
      => ajouter un champ "_type" ou "_class"
      (si absent = type générique de la collection)

Comment ?

{
    "_type": "car",
    "name": "Voiture",
    "plate": "AR-456-YI",
    "nextControlDate": ISODate("2017-10-01T00:00:00.000Z"),
    "nbSeats": 5
}
{
    "_type": "truck",
    "name": "Camion",
    "plate": "AD-123-PS",
    "nextControlDate": "none",
    "capacity": 20
}

Versionner les documents

Sinon c'est vite le bordel

Pourquoi ?

  • Parce qu'il n'y a pas de schéma !
    => plusieurs versions différentes peuvent cohabiter
    • pas la peine de mettre les documents à jour
      => pas de script de mise à jour
      (sauf énorme changement)
    • mais on ne sait pas quelle version on manipule
      => ajouter un champ "_version"
      (si absent = version 0)

Comment ?

{
    "addressStreet": "rue d'Alembert",
    "addressZipCode": "35000",
    "addressCityName": "Rennes",
    ...
}
{
    "_version": 1
    "address": {
        "street": "rue de Gauss",
        "zipCode": "56230",
        "cityName": "Questembert"
    },
    ...
}
{
    "_version": 2
    "address": {
        "street": "rue du Rennes",
        "city" : {
            "zipCode": "35700",
            "name": "Saint Malo"
        }
    },
    ...
}
var CONVERTERS = [
  function(obj) { 
    obj.address = {
      street: obj.addressStreet,
      zipCode: obj.addressZipCode,
      cityName: obj.addressCityName
    };
    delete obj.addressStreet;
    delete obj.addressZipCode;
    delete obj.addressCityName;
    obj._version = 1;
  },
  function(obj) { 
    obj.address.city = {
      zipCode: obj.address.zipCode,
      name: obj.address.cityName
    };
    delete obj.address.zipCode;
    delete obj.address.cityName;
    obj._version = 2;
  }
];
while ( obj._version < currentVersion ) {
  CONVERTERS[obj._version]();
}

Gérer l'historique

Cas #1

Un document par version

{  "docId" : 174, "rev" : 1,  "attr1": 165 }   /*version 1 */
{  "docId" : 174, "rev" : 2,  "attr1": 165, "attr2": "A-1" } 
{  "docId" : 174, "rev" : 3,  "attr1": 184, "attr2": "A-1" }
db.docs.find({ "docId": 174 }).sort({ "rev": -1 }).limit(-1);

findone

db.docs.aggregate( [ 
    { "$sort": { "docId" : 1, "rev" : -1 } },
    { "$group" : { "_id" : "$docId",
                   "doc": { "$first" : "$$ROOT" }
    } },
    { "$match": { "doc.attr1": 184 } }
] );

find

Cas #1a

Un document par version avec current

{  "docId" : 174, "v" : 1,  "attr1": 165 }
{  "docId" : 174, "v" : 2,  "attr1": 165, "attr2": "A-1" } 
{  "docId" : 174, "v" : 3,  "attr1": 184, "attr2": "A-1", "current": true }
db.docs.findOne({ "docId": 174, "current": true }));

findone

db.docs.find({ "doc.attr1": 184, "current": true }));

find

Cas #2

un seul document contenant toutes les versions

{  
    "docId" : 174, 
    "current" : { "attr1": 184, "attr2": "A-1" },
    "prev": [ {  "attr1": 165 }, {  "attr1": 165, "attr2": "A-1" } ]
}
db.CurrentCollection.findone({ "docId": 174 }, { current: 1 });

findone

db.CurrentCollection.find(
    { "current.attr1": 184 }, 
    { current: 1 }
);

find

Cas #3

Deux collections

CurrentCollection:    
  { "docId" : 174, "rev": 3, "attr1": 184, "attr2": "A-1" }
PreviousCollection:
  { "docId" : 174, "rev": 1, "attr1": 165 }
  { "docId" : 174, "rev": 2, "attr1": 165, "attr2": "A-1" }
  { "docId" : 174, "rev": 3, "attr1": 184, "attr2": "A-1" }
db.CurrentCollection.findone({ "docId": 174 });

findone

db.CurrentCollection.find({ "attr1": 184 });

find

Cas #4

Un document par version mais on stocke les deltas

sous forme de JSONPatch

{ "_id": ..., "id": 174, "userId": "xxx", "date": ISODate(...), patch: [
    { "op": "add", "path": "/attr1", "value": 165 }
] }
{ "_id": ..., "id": 174, "userId": "yyy", "date": ISODate(...), patch: [
    {"op": "add", "path": "/attr2", "value": "A-1" }
] } 
{ "_id": ..., "id": 174, "userId": "zzz", "date": ISODate(...), patch: [
    { "op": "replace", "path": "/attr1", "value": 184 }
] }

chaque patch contient

  • id du document
  • date de modification
  • patch à appliquer

en option

  • id utilisateur
  • son rôle
  • id du tenant

find

Compliqué on doit procéder en deux étapes

  • créer une collection où on reconstruit les objets
  • puis effectuer la recherche
db.docs.aggregate( [ 
    { $match: { id: "174" } },    // recherche tous les patches sur l'entité
    { $sort: { "date" : 1 } },    // trie les résultats par date
    { $unwind : "$patch" },       // aplati tous les patchs
    { $group: { _id: "$id", patch: { $push:"$patch" } } } 
    // regroupe tous les patchs en un tableau
] );

findone

  • On agrège tous les patchs lié à l'id de l'objet puis on applique la liste de patchs sur un objet vide
  • On peut ajouter un filtre sur la date pour gérer les révisions

Cas #3+4

Deux collections

SnapshotCollection:    
  { "id" : 174,  "v": 3, "attr1": 184, "attr2": "A-1" }
PatchCollection:
  { "_id": ..., "id": 174, "userId": "xxx", "date": ISODate(...), patch: [...] }
  { "_id": ..., "id": 174, "userId": "yyy", "date": ISODate(...), patch: [...] } 
  { "_id": ..., "id": 174, "userId": "zzz", "date": ISODate(...), patch: [...] }
db.SnapshotCollection.findone({ "docId": 174 });

findone

db.SnapshotCollection.find({ "attr1": 184 });

find

Cas #5

oplog

Le but est d'utiliser les logs de mongo pour gérer l'historique des versions du document (voir ce blog)

Au final

S'adapter

au besoin

et

à la volumétrie

  • dans le nom de la collection

Les tenants

  • dans les documents
db.data_564f36c87dcbd97cf6f3e2ea.find()

Mettre un id

db.data.find({ownerId: "564f36c87dcbd97cf6f3e2ea"})
{
    ...,
    ownerId: "564f36c87dcbd97cf6f3e2ea",
    ...
}

Les doublons

C'est le mal !

Ben non, c'est même plutôt utile

Les doublons

un cache

=> il faut une référence

  • Idéal pour les données en lecture seule
  • Il faut un mode de synchronisation

Les doublons

requêtes croisées

  • Impossibles en une seule étape
    => Mettre les champs nécessaires
{
    ...
    "otherDocId": xxx,
    ...
}
{
    "_id": xxx,
    "name": "toto",
    ...
}
{
    "_id": xxx,
    "name": "toto",
    ...
}
{
    ...,
    "otherDoc": {
        "_id": xxx,
        "name": "toto"
    },
    ...
}

Les objets sont vivants

Les objets ont des champs 

  • en plus
  • en moins
  • renommés
  • qui changent de type

En plus

  • même pas mal !
  • si c'est un champ qui ne peut être nul
    => initialiser la valeur par défaut dans le constructeur
  • au prochain enregistrement il apparaîtra

En moins

  • on s'en fout !
  • au prochain enregistrement complet il disparaîtra
  • au pire utiliser $unset

renommage

  • garder les deux noms
  • ajouter accesseur et mutateur

ou

  • utiliser le système de version d'objets

ou

  • mettre à jour les objets de la base avec $rename

changement de type

  • si le langage le permet 
  • ajouter accesseur et mutateur
    pour retourner le bon type

ou

  • utiliser le système de version d'objets

ou

  • mettre à jour les objets de la base

Un objet

  • change de nature
  • fusionne avec un autre
  • se scinde en deux

changement de nature

  • le nouveau format est dans une autre collection
  • grâce au UUIDs
    • si l'objet est dans la nouvelle collection => ok
    • sinon l'objet est dans l'ancienne
      => convertir l'objet
  • inconvénient les recherches doivent se faire dans les deux collections et les résultats fusionnés

ou

  • utiliser le système de version d'objets

ou

  • on met à jour les objets de la base

fusion

  • à la lecture l'objet principal agrège le second objet
  • Il faut utiliser le système de version

ou

  • on met à jour les objets de la base

scission

  • le second objet est dans une autre collection
  • principe
    • rien ne change pour l'objet principal
    • le second objet est dans la nouvelle collection => ok
    • sinon l'objet est dans l'ancienne
      => convertir la partie du second objet
      => mettre à jour l'objet principal
  • inconvénient les recherches doivent se faire dans les deux collections et les résultats fusionnés
  • problème de UUIDs

ou

  • on met à jour les objets de la base

Un objet a un cycle de vie

  • création
  • modification
  • destruction

destruction

  • delete

ou

  • Archivage
    =>Ajout d'un champ _archive: true
find({ 
    ..., 
    $or: [ { _archive: { $exists: false } }, { _archive: false } ]
})
find({ 
    ..., 
    _archive: true
})

Indexation

_id par défaut

Rappel sur _id et UUID

  • C'est n'importe quoi
  • mais ce doit être un UUID
  • on peut trouver une équivalence entre SQL et mongo
{
    _id: ObjectId("000280000000000000000042"),
    ...
}
Id ...
42 ...

table  #28

Créer un index

  • Différents types d'index :
    • simple, composé, multi-clé, géospatial, texte, hash
  • Propriétés
    • unique, partiel, clairsemé, TTL, 
db.xxx.createIndex( <key & index type specification>, <options> )

Indexation

  • Pour le geospatial c'est top
     
  • Pour le texte c'est bof mais ça s'améliore
    il faut mieux utiliser une base d'indexation basée sur lucene (comme solr ou elasticsearch)

MongoDB

Compléments

Retour sur

agrégation & relation

{
    _id: "5b50f8968656472a58970167",
    password: "xxx",
    userId: "bchanclo",
}
{
    _id: "5b50f8968656472a58970167",
    firstname: "Benoît",
    lastname: "Chanclou",
    birthdate: ...
}

Un id

  • on sait où aller chercher la ressource
  • on sait faire 
    • en local
       
    • ou sur un autre référentiel 
{
    user: "564f36c87dcbd97cf6f3e2ea",
}
GET http://api.myapp.com/tiers/1/contacts/564f36c87dcbd97cf6f3e2ea
db.users.findOne({_id: "564f36c87dcbd97cf6f3e2ea"})

Une uri

  • dans le cas d'une ressource d'un autre référentiel
  • on sait
    • extraire l'id pour une requête locale
    • ou appeler l'url correspondant à l'uri
{
    user: "http://api.myapp.com/tiers/1/contacts/564f36c87dcbd97cf6f3e2ea",
}

Une référence riche

  • les objets de type "User" sont dans une collection ou un référentiel connu
  • on sait
    • récupérer la ressource
    • que ce sera un objet de type User
{
    user: {
        type: "User",
        id: "564f36c87dcbd97cf6f3e2ea"
    },
}

Une référence avec cache

  • on sait récupérer la ressource
  • si régulièrement on veut juste le nom et le prénom pour l'afficher
    • inutile d'aller charger la ressource
    • on utilise le cache
  • lorsque la ressource est chargée en lazy loading, le cache peut être mis à jour si nécessaire
{
    user: {
        firstname: "Benoît",
        lastname: "Chanclou",
        uri: "http://api.myapp.com/tiers/1/contacts/564f36c87dcbd97cf6f3e2ea"
    },
}

Utiliser un framework 

  • Spring sait gérer ce type de référence
    (au sein d'une même application)

Les champs supp' dynamiques

C'est open bar

Pas de schéma

  • Il suffit d'ajouter les champs
  • Les champs peuvent être :
    • un objet
    • un tableau
    • un type simple

Nécessité d'un modèle

  • Connaître le contenu est indispensable
    => Spécifier le type de document

Sous types

=> utiliser le "_type"

{
    "_id": ...,
    "_type": "Sous type 1",
    "champSpecifiqueSousType1" : []
    ...
}

Agrégats

=> utiliser un tableau  de "_tags"

{
    "_id": ...,
    "_tags": [
        "Attribut 1", 
        "Attribut 2", 
        ...],
    "champSpecifiqueAttr1" : null,
    "champSpecifiqueAttr2" : ""
    ...
}

Champs dynamiques

=> utiliser un modèle

{
    "_id": ...,
    "_modId": 1,
    "aaa": 42
    ...
}
{
    "_id": 1,
    "fields": [ { 
        "name": "Champ quelconque", 
        "label": "aaa", 
        "type": "int", 
        .... }, 
    ... ]
}

Recommandations

  • Attention aux collisions de noms de champs
    • envisager des sous documents
  • Les requêtes se font de la même manière
    • champs supp' <=> champs normaux

MongoDB

outils

RoboMongo

  • le plus connu
  • permet d'éditer les requêtes directement
  • version plus complète payante

MongoDB Compass

  • l'outil officiel
  • beaucoup plus complet (management et monitoring)
  • payant pour avoir la version complète

présentation MongoDB

By Benoît Chanclou

présentation MongoDB

Présentation sur la structuration des données. Les sujets de déploiement de Mongo ne sont pas abordés.

  • 586