Skip to content

Instantly share code, notes, and snippets.

@ming900518
Last active September 8, 2023 08:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ming900518/c39a16ae5fe4cbe9fe2ac896983ab60d to your computer and use it in GitHub Desktop.
Save ming900518/c39a16ae5fe4cbe9fe2ac896983ab60d to your computer and use it in GitHub Desktop.
MongoDB Transaction with TypeScript 5.2 `using` keyword

利用 TypeScript 5.2 的 using keyword ,實作 MongoDB Transaction

好笑的是,雖然 using 語法來自 C# ,但用 Rust 的 lifetime 機制來解釋反而更清楚

事前準備

  1. 至少一個有開啓 Replica Set 功能的 MongoDB instance
於 Docker 中搭建環境

本機須先安裝 mongosh

docker run -p 27017:27017 --name mongo --restart unless-stopped -d mongo mongod --replSet rs0

echo "rs.initiate({ _id: \"rs0\", members: [ { _id: 0, host: \"localhost:27017\" } ] })" | mongosh

  1. 專案 TypeScript 5.2 以上,並設置 tsconfig.json 如下
{
    "compilerOptions": {
        "target": "es2022",
        "lib": [
            "es2022",
            "esnext.disposable",
            "dom"
        ]
    }
    /* 其他原有的參數 */
}
  1. 安裝 core-js
npm i -D core-js

這個 TC39 Proposal 過了之後,就不需要做這步了

MongoTransaction class

import 'core-js/modules/esnext.symbol.async-dispose.js'; // 待 TC39 Proposal 通過後可移除
import 'core-js/modules/esnext.disposable-stack.constructor.js'; // 待 TC39 Proposal 通過後可移除

export class MongoTransaction implements AsyncDisposable {
  session: any;
  #abort: boolean = false;

  constructor(session: any) {
    this.session = session;
  }

  abort() {
    this.#abort = true;
  }

  [Symbol.asyncDispose]() {
    if (this.#abort) {
      return this.session
        .abortTransaction()
        .finally(() => this.session.endSession());
    } else {
      return this.session
        .commitTransaction()
        .finally(() => this.session.endSession());
    }
  }

  static async new(client: MongoClient) {
    const session = await client.startSession();
    session.startTransaction();
    return new MongoTransaction(session);
  }
}
Feathers 版(替換上方的 new static method)
static async new(app: Application) {
  const session = await app.get('mongoClient').startSession();
  session.startTransaction();
  return new MongoTransaction(session);
}

使用方式

await using transaction = await MongoTransaction.new(app);
try {
  await db('database').collection('collection').insertOne({test: "test data"}, {session: transaction.session})
} catch (error) {
  transaction.abort();
}
Feathers 版(替換上方的查詢)
await app.service('collection').Model.insertOne({test: "test data"}, {session: transaction.session})
  1. 利用 using keyword new 一個新的 MongoTransaction class。
  2. 對資料庫進行操作:
    在操作的後方加上 {session: transaction.session} ,讓 MongoDB 使用開啓了 Transaction 的 Session 處理本次操作。
  3. 錯誤處理中,可以加上 abort() method , 註記取消本次操作。

由於使用了 using keyword ,JavaScript 會在 transaction 不再被用到時,自動執行 MongoTransaction class 中的 [Symbol.asyncDispose]。程式會根據是否註記取消操作,寫入資料庫後主動關閉連線,無需手動操作。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment