简体   繁体   中英

What is the best way to keep track of changes of a document's property in MongoDB?

I would like to know how to keep track of the values of a document in MongoDB.

It's a MongoDB Database with a Node and Express backend.

Say I have a document, which is part of the Patients collection.

{
    "_id": "4k2lK49938d82kL",
    "firstName": "John",
    "objective": "Burn fat"
}

Then I edit the "objective" property, so the document results like this:

{
    "_id": "4k2lK49938d82kL",
    "firstName": "John",
    "objective": "Gain muscle"
}

What's the best/most efficient way to keep track of that change? In other words, I would like to know that the "objective" property had the value "Burn fat" in the past, and access it in the future.

Thanks a lot!

Maintain it as a sub-document like below

{
    "_id": "4k2lK49938d82kL",
    "firstName": "John",
    "objective": {
        obj1: "Gain muscle",
        obj2: "Burn fat"
    }

}

You can also maintain it as an array field but remember, mongodb doesn't allow you to maintain uniqueness in an array field and if you plan to index the "objective" field, you'll have to create a multi key index

Maybe you can change the type of "objective" to array and track the changes in it. the last one of the array is the latest value.

I think the simplest solution would be to use and update an array:

const patientSchema = new Schema({
  firstName: { type: String, required: true },
  lastName: { type: String, required: true },
  objective: { type: String, required: true }
  notes: [{
    date: { type: Date, default: Date.now() },
    note: { type: String, required: true }
  }],
});

Then when you want to update the objective...

const updatePatientObjective = async (req, res) => {
  try {
    // check if _id and new objective exist in req.body
    const { _id, objective, date } = req.body;
    if (!_id || !objective) throw "Unable to update patient's objective.";

    // make sure provided _id is valid
    const existingPatient = await Patient.findOne({ _id });
    if (!existingPatient) throw "Unable to locate that patient.";

    // pull out objective as previousObjective 
    const { objective: previousObjective } = existingPatient;

    // update patient's objective while pushing 
    // the previous objective into the notes sub document
    await existingPatient.updateOne({ 
        // update current objective
        $set { objective },
        // push an object with a date and note (previouseObjective) 
        // into a notes array
        $push: { 
          notes: {
            date,
            note: previousObjective              
         },
        },  
      }),
    );

    // send back response
    res
      .status(201)
      .json({ message: "Successfully updated your objective!" });
  } catch (err) {
    return res.status(400).json({ err: err.toString() });
  }
};

Document will look like:

firstName: "John",
lastName: "Smith",
objective: "Lose body fat.",
notes: [
  {
    date: 2019-07-19T17:45:43-07:00,
    note: "Gain muscle".
  },
  { 
    date: 2019-08-09T12:00:38-07:00,
    note: "Work on cardio."
  }
  { 
    date: 2019-08-29T19:00:38-07:00,
    note: "Become a fullstack web developer."
  }
  ...etc
]

Alternatively, if you're worried about document size, then create a separate schema for patient history and reference the user's id (or just store the patient's _id as a string instead of referencing an ObjectId, whichever you prefer):

const patientHistorySchema = new Schema({
  _id: { type: Schema.Types.ObjectId, ref: "Patient", required: true },
  objective: { type: String, required: true }
});

Then create a new patient history document when the objective is updated...

PatientHistory.create({ _id, objective: previousObjective });

And if you need to access to the patient history documents...

PatientHistory.find({ _id });

Maintaining/tracking history in the same document is not all recommended. As the document size will keep on increasing leading to

  • probably if there are too many updates, 16mb document size limit
  • Performance degrades

Instead, you should maintain a separate collection for history. You might have use hibernates' Javers or envers for auditing for your relational databases. if not you can check how they work. A separate table (xyz_AUD) is maintained for each table (xyz). For each row (with primary key abc) in xyz table, there exist multiple rows in xyz_AUD table, where each row is version of that row.

Moreover, Javers also support MongoDB auditing. If you are using java you can directly use it. No need to write your own logic.

Refer - https://nullbeans.com/auditing-using-spring-boot-mongodb-and-javers/

One more thing, Javers Envers Hibernate are java libraries. But I'm sure for other programming languages also, similar libraries will be present.

There is a mongoose plugin as well -

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM