sql >> Databáze >  >> NoSQL >> MongoDB

Mangusta | Middleware | Operace vrácení provedené pre/post hooky, když je vyvolána chyba

TLDR; Middleware Mongoose pro to nebyl navržen.

Tato metoda vkládání transakcí ve skutečnosti opravuje funkčnost middlewaru a v podstatě vytváříte rozhraní API zcela oddělené od mongoose middleware.

Co by bylo lepší, je převrátit logiku pro váš dotaz na odstranění v samostatné funkci.

Jednoduché a zamýšlené řešení

Umožněte metodě zpracování transakcí, aby udělala své kouzlo, a vytvořte samostatnou metodu odebrání pro váš nadřazený model. Mongoose zabalí mongodb.ClientSession.prototype.withTransaction s mongoose.Connection.prototype.transaction a nemusíme ani vytvářet instanci nebo spravovat relaci! Podívejte se na rozdíl mezi délkou tohoto a toho níže. A ušetříte si mentální bolesti hlavy při zapamatování vnitřních částí tohoto middlewaru za cenu jedné samostatné funkce.


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!
}

Malé rozšíření

Dalším způsobem, jak si to usnadnit, je zaregistrovat middleware, který jednoduše zdědí relaci iff _ dotaz má jeden zaregistrovaný. Možná vyvolá chybu, pokud transakce nebyla zahájena.

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 });
    });
}

Rizikové a složité řešení

Tohle funguje a je to naprosto, strašně složité. Nedoporučeno. Pravděpodobně se jednoho dne zlomí, protože se spoléhá na složitosti rozhraní API pro mongoose. Nevím, proč jsem to zakódoval, nezahrnujte to prosím do svých projektů .

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. Agregát MongoDB v rámci denního seskupení

  2. MongoDB není autorizován pro dotaz - kód 13

  3. Alternativy k vnořeným strukturám v Redis?

  4. Problémy s hledáním/načítáním Meteor MongoDB