Problem working with transactions using knex/objection

90 views Asked by At

I have two databases that are used on an endpoint, this endpoint is developed with nodejs and serverless-framework, that is, it is a lambda function.

I need to insert the information using transactions in both, in case an error occurs, the information does not persist, however, in some cases, I receive an error when performing a select on one of the bases.

Error: Transaction query already complete, run with DEBUG=knex:tx for more info

The log for DEBUG=knex:tx is:

knex:tx trx1: Transaction completed: select 'cod_farmacia' from 'farmacia' where 'cod_farmacia' = ?

This error occurs infrequently, usually the process happens normally, without any errors, but it is something that interferes a lot.

Below is the code where I manage the database: connection, commits and rollbacks of transactions.

import Knex, { Knex as KnexTypes } from 'knex';
import env from './env';

interface IConfigKnex {
  client: string;
  connection: object;
}

interface ITransactions {
  drugstore: KnexTypes.Transaction;
  payment: KnexTypes.Transaction;
}

class DB {
  transactions = <ITransactions>{
    drugstore: null,
    payment: null,
  };

  connectToDrugstore = async () => {
    if (this.transactions.drugstore) {
      return true;
    }

    const knex = Knex(<IConfigKnex>{
      client: 'mysql',
      connection: {
        host: env('DB_DRUGSTORE_HOST'),
        user: env('DB_DRUGSTORE_USER'),
        password: env('DB_DRUGSTORE_PASSWORD'),
        database: env('DB_DRUGSTORE_DATABASE'),
        port: env('DB_DRUGSTORE_PORT'),
        timezone: env('TIMEZONE'),
      },
    });

    const trx = await knex.transaction();

    this.transactions = {
      ...this.transactions,
      drugstore: trx,
    };

    return true;
  };

  commitDrugstore = async () => {
    if (this.transactions.drugstore === null) {
      return true;
    }
    await this.transactions.drugstore.commit();
    return true;
  };

  connectToPayment = async () => {
    if (this.transactions.payment) {
      return true;
    }

    const knex = Knex(<IConfigKnex>{
      client: 'mysql',
      connection: {
        host: env('DB_PAYMENT_HOST'),
        user: env('DB_PAYMENT_USER'),
        password: env('DB_PAYMENT_PASSWORD'),
        database: env('DB_PAYMENT_DATABASE'),
        port: env('DB_PAYMENT_PORT'),
        timezone: env('TIMEZONE'),
      },
    });

    const trx = await knex.transaction();

    this.transactions = {
      ...this.transactions,
      payment: trx,
    };

    return true;
  };

  commitPayment = async () => {
    if (this.transactions.payment === null) {
      return true;
    }

    await this.transactions.payment.commit();

    return true;
  };

  connectToAll = async () => {
    await this.connectToDrugstore();
    await this.connectToPayment();

    return true;
  };

  commitAll = async () => {
    await this.commitDrugstore();
    await this.commitPayment();

    return true;
  };

  closeConnections = async () => {
    if (this.transactions.payment !== null) {
      await this.transactions.payment.destroy();
    }

    if (this.transactions.drugstore !== null) {
      await this.transactions.drugstore.destroy();
    }
  };
}

export default DB;

Endpoint code, I left it summarized so as not to contain a lot of private information, but it contains the calls to make the connection, commit and close the connection with the database.

export class Payment {
  paymentTrx: Knex.Transaction;
  drugstoreTrx: Knex.Transaction;

  constructor(private connectDB: DB) {}

  async request(event: APIGatewayEvent): Promise<any> {
    try {
      await this.connectDB.connectToAll();

      this.drugstoreTrx = await this.connectDB.transactions['drugstore'];
      this.paymentTrx = await this.connectDB.transactions['payment'];
  
      const response = await getDrugstoreById(10, 1, this.drugstoreTrx);

      // Rest of code..

      await this.connectDB.commitAll();

      return {
        statusCode: 200,
        message: "ok"
      };
    } catch (error: AxiosError | any) {
      console.log('Error payment.ts', error);

      if (error.isAxiosError) {
        throwApiError(this.payload.payment_api_id, error);
      } else {
        throw new HttpResponseError({
          statusCode: 500,
          message: 'Erro desconhecido',
        });
      }
    } finally {
      console.log('fechando conexão...');
      await connectDB.closeConnections();
      console.log('conexão fechada');
    }
  }
}

const connectDB = new DB();

export const handler = handleAuthBasic(
  async (event: APIGatewayEvent) => await new Payment(connectDB).request(event)
);

And here is the code for the getDrugstoreById function.

import { Knex } from 'knex';
import { Model } from 'objection';

import { IPaymentGatewaySettings } from './PaymentGatewaySettings';

class Drugstore extends Model {
  static modelPaths = [__dirname];

  static get tableName() {
    return 'farmacia';
  }

  static relationMappings = {
    payment_gateway_settings: {
      relation: Model.HasManyRelation,
      modelClass: 'PaymentGatewaySettings',
      join: {
        to: 'payment_gateway_settings.drugstore_id',
        from: 'farmacia.cod_farmacia',
      },
    },
  };
}

export default <any>Drugstore;

export const getDrugstoreById = async (
  drugstore_id: number,
  payment_gateway_settings_id: number,
  trx: Knex.Transaction
): Promise<DrugstoreDatabaseResponse> => {
  const drugstore: DrugstoreDatabaseResponse = <any>await Drugstore.query(trx)
    .select('cod_farmacia')
    .where('cod_farmacia', drugstore_id)
    .withGraphFetched('payment_gateway_settings(select)')
    .modifiers({
      select(builder) {
        builder.select().where('id', payment_gateway_settings_id);
      },
    })
    .first();

  return drugstore;
};
0

There are 0 answers