2023-06-18
Reflexion à propos du modèle de donnée de PlanDive #dev
Entity Relation Diagram
Link
Mermaid Live Editor on mermaid.live
Représentation
Intégration dans Firestore
Avec Document References et Denormalization
exports.initializeDb = onRequest(async (req, res) => {
// Init user
const user = {
'name': 'Nicolas SAVON',
'email': 'nicolas.savon@gmail.com',
'licence': 'YXozNGZkc2cyNA==',
'rate': 5.0,
'profile': {
'name': 'Niko',
'type': 'OWNER',
},
};
await db.collection(dbCollection.USER).doc('UGxhbkRpdmU=').set(user);
// Init company
const company1 = {
'name': 'Continental Sea Inc',
};
const company1resource = [
{
'name': 'Tequila',
'type': 'CRUISE BOAT',
'quantity': 20.0,
},
{
'name': 'Santa Cruz',
'type': 'CRUISE BOAT',
'quantity': 5,
}
]
const calendarSlot1 = {
'from': new Date(2023, 5, 19, 10, 0, 0, 0),
'to': new Date(2023, 5, 19, 18, 0, 0, 0),
'availableQuantity': -1,
};
const company1Ref = db.collection(dbCollection.COMPANY).doc();
await company1Ref.set(company1);
company1resource.forEach( async (resource) => {
const resourceRef = db.collection(dbCollection.COMPANY).doc(company1Ref.id).collection(dbCollection.RESOURCE).doc();
await resourceRef.set(resource);
await db.collection(dbCollection.COMPANY).doc(company1Ref.id).collection(dbCollection.RESOURCE).doc(resourceRef.id).collection(dbCollection.CALENDARSLOT).doc().set(calendarSlot1)
})
// Init booking
const booking1 = {
'name': 'Cool dive experience on new boat'
};
const booking1Ref = db.collection(dbCollection.BOOKING).doc();
await booking1Ref.set(booking1);
const booking2 = {
'name': 'Cool dive experience on old boat'
};
const booking2Ref = db.collection(dbCollection.BOOKING).doc();
await booking2Ref.set(booking2);
// Adding company
const company1QuerySnapshot = await db.collection(dbCollection.COMPANY).doc(company1Ref.id).get();
const companyData1 = {
id: company1QuerySnapshot.id,
...company1QuerySnapshot.data()
};
await db.collection(dbCollection.BOOKING).doc(booking1Ref.id).update({company: companyData1});
await db.collection(dbCollection.BOOKING).doc(booking2Ref.id).update({company: companyData1});
// Adding resource
const resourcesFromCompany1QuerySnapshot = await db.collection(dbCollection.COMPANY).doc(company1Ref.id)
.collection(dbCollection.RESOURCE).get();
const resourceData0 = {
id: resourcesFromCompany1QuerySnapshot.docs[0].id,
...resourcesFromCompany1QuerySnapshot.docs[0].data()
};
const resourceData1 = {
id: resourcesFromCompany1QuerySnapshot.docs[1].id,
...resourcesFromCompany1QuerySnapshot.docs[1].data()
};
await db.collection(dbCollection.BOOKING).doc(booking1Ref.id).update({resource: resourceData0});
await db.collection(dbCollection.BOOKING).doc(booking2Ref.id).update({resource: resourceData1});
// Adding user
const userQuerySnapshot = await db.collection('user').doc('UGxhbkRpdmU=').get();
const userData = {
id: userQuerySnapshot.id,
... userQuerySnapshot.data()
}
await db.collection(dbCollection.BOOKING).doc(booking1Ref.id).update({user: userData})
await db.collection(dbCollection.BOOKING).doc(booking2Ref.id).update({user: userData})
res.status(200).send('Initialization finish');
})
Représentation JSON
Représentation V1
Collections and subcollections
- users
- companies
- companies.resources
- calendarslots
- booking
Schema
{
"users": {
"UGxhbkRpdmU=": {
"name": "Nicolas SAVON",
"email": "nicolas.savon@gmail.com",
"licence": "YXozNGZkc2cyNA==",
"rate": 5.0,
"profile": {
"name": "Niko",
"type": "OWNER"
}
}
},
"companies": {
"company1": {
"name": "Continental Sea Inc",
"resources": [
{
"name": "Tequila",
"type": "CRUISE BOAT",
"quantity": 20.0
}
]
}
},
"calendarslots": {
"calendarslot1": {
"from": "2023-06-02T10:00:00Z",
"to": "2023-06-02T11:00:00Z"
}
},
"bookings": {
"booking1": {
"name": "Cool dive experience on new boat",
"equipment": ["food", "drink", "live music"],
"company": {
"id": "company1",
"name": "Continental Sea Inc",
},
"resource": {
"id": "resource1",
"name": "Tequila",
"type": "CRUISE BOAT",
},
"calendarslot": {
"id": "calendarslot1",
"from": "2023-06-02T10:00:00Z",
"to": "2023-06-02T11:00:00Z",
}
}
}
}
Représentation V2
Collections and subcollections
Schema
- companies
- companies.users
- companies.resources
- companies.resources.calendarslots
- booking
{
"companies": {
"company1": {
"name": "Continental Sea Inc",
"users": {
"UGxhbkRpdmU=": {
"name": "Nicolas SAVON",
"email": "nicolas.savon@gmail.com",
"licence": "YXozNGZkc2cyNA==",
"rate": 5.0,
"profile": {
"name": "Niko",
"type": "OWNER"
}
}
},
"resources": [{
"name": "Tequila",
"type": "CRUISE BOAT",
"quantity": 20.0,
"calendarslots": {
"calendarslot1": {
"from": "2023-06-02T10:00:00Z",
"to": "2023-06-02T11:00:00Z"
},
"calendarslot2": {
"from": "2023-06-02T11:00:00Z",
"to": "2023-06-02T12:00:00Z"
},
"calendarslot3": {
"from": "2023-06-02T14:00:00Z",
"to": "2023-06-02T18:00:00Z"
},
},
}]
}
},
"bookings": {
"booking1": {
"name": "Cool dive experience on new boat",
"equipment": ["food", "drink", "live music"],
"company": {
"id": "company1",
"name": "Continental Sea Inc",
},
"resource": {
"id": "resource1",
"name": "Tequila",
"type": "CRUISE BOAT",
},
"calendarslot": {
"id": "calendarslot1",
"from": "2023-06-02T10:00:00Z",
"to": "2023-06-02T11:00:00Z",
}
}
}
}
Updating denormalized data
Updating denormalized data in Firestore can be tricky, as there are no built-in mechanisms for maintaining consistency between copies of the same data. However, there are a few strategies you can use:
- Cloud Functions: You can use Firebase Cloud Functions to listen for changes in your
resourcessubcollection in thecompaniescollection. When a change occurs, the function can find allbookingsthat reference the changedresourceand update the denormalized data accordingly.
Here's an example Cloud Function in JavaScript that listens for changes to a resource document and updates the corresponding bookings:
// The Cloud Functions for Firebase SDK to create Cloud Functions and triggers.
const {logger} = require("firebase-functions");
const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated, onDocumentUpdated, Change, FirestoreEvent} = require("firebase-functions/v2/firestore");
// The Firebase Admin SDK to access Firestore.
const {initializeApp} = require("firebase-admin/app");
const {getFirestore} = require("firebase-admin/firestore");
initializeApp();
const db = getFirestore();
const dbCollection = {
USER: 'user',
COMPANY: 'company',
RESOURCE: 'resource',
BOOKING: 'booking',
};
exports.updateResource = onDocumentUpdated("company/{companyId}/resource/{resourceId}", async (event) => {
// Get an object representing the document
const previousValue = event.data.before.data();
const newValue = event.data.after.data();
// Get all booking with a resource of a specific Id
const bookingsSnapshot = await db.collection(dbCollection.BOOKING).where('resource.id', '==', event.params.resourceId).get();
// Batch update all booking finded with the new resource value
const updates = bookingsSnapshot.docs.map(doc => doc.ref.update({ 'resource': newValue }));
await Promise.all(updates);
});
- Client-side code: You can also handle the updates from the client-side application code. Whenever you update a
resource, you also fetch all thebookingsthat reference thatresourceand update the denormalized data. However, this approach can be less reliable than using Cloud Functions because it relies on the client to perform the updates, which can fail if the client loses its network connection or if the operation is interrupted for any other reason.
In both cases, you'll need to handle the potential for a large number of updates if a resource is referenced by many bookings. Firestore operations are charged per document read or written, so an operation that needs to update many documents can be costly. If you find that you're frequently needing to update a large number of documents due to changes in your resources, you may want to reconsider your data model or denormalization strategy.