✨ Feature Proposal: Simplify Transaction Handling with Wrapper APIs
使用场景 | Use Case Scenario
In the official MongoDB Go driver, transaction handling requires multiple manual steps, such as session management, transaction start/commit/abort, and context propagation. This results in verbose boilerplate code and increases the risk of errors, especially in complex workflows.
To align with mongox's goal of improving developer ergonomics, we propose introducing high-level transaction wrapper APIs to simplify these operations.
可行方案 | Feasible Solutions
// Simplified transaction handling with automatic session management
func (c *Client) RunTransaction(
ctx context.Context,
fn func(ctx context.Context) (any, error),
txnOptions ...options.Lister[options.TransactionOptions],
) (any, error)
// Advanced transaction handling with manual session control
func (c *Client) WithManualTransaction(
ctx context.Context,
fn func(ctx context.Context, session *mongo.Session, txnOptions ...options.Lister[options.TransactionOptions]) error,
txnOptions ...options.Lister[options.TransactionOptions],
) error
Usage Examples:
- RunTransaction (Automatic session management):
result, err := client.RunTransaction(ctx, func(txCtx context.Context) (any, error) {
// Perform multiple operations within the transaction
_, err := userColl.Creator().InsertOne(txCtx, &User{Name: "Mingyong Chen", Age: 18})
if err != nil {
return nil, err
}
_, err := userColl.Updater().
Filter(query.Id("60e96214a21b1b0001c3d69e")).
Updates(update.Set("name", "Mingyong Chen")).
UpdateOne(txCtx)
if err != nil {
return nil, err
}
return "Transaction Successful", nil
})
if err != nil {
log.Fatalf("Transaction failed: %v", err)
}
fmt.Println("Transaction result:", result)
- WithManualTransaction (Manual session control):
err := client.WithManualTransaction(ctx, func(txCtx context.Context, session *mongo.Session, txnOptions ...options.Lister[options.TransactionOptions]) error {
// Start a transaction manually
if err = session.StartTransaction(txnOptions); err != nil {
return err
}
// Perform operations
_, err := userColl.Creator().InsertOne(txCtx, &User{Name: "Mingyong Chen", Age: 18})
if err != nil {
return nil, err
}
_, err := userColl.Updater().
Filter(query.Id("60e96214a21b1b0001c3d69e")).
Updates(update.Set("name", "Mingyong Chen")).
UpdateOne(txCtx)
if err != nil {
return nil, err
}
if err = session.CommitTransaction(txCtx); err != nil {
return err
}
return nil
})
if err != nil {
log.Fatalf("Transaction failed: %v", err)
}
fmt.Println("Transaction completed successfully")
These methods:
- Default to
writeConcern.Majority()(customizable viatxnOptions). - Automatically manage session lifecycle.
- Promote a clear, consistent pattern for transaction use.
This design was inspired by the official Go driver usage patterns and aims to streamline the most common transaction use cases.
其它 | Others
We’d love to gather feedback on:
- API naming: Are
RunTransactionandWithManualTransactionintuitive? Any preferred alternatives? - Design feedback: Does the proposed design align with your expectations for simplicity and flexibility?
- Additional features: Are there other transaction-related features you'd like to see in
mongox? For example:- Retry strategies for transient errors.
- Pre/post hooks for transaction lifecycle events.
- Metrics or logging integration for transaction monitoring.
Looking forward to community input and suggestions 🙌
If you’re interested in contributing, feel free to submit a PR!
RunAutoTransaction RunManualTransaction
RunAutoTransaction RunManualTransaction
@codepzj Thank you for your suggestion! I think your proposed names, RunAutoTransaction and RunManualTransaction, do make the distinction between the two methods more explicit. However, from a best practices perspective, default behavior (automatic transaction) usually doesn’t need to be explicitly highlighted. Therefore, I’m leaning toward the following naming:
RunTransaction(for automatic transaction management, as the default implementation)RunManualTransaction(for manual transaction management, clearly indicating its special nature)
This approach keeps the naming concise while clearly distinguishing the two transaction handling methods, and it aligns with common naming conventions. Thanks again for your input, and I’m happy to discuss further! 😊