Principes
et
Bonnes pratiques
Outils
Manipuler et administrer les bases mongo
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
Ligne de commande
- outil le plus basique
- outil complet
- utile pour l'initialisation, la sauvegarde et la restauration de données
(notamment avec des scripts) - permet de tester une requête
(mais les autres sont plus conviviaux)
MongoDB vs mySQL
Base NOSQL
non relationnelle
orientée document
Vocabulaire
SQL vs Mongo
- Base <=> Base
- Collection <=> Table
- Document / Objet <=> Ligne / Enregistrement
- Champ <=> Colonne
- Sous document ou Lien <=> Jointure
Mongo
Base de données
- NoSQL avec
- replication
- sharding
- Orientée documents
- sans schéma
- sans relationnel

Replication
- Redondance des nœuds d'instance
En cas de panne de l'un des nœuds
la redondance assure la disponibilité des données

Sharding
- La base est tronçonnée
Répartition des documents
selon une de leur caractéristique

Orientée document
Document <=> objet JSON avec un champ "_id"
{
"_id" : ObjectId("5811bac48b0cf22ea39e16ec"),
...
}{
"_id" : "toto",
...
}{
"_id" : 42,
...
}rappel sur l'auto-incrémentation
- L'auto-incrémentation c'est le mal
- espionnage via API
- ids identiques pour des ressources différentes
- L'UUID s'est le bien

Il faut privilégier des UUIDs

{
_id: ObjectId("000280000000000000000042"),
...
}| Id | ... |
|---|---|
| 42 | ... |
table #28
mongo
on peut trouver une équivalence entre SQL et mongo
sql
NoSQL Orientée document
=>
- Pas de relationnel
- Pas de schéma
(en option) - Pas de transactionnel
(mais ça va venir dans certaines conditions)
Requêtes
Find & Aggregate

Requêtes simples
Les fonctions CRUD
de bases

No SQL
Langage de requête
en ecmascript
(alias javascript)
Insert
INSERT INTO Tiers (First_Name, Last_Name)
VALUES ('John','Doe'); db.Tiers.insert(
{
First_Name: "John",
Last_Name : "Doe"
}
)Find
SELECT *
FROM Tiers
WHERE First_Name = 'John'; db.Tiers.find(
{ First_Name: "John" }
)[
{
"_id": ObjectId("58776b8643ab721e65d744ba"),
"First_Name": "John",
"Last_Name": "Doe"
},
...
]Findone
SELECT *
FROM Tiers
WHERE First_Name = 'John'; db.Tiers.findone(
{ First_Name: "John" }
){
"_id": ObjectId("58776b8643ab721e65d744ba"),
"First_Name": "John",
"Last_Name": "Doe"
}Find (avec projection)
SELECT Last_Name
FROM Tiers
WHERE First_Name = 'John'; db.Tiers.find(
{ First_Name: "John" },
{ Last_Name: 1 }
){
"_id": ObjectId("58776b8643ab721e65d744ba"),
"Last_Name": "Doe"
}Find (dans un sous objet)
SELECT Tiers.Name
FROM Tiers
LEFT JOIN Address
ON Address.OwnerId = Tiers.ID
WHERE City REGEXP 'Saint.*'; db.Tiers.find(
{ "address.city": { $regex: /Saint.*/g } },
{ name: 1 }
){
"_id": ObjectId("58776b8643ab721e65d744ba"),
"name": "Tactilia"
}Find (opérateurs)
- Comparaisons : $eq, $gt, $in,...
- Logiques : $and, $not,...
- Elements : $exists, $type
- Evaluation : $regex, $mod, $where,...
- Géospatial : $near,...
- Tableaux : $size, $elemMatch,...
- Bits : $bitsAllSet,...
db.Tiers.find(
{ First_Name: { $in: ["John", "Jane"] } }
)
db.Equipments.find(
{ $or: [ { nextControlDate: "none" }, { nextControlDate: { $gt: new Date() } } ] }
)
db.Equipments.find(
{ nbSeats: { $exists: true } }
)Update
UPDATE Tiers
SET First_Name = 'Jane'
WHERE Last_Name = 'Doe'; db.Tiers.update(
{ Last_Name: "Doe" },
{ $set: { First_Name: "Jane" } }
) db.Tiers.update(
{ Last_Name: "Doe" },
{ First_Name: "Jane" }
)Update (opérateurs)
db.Tiers.update(
{},
{ $rename: { First_Name: "firstName", Last_Name: "lastName" } },
{ multi: true }
)
db.Tiers.update(
{ "_id": ObjectId("58776b8643ab721e65d744ba") },
{ $push: { products: "configurateur" } },
{ upsert: true }
)
db.Scores.update(
{ "_id": ObjectId("55d744ba76b864387ab721e6") },
{
$push: { score: 42, game: "slot" },
$currentDate: { "last.play": true },
$set: { "last.result": "win" },
$inc: { credits: -1 }
}
)Delete
DELETE FROM Tiers
WHERE Last_Name = 'Doe'; db.Tiers.delete(
{ Last_Name: "Doe" }
)Requêtes complexes l'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 1655
],
"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 1655
],
"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
}Pas de schéma
des objets
Pas de schéma
On peut y mettre n'importe quel JSON (jusqu'à 16Mo)
=> mongo n'impose aucun schéma
=> c'est à l'utilisateur (programme appelant) 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
}
Stockage d'objets
- C'est fait pour !
- Pas besoin des complications de SQL
Quid du polymorphisme ?
Règles de bon sens
- On met un seul type d'objet dans une collection
=> On sait a priori ce qui est stocké
- On met une famille d'objets dans la collection
& on identifie le type de chaque objet
=> Pour savoir précisément ce qu'on manipule
Quel est le type de mon document ?
on ajoute un champ "_type" ou "_class"
=> on sait quel type on manipule
=> si absent = type générique de la collection
Comment on identifie le type ?
Quel est le type de mon document ?
Exemple
{
"_type": "car",
"name": "Voiture",
"plate": "AR-456-YI",
"nextControlDate": ISODate("2017-10-01T...Z"),
"nbSeats": 5
}
{
"_type": "truck",
"name": "Camion",
"plate": "AD-123-PS",
"nextControlDate": "none",
"capacity": 20
}
Relations entre objets
composition et agrégation
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"
...
},
products: [
"fidbe", ...
],
...
}
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
Agrégation & relation
{
_id: "5b50f8968656472a58970167",
password: "xxx",
userId: "bchanclo",
??? lien vers le tiers ???
}{
_id: "5b50f8968656472a58970167",
firstname: "Benoît",
lastname: "Chanclou",
birthdate: ...
}Account
Tiers
Exemple un compte d'accès en lien avec un tiers
Un simple id
- suppose qu'on sache où aller chercher la ressource
- on sait faire
-
un accès direct à la base
- ou via l'api du référentiel Tiers
-
un accès direct à la base
{
_id: "5b50f8968656472a58970167",
password: "xxx",
userId: "bchanclo",
user: "564f36c87dcbd97cf6f3e2ea",
}
GET http://api.myapp.com/tiers/1/contacts/564f36c87dcbd97cf6f3e2eadb.tiers.findOne({_id: "564f36c87dcbd97cf6f3e2ea"})Une uri (c'est universel !)
- ne présuppose rien sur la localisation de la ressource
- on sait
- extraire l'id pour une requête directe à la base
- ou appeler l'url correspondant à l'uri
{
user: "http://api.myapp.com/tiers/1/contacts/564f36c87dcbd97cf6f3e2ea",
}
Une référence enrichie
- les objets de type "Tiers" sont dans une collection ou un référentiel connu
- on sait
- récupérer la ressource
- que ce sera un objet de type Tiers
{
user: {
type: "Tiers",
id: "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
- Pour les requêtes croisées
compense l'absence de relationnel - Pour les données en lecture seule
- Il faut un mode de synchronisation
Requêtes croisées
Trouver des livreur dont le nom est "John"
Impossible en une seule étape
{
...
"deliveryMan": xxx,
...
}{
"_id": xxx,
"firstname": "John",
"lastname": "Doe",
"phone": "+33123456789",
...
}db.Deliveries.find(
{ deliveryMan: xxx }
)db.Tiers.find(
{ firstname: "John" }
)Tiers
Deliveries
Requêtes croisées
Impossible sauf en mettant
les champs nécessaires dans le cache
{
...
"contactId": xxx,
...
}{
"_id": xxx,
"firstname": "John",
"lastname": "Doe",
"phone": "+33123456789",
...
}{
...,
"contact": {
"_id": xxx,
"firstname": "John",
"lastname": "Doe",
"phone": "+33123456789"
},
...
}db.Customers.find(
{ contact.firstname: "John" }
)Tiers
Customers
Customers
Une référence avec cache
- si on veut juste le nom et le prénom
- inutile d'aller charger la ressource secondaire
- on utilise le cache
- on sait récupérer la ressource si besoin
{
"contact": {
"_id": xxx,
"firstname": "John",
"lastname": "Doe",
"phone": "+33123456789"
},
}
Avantages et Contraintes du cache
+ Accès rapide
+ Recherches possible sur les champs mis en cache
+ Lazy loading de la ressource référencée
- Gérer le cycle de vie du cache :
- Invalidation
- Mise à jour
Mise à jour du cache
- Asynchrone :
Lorsque la ressource secondaire est chargée (en lazy loading), le cache peut être mis à jour
-
Synchrone :
Lorsque la ressource secondaire est mise à jour- En local :
Le cache est lui aussi mis à jour - Dans un autre référentiel :
Le secondaire appelle un webhook du principal pour mettre à jour le cache
- En local :
Versionner
Pourquoi ?
Les objets sont vivants
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
})Les objets ont des champs
- en plus
- en moins
- renommés
- qui changent de type
En plus
- si c'est un champ qui ne peut pas être nul
=> initialiser la valeur par défaut dans le constructeur - au prochain enregistrement il apparaîtra

