sql >> Database >  >> NoSQL >> MongoDB

mangoest | Middleware | Rollback-bewerkingen uitgevoerd door pre/post hooks wanneer er een fout wordt gegenereerd

TLDR; Mongoose middleware is hier niet voor ontworpen.

Deze methode voor het invoegen van transacties is eigenlijk het patchen van de middleware-functionaliteit, en u maakt in wezen een api die volledig los staat van de mongoose middleware.

Wat beter zou zijn, is de logica voor uw verwijderquery omkeren in een aparte functie.

Eenvoudige en beoogde oplossing

Laat een transactieverwerkingsmethode zijn magie doen en maak een afzonderlijke verwijderingsmethode voor uw bovenliggende model. Mongoose verpakt mongodb.ClientSession.prototype.withTransaction met mongoose.Connection.prototype.transaction en we hoeven niet eens een sessie te instantiëren of te beheren! Kijk naar het verschil tussen de lengte van dit en dat hieronder. En je bespaart de mentale hoofdpijn van het onthouden van de binnenkant van die middleware ten koste van één afzonderlijke functie.


const parentSchema = new mongoose.Schema({
    name: String,
    children: [{ type: mongoose.Schema.Types.ObjectId, ref: "Child" }],
});

const childSchema = new mongoose.Schema({
    name: String,
    parent: { type: mongoose.Schema.Types.ObjectId, ref: "Parent" },
});

// Assume `parent` is a parent document here
async function fullRemoveParent(parent) {
    // The document's connection
    const db = parent.db;

    // This handles everything with the transaction for us, including retries
    // session, commits, aborts, etc.
    await db.transaction(async function (session) {
        // Make sure to associate all actions with the session
        await parent.remove({ session });
        await db
            .model("Child")
            .deleteMany({ _id: { $in: parent.children } })
            .session(session);
    });

    // And done!
}

Kleine uitbreiding

Een andere manier om dit gemakkelijk te maken, is door een middleware te registreren die gewoon een sessie erft iff _ de zoekopdracht heeft er een geregistreerd. Misschien een foutmelding geven als een transactie niet is gestart.

const parentSchema = new mongoose.Schema({
    name: String,
    children: [{ type: mongoose.Schema.Types.ObjectId, ref: "Child" }],
});

const childSchema = new mongoose.Schema({
    name: String,
    parent: { type: mongoose.Schema.Types.ObjectId, ref: "Parent" },
});

parentSchema.pre("remove", async function () {
    // Look how easy!! Just make sure to pass a transactional 
    // session to the removal
    await this.db
        .model("Child")
        .deleteMany({ _id: { $in: parent.children } })
        .session(this.$session());

    // // If you want to: throw an error/warning if you forgot to add a session
    // // and transaction
    // if(!this.$session() || !this.$session().inTransaction()) {
    //    throw new Error("HEY YOU FORGOT A TRANSACTION.");
    // }
});

// Assume `parent` is a parent document here
async function fullRemoveParent(parent) {
    db.transaction(async function(session) {
        await parent.remove({ session });
    });
}

Riskante en complexe oplossing

Dit werkt, en is totaal, verschrikkelijk complex. Niet aangeraden. Zal waarschijnlijk op een dag breken omdat het afhankelijk is van de fijne kneepjes van de mangoest-API. Ik weet niet waarom ik dit heb gecodeerd, neem het alsjeblieft niet op in je projecten .

import mongoose from "mongoose";
import mongodb from "mongodb";

const parentSchema = new mongoose.Schema({
    name: String,
    children: [{ type: mongoose.Schema.Types.ObjectId, ref: "Child" }],
});

const childSchema = new mongoose.Schema({
    name: String,
    parent: { type: mongoose.Schema.Types.ObjectId, ref: "Parent" },
});

// Choose a transaction timeout
const TRANSACTION_TIMEOUT = 120000; // milliseconds

// No need for next() callback if using an async function.
parentSchema.pre("remove", async function () {
    // `this` refers to the document, not the query
    let session = this.$session();

    // Check if this op is already part of a session, and start one if not.
    if (!session) {
        // `this.db` refers to the documents's connection.
        session = await this.db.startSession();

        // Set the document's associated session.
        this.$session(session);

        // Note if you created the session, so post can clean it up.
        this.$locals.localSession = true;

        //
    }

    // Check if already in transaction.
    if (!session.inTransaction()) {
        await session.startTransaction();

        // Note if you created transaction.
        this.$locals.localTransaction = true;

        // If you want a timeout
        this.$locals.startTime = new Date();
    }

    // Let's assume that we need to remove all parent references in the
    // children. (just add session-associated ops to extend this)
    await this.db
        .model("Child") // Child model of this connection
        .updateMany(
            { _id: { $in: this.children } },
            { $unset: { parent: true } }
        )
        .session(session);
});

parentSchema.post("remove", async function (parent) {
    if (this.$locals.localTransaction) {
        // Here, there may be an error when we commit, so we need to check if it
        // is a 'retryable' error, then retry if so.
        try {
            await this.$session().commitTransaction();
        } catch (err) {
            if (
                err instanceof mongodb.MongoError &&
                err.hasErrorLabel("TransientTransactionError") &&
                new Date() - this.$locals.startTime < TRANSACTION_TIMEOUT
            ) {
                await parent.remove({ session: this.$session() });
            } else {
                throw err;
            }
        }
    }

    if (this.$locals.localSession) {
        await this.$session().endSession();
        this.$session(null);
    }
});

// Specific error handling middleware if its really time to abort (clean up
// the injections)
parentSchema.post("remove", async function (err, doc, next) {
    if (this.$locals.localTransaction) {
        await this.$session().abortTransaction();
    }

    if (this.$locals.localSession) {
        await this.$session().endSession();
        this.$session(null);
    }

    next(err);
});




  1. Hoe kan ik mongodump gebruiken om records te dumpen die overeenkomen met een specifiek datumbereik?

  2. Morphia List<Map<String,Object>>> return Ingesloten element is geen DBObject bij zoekbewerking

  3. Meerdere Docker-containers en MongoDB instellen om te draaien in CircleCI

  4. Hoe converteer ik een BasicDBObject naar een Mongo Document met de Java Mongo DB driver versie 3?