{"id":50409454,"url":"https://github.com/metalfm/transactor","last_synced_at":"2026-06-11T17:00:42.658Z","repository":{"id":289659402,"uuid":"966702781","full_name":"metalfm/transactor","owner":"metalfm","description":"Transactor is a library for simplifying transaction management in Go","archived":false,"fork":false,"pushed_at":"2026-05-17T19:43:14.000Z","size":125,"stargazers_count":4,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-05-17T20:46:20.906Z","etag":null,"topics":["database","go","golang","transaction-manager","transactions"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/metalfm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-04-15T10:24:12.000Z","updated_at":"2026-03-22T13:20:51.000Z","dependencies_parsed_at":"2025-04-24T12:34:04.450Z","dependency_job_id":null,"html_url":"https://github.com/metalfm/transactor","commit_stats":null,"previous_names":["metalfm/transactor"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/metalfm/transactor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metalfm%2Ftransactor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metalfm%2Ftransactor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metalfm%2Ftransactor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metalfm%2Ftransactor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/metalfm","download_url":"https://codeload.github.com/metalfm/transactor/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metalfm%2Ftransactor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34208761,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","go","golang","transaction-manager","transactions"],"created_at":"2026-05-31T03:00:25.609Z","updated_at":"2026-06-11T17:00:42.651Z","avatar_url":"https://github.com/metalfm.png","language":"Go","funding_links":[],"categories":["Data Integration Frameworks","Database Drivers"],"sub_categories":["Interfaces to Multiple Backends"],"readme":"# Transactor\n\n[![CI](https://github.com/metalfm/transactor/actions/workflows/ci.yml/badge.svg)](https://github.com/metalfm/transactor/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/metalfm/transactor/graph/badge.svg)](https://codecov.io/gh/metalfm/transactor)\n[![Go Report Card](https://goreportcard.com/badge/github.com/metalfm/transactor)](https://goreportcard.com/report/github.com/metalfm/transactor)\n[![Go Reference](https://pkg.go.dev/badge/github.com/metalfm/transactor.svg)](https://pkg.go.dev/github.com/metalfm/transactor)\n\n`Transactor` is a library for simplifying transaction management in Go.\n\nIt provides the `Transactor[T any]` interface,\nwhich allows performing operations within a transaction while abstracting the transaction management logic.\n\n## Installation\n\nAdd the module to your project:\n\n```bash\ngo get github.com/metalfm/transactor\n```\n\nThen import the core package and the driver implementation your project uses.\n\n### Using `database/sql`\n\n```go\nimport (\n\t\"github.com/metalfm/transactor/tr\"\n\t\"github.com/metalfm/transactor/driver/sql/trm\"\n)\n```\n\n### Using `sqlx`\n\n```go\nimport (\n\t\"github.com/metalfm/transactor/tr\"\n\t\"github.com/metalfm/transactor/driver/sqlx/trm\"\n)\n```\n\n### Using `pgx`\n\n```go\nimport (\n\t\"github.com/metalfm/transactor/tr\"\n\t\"github.com/metalfm/transactor/driver/pgx/trm\"\n)\n```\n\nCurrently, the `transactor` library supports the `database/sql` driver from Go's standard library, `sqlx`, and `pgx`.\n\n## Key Concepts\n\n### 1. `Transactor[T any]` Interface\n\nThe interface is simple, and `[T any]` means it can accept any type, allowing it to work with various repository\nimplementations while maintaining type safety at compile time.\n\n```go\ntype Transactor[T any] interface {\n    InTx(ctx context.Context, fn func (T) error) error\n}\n```\n\nThe `InTx` method takes a context and a function. This function contains the logic that should be executed within the\ntransaction.\n`T` is the type of repository that will be used in the business logic.\n\n- If the function returns an error, the transaction is rolled back.\n- If the function completes successfully, the transaction is committed.\n\nExample usage:\n\n```go\npackage example\n\ntype repoTx interface {\n    CreateUser(ctx context.Context, name string) error\n    CreateOrder(ctx context.Context, items []string) error\n}\nerr := transactor.InTx(ctx, func (repo repoTx) error {\n    err := repo.CreateUser(ctx, \"John Doe\")\n    if err != nil {\n        return err\n    }\n\n    err = repo.CreateOrder(ctx, []string{\"item1\", \"item2\"})\n    if err != nil {\n        return err\n    }\n\n    return nil\n})\n```\n\nNote that all dependencies are based on interfaces, making it easy to mock them in tests as well as specific\nimplementations.\n\n### 2. Repositories and Factory Method\n\nRepositories depend on the `trm.Query` interface, which provides methods for executing SQL queries. This interface is\npart of the specific database driver implementation.\nThe `trm.Transaction` interface, which extends `trm.Query`, is used for transaction management and adds `Commit` and\n`Rollback` methods.\n\n#### Definition of `trm.Query` and `trm.Transaction` Interfaces\n\n```go\npackage trm\n\nimport (\n    \"context\"\n    \"database/sql\"\n)\n\n// Query — interface for executing SQL queries.\ntype Query interface {\n    ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)\n    PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)\n    QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)\n    QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row\n}\n\n// Transaction — interface for transaction management.\n// Extends Query and adds Commit and Rollback methods.\ntype Transaction interface {\n    Query\n    Commit() error\n    Rollback() error\n}\n```\n\n#### Factory Method `WithTx`\n\nThe factory method `WithTx` is used for transaction management and returns a new instance of the repository associated\nwith the `trm.Transaction`. This isolates transaction logic within repositories.\n\nExample implementation of the factory method:\n\n```go\npackage example\n\nimport (\n    \"github.com/metalfm/transactor/driver/sql/trm\"\n)\n\ntype RepoUser struct {\n    q trm.Query\n}\n\nfunc NewRepoUser(q trm.Query) *RepoUser {\n    return \u0026RepoUser{q}\n}\n\n// WithTx example of a factory method\n// all methods of *RepoUser will be called within the transaction\nfunc (slf *RepoUser) WithTx(tx trm.Transaction) *RepoUser {\n    return \u0026RepoUser{q: tx}\n}\n```\n\nUsing the factory method allows explicit transaction passing, making the code more readable and safer. Note that the\nfactory method `WithTx` returns a new instance of `*RepoUser`, and duck typing avoids importing interfaces into business\nlogic.\n\nBe careful when implementing `WithTx`: the library cannot verify that the returned repository actually uses the provided\ntransaction. Returning `nil`, returning the original repository, or returning a repository that still uses the original\ndatabase connection can lead to runtime panics or queries executed outside the transaction.\n\nIncorrect implementation:\n\n```go\nfunc (slf *RepoUser) WithTx(tx trm.Transaction) *RepoUser {\n    return slf\n}\n```\n\nCorrect implementation:\n\n```go\nfunc (slf *RepoUser) WithTx(tx trm.Transaction) *RepoUser {\n    return \u0026RepoUser{q: tx}\n}\n```\n\n### 3. Adapter for Repositories\n\nThe adapter is not part of the `transactor` library but provides the ability to combine code from various repositories\nusing an adapter. The adapter encapsulates the logic of working with multiple repositories, providing a unified\ninterface for working with them, including performing operations within a single transaction.\n\n```go\npackage example\n\nimport (\n    \"github.com/metalfm/transactor/driver/sql/trm\"\n)\n\ntype Adapter struct {\n    repoUser  *RepoUser\n    repoOrder *RepoOrder\n}\n\nfunc NewAdapter(repoUser *svc.RepoUser, repoOrder *svc.RepoOrder) *Adapter {\n    return \u0026Adapter{\n        repoUser:  repoUser,\n        repoOrder: repoOrder,\n    }\n}\n\n// WithTx example of a factory method for combining logic from multiple repositories\nfunc (slf *Adapter) WithTx(tx trm.Transaction) *Adapter {\n    return \u0026Adapter{\n        repoUser:  slf.repoUser.WithTx(tx),\n        repoOrder: slf.repoOrder.WithTx(tx),\n    }\n}\n```\n\n### 4. Why is the Factory Method Better Than Passing Transactions Through Context?\n\n- **Explicitness**: Transactions are passed explicitly through the factory method, not hidden in the context, making the\n  code more readable and understandable.\n- **Safety**: Context is intended for passing request-related data (e.g., timeouts or metadata), not for managing\n  transaction state.\n- **Encapsulation**: The factory method isolates transaction logic within repositories, preventing it from spreading to\n  other parts of the code.\n- **Testability**: The factory method simplifies creating mocks for testing since the transaction remains part of the\n  repository interface.\n- **Performance**: Passing transactions through the factory method does not require additional operations, such as\n  extracting data from the context or type casting. This makes transaction management faster and more efficient compared\n  to using context.\n\n### 5. Example Service\n\nThe `Service` contains business logic and depends only on the local `repoTx` contract and a small transactional\ncallback. It knows nothing about the internal structure of transactions, simplifying testing and isolating logic.\n\nExample:\n\n```go\npackage app\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"github.com/metalfm/transactor/tr\"\n)\n\n// repoTx declares dependencies for business logic\n// all repository methods that will be used within the transaction\ntype repoTx interface {\n    CreateUser(ctx context.Context, name string) error\n    CreateOrder(ctx context.Context, items []string) error\n}\n\ntype inTx func(context.Context, func(repoTx) error) error\n\ntype Service struct {\n    inTx inTx\n}\n\nfunc NewService[T repoTx](tr tr.Transactor[T]) *Service {\n    return \u0026Service{\n        inTx: func(ctx context.Context, fn func(repoTx) error) error {\n            return tr.InTx(ctx, func(r T) error { return fn(r) })\n        },\n    }\n}\n\nfunc (slf *Service) Create(ctx context.Context, name string, items []string) error {\n    err := slf.inTx(ctx, func(r repoTx) error {\n        err := r.CreateUser(ctx, name)\n        if err != nil {\n            return fmt.Errorf(\"create user: %w\", err)\n        }\n\n        err = r.CreateOrder(ctx, items)\n        if err != nil {\n            return fmt.Errorf(\"create order: %w\", err)\n        }\n\n        return nil\n    })\n    if err != nil {\n        return fmt.Errorf(\"create user \u0026 order: %w\", err)\n    }\n\n    return nil\n}\n```\n\nYou can find the full example here — [example](https://github.com/metalfm/transactor/tree/master/internal/example).\n\n### 6. Testing and `trtest` Package\n\nTo simplify testing, the library provides the `trtest` package, which allows creating mock implementations of the\n`Transactor[T any]` interface. This is useful for isolating business logic from the real database.\n\nExample usage of\n`trtest.MockTransactor` — [example](https://github.com/metalfm/transactor/blob/master/internal/example/app/service_test.go)\n\n## Benchmarks\n\nAll benchmarks were conducted using the following setup:\n\n- **Machine**: Apple M1 Pro (Darwin, arm64)\n- **Database**: PostgreSQL running in Docker\n\nTo reproduce the benchmarks, ensure you have Docker installed and run the following commands:\n```bash\nmake up \u0026\u0026 make bench\n```\n\n### Libraries Used in Comparison\n\nThe following libraries and approaches were used for benchmarking:\n\n1. **Native** — a basic approach using the standard `sql.DB` driver from Go's standard library without additional\n   abstractions.\n2. **⚡ Transactor** — the tested `Transactor` library, which provides the `Transactor[T any]` interface for transaction\n   management.\n3. **[Avito](https://github.com/avito-tech/go-transaction-manager)** — an approach based on the transaction manager\n   implementation used in Avito projects.\n4. **[Aneshas](https://github.com/aneshas/tx)** — an alternative library for transaction management.\n5. **[Thiht](https://github.com/Thiht/transactor)** — another library for transaction management.\n\nEach approach was tested on identical scenarios to ensure an objective comparison of performance, memory consumption,\nand allocation count.\n\n### Benchmark Results\n\n#### Execution Time (sec/op)\n\n| Metric     | Native      | ⚡ Transactor               | Avito                           | Aneshas                    | Thiht                      |\n|------------|-------------|----------------------------|---------------------------------|----------------------------|----------------------------|\n| **sec/op** | 289.6µ ± 4% | 284.5µ ± 2% -1.75% (p=0.025) | 288.6µ ± 2% ~ (p=0.383) | 283.4µ ± 1% -2.14% (p=0.006) | 285.8µ ± 1% ~ (p=0.086) |\n\n#### Memory Consumption (B/op)\n\n| Metric   | Native     | ⚡ Transactor         | Avito                  | Aneshas              | Thiht                 |\n|----------|------------|----------------------|------------------------|----------------------|-----------------------|\n| **B/op** | 1.185Ki ± 3% | 1.277Ki ± 1% +7.75% | 1.823Ki ± 2% +53.81% | 1.283Ki ± 1% +8.28% | 1.312Ki ± 2% +10.71% |\n\n#### Allocation Count (allocs/op)\n\n| Metric        | Native     | ⚡ Transactor          | Avito                 | Aneshas               | Thiht                 |\n|---------------|------------|-----------------------|-----------------------|-----------------------|-----------------------|\n| **allocs/op** | 30.00 ± 3% | 33.00 ± 3% +10.00% | 46.00 ± 0% +53.33% | 34.00 ± 3% +13.33% | 34.50 ± 1% +15.00% |\n\n### Benchmark Analysis\n\n#### Execution Time (`sec/op`):\n\n- **native**: 289.6µs ± 4% — baseline performance.\n- **⚡ transactor**: 284.5µs ± 2% — slightly faster than native in this run (-1.75%, p=0.025).\n- **avito**: 288.6µs ± 2% — close to native, statistically insignificant (p=0.383).\n- **aneshas**: 283.4µs ± 1% — slightly faster than native in this run (-2.14%, p=0.006).\n- **Thiht**: 285.8µs ± 1% — close to native, statistically insignificant (p=0.086).\n\n#### Memory Consumption (`B/op`):\n\n- **native**: 1.185 KiB ± 3% — baseline memory usage.\n- **⚡ transactor**: 1.277 KiB ± 1% — a **7.75%** increase.\n- **avito**: 1.823 KiB ± 2% — a **53.81%** increase.\n- **aneshas**: 1.283 KiB ± 1% — an **8.28%** increase.\n- **Thiht**: 1.312 KiB ± 2% — a **10.71%** increase.\n\n#### Allocation Count (`allocs/op`):\n\n- **native**: 30.00 ± 3% — baseline allocation count.\n- **⚡ transactor**: 33.00 ± 3% — a **10.00%** increase.\n- **avito**: 46.00 ± 0% — a **53.33%** increase.\n- **aneshas**: 34.00 ± 3% — a **13.33%** increase.\n- **Thiht**: 34.50 ± 1% — a **15.00%** increase.\n\n### Overall Conclusion\n\n- **native** remains the baseline for performance.\n- **⚡ transactor** introduces moderate overhead in memory and allocations while keeping execution time close to native.\n- **avito** has the highest memory consumption and allocation count in this benchmark.\n- **aneshas** and **Thiht** show similar memory and allocation profiles, with `Thiht` consuming slightly more.\n\n✅ **`transactor` remains an optimal choice** for projects requiring a balance between performance, memory consumption, and architectural clarity.\n\n## Design Trade-offs\n\n### Nested Transactions\n\n`transactor` does not support nested transactions by design. They are not needed for most application code and introduce\nadditional complexity that tends to leak into business logic. In most cases, a single transaction boundary around a use\ncase is enough. Compose several operations inside the same `InTx` callback instead of opening another transaction inside\nit.\n\n### Isolation Levels\n\n`transactor` intentionally does not expose transaction isolation level configuration. In most PostgreSQL-backed\napplications, [advisory locks](https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS) are a\nsimpler and faster way to coordinate concurrent business operations, and they cover the common cases without adding\nisolation-level decisions to business logic.\n\n## License\n\nTransactor is licensed under the MIT License. See [LICENSE](https://github.com/metalfm/transactor/blob/master/LICENSE)\nfor more\ninformation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetalfm%2Ftransactor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmetalfm%2Ftransactor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetalfm%2Ftransactor/lists"}