Search code examples
databasemongodbgotransactionsmongo-go

MongoDB transactions, the callback API approach


Having read many doc/articles on MongoDB transactions, I still need further clarification.

Here it says:

MongoDB provides two APIs to use transactions. The first is the core API which has similar syntax to relational databases. The second, the callback API, is the recommended approach to using transactions in MongoDB.

Yet it went on introduced the core API approach, and did not touch the recommended approach at all.

The official doc here says,

This example highlights the key components of the transactions API. In particular, it uses the callback API. The callback API:

  • starts a transaction
  • executes the specified operations
  • commits the result (or aborts on error)

However, when it comes to the most important "executes the specified operations" step, the example didn't shown any relevant code. I.e., as asked in MongoDB Transactions In NodeJS, I "still need a real example".

PS. In case the example get changed or goes away, here is the Golang version:

// WithTransactionExample is an example of using the Session.WithTransaction function.
func WithTransactionExample(ctx context.Context) error {
    // For a replica set, include the replica set name and a seedlist of the members in the URI string; e.g.
    // uri := "mongodb://mongodb0.example.com:27017,mongodb1.example.com:27017/?replicaSet=myRepl"
    // For a sharded cluster, connect to the mongos instances; e.g.
    // uri := "mongodb://mongos0.example.com:27017,mongos1.example.com:27017/"
    uri := mtest.ClusterURI()
    clientOpts := options.Client().ApplyURI(uri)
    client, err := mongo.Connect(ctx, clientOpts)
    if err != nil {
        return err
    }
    defer func() { _ = client.Disconnect(ctx) }()
    // Prereq: Create collections.
    wcMajority := writeconcern.Majority()
    wcMajority.WTimeout = 1 * time.Second
    wcMajorityCollectionOpts := options.Collection().SetWriteConcern(wcMajority)
    fooColl := client.Database("mydb1").Collection("foo", wcMajorityCollectionOpts)
    barColl := client.Database("mydb1").Collection("bar", wcMajorityCollectionOpts)
    // Step 1: Define the callback that specifies the sequence of operations to perform inside the transaction.
    callback := func(sessCtx mongo.SessionContext) (interface{}, error) {
        // Important: You must pass sessCtx as the Context parameter to the operations for them to be executed in the
        // transaction.
        if _, err := fooColl.InsertOne(sessCtx, bson.D{{"abc", 1}}); err != nil {
            return nil, err
        }
        if _, err := barColl.InsertOne(sessCtx, bson.D{{"xyz", 999}}); err != nil {
            return nil, err
        }
        return nil, nil
    }
    // Step 2: Start a session and run the callback using WithTransaction.
    session, err := client.StartSession()
    if err != nil {
        return err
    }
    defer session.EndSession(ctx)
    result, err := session.WithTransaction(ctx, callback)
    if err != nil {
        return err
    }
    log.Printf("result: %v\n", result)
    return nil
}

It looks to me from the example that the most important "executes the specified operations" step is done in callback. Is that so? If so, the official doc really needs to emphasize on that.


Solution

  • The example is complete. It contains this key comment:

    // Step 2: Start a session and run the callback using WithTransaction.
    

    So the callback function is executed by the Session.WithTransaction() method. You pass the callback function to it, and it will be called by the Session.WithTransaction() method.

    The implementation ensures the operations done in the callback function will be executed as a transaction (that is, either all will be applied or none of them). If the callback function returns a non-nil error, the transaction will be aborted, else the transaction will be committed.