This Go package provides a simple implementation of the Unit of Work pattern. It facilitates managing transactions across different data sources, ensuring atomicity and consistency.
This package is particularly useful in complex applications where multiple data sources are involved. The Unit of Work pattern promotes better software architecture by decoupling data access logic from core business processes. By abstracting away the specifics of each data source, the uow package simplifies the development and maintenance of your application, making it easier to manage transactions and ensure data consistency across disparate systems. This decoupling makes the system more robust, testable, and easier to scale.
go get github.com/agtabesh/uowSee CHANGELOG.md for version history and changes.
- Transaction Management: Handles transaction initiation, commit, and rollback across various data sources.
- Abstraction: Abstracts away the specifics of individual data sources, providing a consistent interface. This allows for easy swapping of data sources without modifying core application logic.
- Error Handling: Robust error handling, including rollback on failure. Provides informative error messages to aid in debugging.
- Testability: Designed for easy testing with mock implementations. Includes a
MockTximplementation for simplified unit testing. - Extensibility: The
Runnerinterface allows for easy integration with additional data sources. Simply implement the interface for your chosen data store and integrate with theUoW. - Context Awareness: Uses the Go context package to allow for cancellation and timeout handling during transactions.
The package revolves around two core types:
Defines the contract for a transactional data source:
type Runner interface {
Ctx(ctx context.Context) (context.Context, error)
Get(ctx context.Context) any
Commit(ctx context.Context) error
Rollback(ctx context.Context) error
}Orchestrates the transaction lifecycle. When you call Run, it executes the following sequence:
Ctx— starts a transaction and returns a context carrying the transaction handlefn— your business logic runs inside the transactionCommit— on success, persists the changesRollback— on any error, discards the changes
If both fn and Rollback fail, both errors are accessible via errors.Is.
The uow package provides a UoW struct which coordinates the unit of work. You'll need to provide a Runner implementation tailored to your data source. The Runner interface defines the necessary methods for managing transactions.
This package includes example implementations for:
MockTx: A mock implementation for testing purposes.MongoTx: An implementation for MongoDB usinggo.mongodb.org/mongo-driver/mongo.SQLTx: An implementation for any SQL database via the standarddatabase/sqlinterface.
package main
import (
"context"
"fmt"
"github.com/agtabesh/uow"
)
func main() {
// Create a new MockTx
mt := uow.NewMockTx()
// Create a new UoW using the MockTx
txs := uow.New(mt)
// Run the unit of work
err := txs.Run(context.Background(), func(ctx context.Context) error {
// Get the transaction state
tx := txs.Get(ctx).(*uow.State)
// Perform operations on the data source
tx.SetValue("Test Value")
// Simulate an error. Remove this line for successful commit
// return errors.New("simulated error")
return nil
})
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Transaction successful: %s\n", mt.state.Value())
}
}package main
import (
"context"
"fmt"
"github.com/agtabesh/uow"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
// Replace with your MongoDB connection string
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
panic(err)
}
defer client.Disconnect(context.TODO())
mt := uow.NewMongoTx(client, "your_database_name")
txs := uow.New(mt)
err = txs.Run(context.Background(), func(ctx context.Context) error {
// Get the database instance
db := txs.Get(ctx).( *mongo.Database)
// Perform operations on the database
// ...your MongoDB operations here...
return nil
})
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Println("Transaction successful!")
}
}package main
import (
"context"
"database/sql"
"fmt"
"github.com/agtabesh/uow"
_ "github.com/lib/pq" // PostgreSQL
)
func main() {
// Replace with your PostgreSQL connection string
db, err := sql.Open("postgres", "postgres://user:password@localhost/dbname?sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
sqlTx := uow.NewSQLTx(db)
txs := uow.New(sqlTx)
err = txs.Run(context.Background(), func(ctx context.Context) error {
// Get the transaction or database connection
tx := txs.Get(ctx).(*sql.Tx)
// Perform SQL operations within the transaction
_, err := tx.ExecContext(ctx, "INSERT INTO users (name) VALUES ($1)", "John Doe")
if err != nil {
return err
}
return nil
})
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Println("Transaction successful!")
}
}Supported SQL databases (via standard database/sql interface):
- PostgreSQL (using
github.com/lib/pqorgithub.com/jackc/pgx/v5/stdlib) - MySQL/MariaDB (using
github.com/go-sql-driver/mysql) - SQLite (using
github.com/mattn/go-sqlite3)
make test # run all tests
make lint # run golangci-lint
make coverage # generate coverage report
make build # build the package
make tidy # tidy Go modulesContributions are welcome! Please open an issue or submit a pull request. Before submitting, ensure your changes pass linting and tests:
make lint && make testThis project is licensed under the MIT License.