En moins
- au prochain enregistrement complet il disparaîtra
- au pire utiliser $unset (cas d'une champs volumineux)

Renommage
- garder les deux noms
- ajouter accesseur et mutateur
ou
- utiliser un système de version de schémas
ou
- mettre à jour les objets de la base avec $rename
Changement de type
- ajouter accesseur et mutateur
pour retourner le bon type
(si le langage le permet)
ou
- utiliser un système de version de schémas
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
ou
- utiliser un système de version de schémas
ou
- on met à jour les objets de la base
fusion
- utiliser un système de version de schéma
- à la lecture l'objet principal agrège le second objet
ou
- mettre à jour les objets de la base
scission
-
l'objet principal reste dans sa collection
- à la lecture si encore agrégé scinder l'objet
- le second objet est transféré dans une autre collection
- l'objet est dans la nouvelle collection => ok
-
sinon l'objet est encore agrégé à l'objet principal
=> convertir la partie du second objet
(accessoirement mettre aussi à jour l'objet principal) - problème de génération de l'id du second objet
ou
- on met à jour les objets de la base
Versionner les schémas
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)
- pas la peine de mettre les documents à jour
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 = [
obj => {
obj.address = {
street: obj.addressStreet,
zipCode: obj.addressZipCode,
cityName: obj.addressCityName
};
delete obj.addressStreet;
delete obj.addressZipCode;
delete obj.addressCityName;
obj._version = 1;
},
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 < CONVERTERS.length) {
CONVERTERS[obj._version](obj);
}Avantages et Contraintes
+ on ne manipule que des objets au dernier format
+ on n'est pas obligé de mettre toute la collection à jour
+ pas de migration =
- pas d'interruption de service
- conversion au fil de l'eau
- recherche complexifiées
- il faut rechercher dans toutes les versions
utile dans une grosse base où peu d'objets sont utilisés
(ex.: liste de commandes clients, au quotidien on ne manipule que les plus récentes)
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
Indexation
Accélérer les recherches
Qu'est-ce qu'un index ?
Une structure de recherche
- but :
réduire le temps de réponse de requêtes - inconvénient :
prend de la place
consommateur de ressources
Quand les utiliser ?
- il faut les utiliser mais pas en abuser
-
les utiliser pour les requêtes identifiées comme
- lentes
- fréquentes
champ _id
- indexé par défaut
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, ou des services tout fait comme algolia)
MongoDB
Compléments
Les champs supp'
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
- whatever
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" : []
...
}l'héritage c'est le mal,
l'agrégation c'est le bien
Agrégats

=> utiliser un tableau de "_tags"
{
"_id": ...,
"_tags": [
"Attribut 1",
"Attribut 2",
...],
"champSpecifiqueAttr1" : null,
"champSpecifiqueAttr2" : ""
...
}Champs dynamiques

=> utiliser un modèle
{
"_id": ...,
"_modId": [ 123 ],
"aaa": 42
...
}{
"_id": 123,
"fields": [ {
"name": "Champ quelconque",
"label": "aaa",
"type": "int",
.... },
... ]
}Modèle
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
- dans le nom de la collection
Les tenants
- dans les documents
db.data_564f36c87dcbd97cf6f3e2ea.find()Mettre un id de tenant
db.data.find({ownerId: "564f36c87dcbd97cf6f3e2ea"}){
...,
ownerId: "564f36c87dcbd97cf6f3e2ea",
...
}Liens
bibliographie
Documentations
- Documentation mongo (la recherche marche bien)
- https://fr.wikipedia.org/wiki/Base_de_donn%C3%A9es_orient%C3%A9e_documents
- Stack mean
- Mongoose
- Spring
Blogs & présentations
Utiliser un framework
certains frameworks savent gérer ce type de références
(au sein d'une même application, même db)
- java : Spring
- js : mongoose (populate)
MongoDBx4
By Benoît Chanclou
MongoDBx4
en cours
- 375