{"id":15002567,"url":"https://github.com/thiht/transactor","last_synced_at":"2025-04-05T22:04:40.151Z","repository":{"id":244310418,"uuid":"814363399","full_name":"Thiht/transactor","owner":"Thiht","description":"Transactor is an injectable type making DB transactions seamless.","archived":false,"fork":false,"pushed_at":"2025-03-31T16:50:27.000Z","size":172,"stargazers_count":130,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-31T17:48:01.934Z","etag":null,"topics":["go","golang","mssql","mysql","oracle","pgx","postgres","sql","sqlite","sqlx","transactions","transactor"],"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/Thiht.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","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},"funding":{"buy_me_a_coffee":"thiht","custom":["https://www.blockchain.com/explorer/addresses/btc/bc1qawxn5u2rqupvp043rwamn67d383ggd2tc2tfr3"]}},"created_at":"2024-06-12T21:38:10.000Z","updated_at":"2025-03-28T10:50:55.000Z","dependencies_parsed_at":"2024-06-14T00:40:46.176Z","dependency_job_id":"54f79ac7-c1f4-48b2-a614-eae12a542bf1","html_url":"https://github.com/Thiht/transactor","commit_stats":{"total_commits":47,"total_committers":2,"mean_commits":23.5,"dds":"0.36170212765957444","last_synced_commit":"bb90f3449285008013bb88fdf1b09cb0d3ae5518"},"previous_names":["thiht/transactor"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thiht%2Ftransactor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thiht%2Ftransactor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thiht%2Ftransactor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thiht%2Ftransactor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Thiht","download_url":"https://codeload.github.com/Thiht/transactor/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247406085,"owners_count":20933803,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["go","golang","mssql","mysql","oracle","pgx","postgres","sql","sqlite","sqlx","transactions","transactor"],"created_at":"2024-09-24T18:51:10.084Z","updated_at":"2025-04-05T22:04:40.129Z","avatar_url":"https://github.com/Thiht.png","language":"Go","readme":"# transactor\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/Thiht/transactor.svg)](https://pkg.go.dev/github.com/Thiht/transactor) [![Main Pipeline](https://github.com/Thiht/transactor/actions/workflows/main.yml/badge.svg)](https://github.com/Thiht/transactor/actions/workflows/main.yml) [![Coverage](https://codecov.io/github/Thiht/transactor/graph/badge.svg?token=NK6KCBMTR6)](https://codecov.io/github/Thiht/transactor)\n\nThe **transactor** pattern is a way to manage transactions seamlessly.\nYou can inject your transactor in your services to make transactions completely transparently.\n\nIt relies mostly on the [`Transactor` interface](./transactor.go):\n\n```go\ntype Transactor interface {\n  WithinTransaction(context.Context, func(context.Context) error) error\n}\n```\n\n`WithinTransaction` starts a new transaction and adds it to the context. Any repository method can then retrieve a transaction from the context or fallback to the initial DB handler. The transaction is committed if the provided function doesn't return an error. It's rollbacked otherwise.\n\n## Usage\n\n### Installation\n\n```sh\ngo get github.com/Thiht/transactor\n```\n\nThe `database/sql` default implementation (`stdlib`) is included in the `github.com/Thiht/transactor` package.\nAdditional implementations are available in separate modules:\n\n- the [`pgx`](https://github.com/jackc/pgx) implementation is available in `github.com/Thiht/transactor/pgx`,\n- the [`sqlx`](https://github.com/jmoiron/sqlx) implementation is available in `github.com/Thiht/transactor/sqlx`.\n\nThe following examples use the `stdlib` implementation, but the code isn't too different with the other implementations.\n\n### Initialize a `transactor`\n\nThis example uses `database/sql` with the [`pgx`](https://github.com/jackc/pgx) driver, but any `database/sql` driver can be used.\n\n```go\nimport stdlibTransactor \"github.com/Thiht/transactor/stdlib\"\n\ndb, _ := sql.Open(\"pgx\", dsn)\n\ntransactor, dbGetter := stdlibTransactor.NewTransactor(\n  db,\n  stdlibTransactor.NestedTransactionsSavepoints,\n)\n```\n\nThe currently available strategies for nested transactions with the `stdlib` implementation are:\n\n- [NestedTransactionsSavepoints](./stdlib/nested_transactions_savepoints.go), an implementation using `SAVEPOINTS` and compatible with [PostgreSQL](https://www.postgresql.org/docs/16/sql-savepoint.html), [MySQL](https://dev.mysql.com/doc/refman/8.0/en/savepoint.html), [MariaDB](https://mariadb.com/kb/en/savepoint/), and [SQLite](https://sqlite.org/lang_savepoint.html),\n- [NestedTransactionsOracle](./stdlib/nested_transactions_oracle.go), an implementation using [Oracle savepoints](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SAVEPOINT.html),\n- [NestedTransactionsMSSQL](./stdlib/nested_transactions_mssql.go), an implementation using [Microsoft SQL Server savepoints](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/save-transaction-transact-sql?view=sql-server-ver16),\n- [NestedTransactionsNone](./stdlib/nested_transactions_none.go), an implementation that prevents using nested transactions.\n\n### Use the `dbGetter` in your repositories\n\nInstead of injecting the `*sql.DB` handler directly to your repositories, you now have to inject the `dbGetter`. It will return the appropriate DB handler depending on whether the current execution is in a transaction.\n\n```diff\ntype store struct {\n-  db *sql.DB\n+  dbGetter stdlibTransactor.DBGetter\n}\n\nfunc (s store) GetBalance(ctx context.Context, account string) (int, error) {\n  var balance int\n-  err := s.db.QueryRowContext(\n+  err := s.dbGetter(ctx).QueryRowContext(\n    ctx,\n    `SELECT balance FROM accounts WHERE account = $1`,\n    account,\n  ).Scan(\u0026balance)\n  return balance, err\n}\n```\n\nYou can use the `IsWithinTransaction` helper if you need to implement different behaviours depending on whether a transaction is running.\nFor example with PostgreSQL, you could add [`FOR UPDATE`](https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE) conditionally:\n\n```go\nfunc (s store) GetBalance(ctx context.Context, account string) (int, error) {\n  query := `SELECT balance FROM accounts WHERE account = $1`\n  if stdlibTransactor.IsWithinTransaction(ctx) {\n    query += ` FOR UPDATE`\n  }\n\n  // ...\n}\n```\n\n### Use the `transactor` in your services\n\n```go\ntype service struct {\n  balanceStore stores.Balance\n  transactor transactor.Transactor\n}\n\nfunc (s service) IncreaseBalance(ctx context.Context, account string, amount int) error {\n  return s.transactor.WithinTransaction(ctx, func(ctx context.Context) error {\n    balance, err := s.balanceStore.GetBalance(ctx, account)\n    if err != nil {\n      return err\n    }\n\n    balance += amount\n\n    err = s.balanceStore.SetBalance(ctx, account, balance)\n    if err != nil {\n      return err\n    }\n\n    return nil\n  })\n}\n```\n\nThanks to nested transactions support, you can even call your services within a transaction:\n\n```go\ntype service struct {\n  balanceStore stores.Balance\n  transactor transactor.Transactor\n}\n\nfunc (s service) TransferBalance(\n  ctx context.Context,\n  fromAccount, toAccount string,\n  amount int,\n) error {\n  return s.transactor.WithinTransaction(ctx, func(ctx context.Context) error {\n    err := s.DecreaseBalance(ctx, fromAccount, amount)\n    if err != nil {\n      return err\n    }\n\n    err = s.IncreaseBalance(ctx, toAccount, amount)\n    if err != nil {\n      return err\n    }\n\n    return nil\n  })\n}\n\nfunc (s service) IncreaseBalance(ctx context.Context, account string, amount int) error {\n  return s.transactor.WithinTransaction(ctx, func(ctx context.Context) error {\n    // ...\n  })\n}\n\nfunc (s service) DecreaseBalance(ctx context.Context, account string, amount int) error {\n  return s.transactor.WithinTransaction(ctx, func(ctx context.Context) error {\n    // ...\n  })\n}\n```\n\n\u003e [!WARNING]\n\u003e Transactions are not thread safe, so make sure not to call code making concurrent database access inside `WithinTransaction`\n\n### Testing\n\nIn your tests, you can inject a fake `transactor` and `dbGetter`, using [NewFakeTransactor](./stdlib/fake_transactor.go):\n\n```go\ntransactor, dbGetter := stdlibTransactor.NewFakeTransactor(db)\n```\n\nThe fake `transactor` will simply execute its callback function, and the `dbGetter` will return the provided `db` handler.\n\nThis strategy works because when using this library, you don't have to worry about how transactions are made, just about returning errors appropriately in `WithinTransaction`.\n","funding_links":["https://buymeacoffee.com/thiht","https://www.blockchain.com/explorer/addresses/btc/bc1qawxn5u2rqupvp043rwamn67d383ggd2tc2tfr3"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthiht%2Ftransactor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthiht%2Ftransactor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthiht%2Ftransactor/lists"}