{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/shards/2026/03/transactions-with-repository-pattern/",
  "description": "Adding transaction support to a repository interface without leaking storage details.",
  "path": "/shards/2026/03/transactions-with-repository-pattern/",
  "publishedAt": "2026-03-20T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Go",
    "Database"
  ],
  "textContent": "[Previously], I showed how to put a small interface between your service logic and your\nstorage layer so the service doesn't know whether it's talking to sqlc, raw SQL, or anything\nelse. The interface looked like this:\n\nA service depends on Store, a concrete postgres package satisfies it, and in tests you\nswap in an in-memory fake. The service never imports database/sql.\n\nIn the [same Reddit thread], user xinoiP [asked]:\n\n> How would you handle transactions with this approach? Since they are very specific to SQL.\n> I tend to use context and store an optional transaction in there that can be used on the\n> implementation of that interface. So, sqlc checks the context, if there is a transaction,\n> uses it etc. I just wonder how you would handle it.\n\nIf each method on the interface runs independently, there's no way to make two writes\natomic. Say RegisterBook needs to insert a book _and_ an audit log entry, and both must\ncommit or roll back together.\n\n---\n\nThe key is something sqlc already gives you. It generates a DBTX interface that both\nsql.DB and sql.Tx satisfy:\n\nIf your store struct accepts DBTX instead of sql.DB, you can construct a store backed\nby either a connection pool or a live transaction. Same struct, same methods, different\nunderlying executor. That means the interface can offer a Tx method that hands the caller\na transactional version of itself:\n\nThe Postgres implementation of Tx starts a sql.Tx, wraps it in a fresh BookStore, and\npasses that into the callback. If the callback returns an error, it rolls back. Otherwise it\ncommits:\n\nNewBookStore(tx) works because the struct field is DBTX, and sql.Tx satisfies that\ninterface. No new type, no wrapper. The Get, Create, and CreateAuditLog methods on\nthis transactional store run their queries against the tx automatically.\n\nThe service uses Tx when it needs atomicity. Everything inside the callback goes through\nthe transactional store:\n\nBoth writes commit or roll back together. RegisterBook never sees sql.Tx, sql.DB, or\nanything from database/sql. If the audit log insert fails, the book insert is rolled back\ntoo.\n\nFor tests, Tx just calls the function directly against the in-memory store:\n\nNo real transaction needed. The test exercises the same service code as production. If you\nneed to verify actual commit/rollback behavior, swap the in-memory store for something like\nSQLite.\n\n---\n\nBack to xinoiP's approach of storing a *sql.Tx in the context: it works, but it leaks\nstorage into the service layer through the back door. The service has to set up the\ntransaction in context before calling the store, which means it knows a SQL transaction\nexists. That's the coupling the interface was supposed to prevent.\n\nWith the callback approach, the service says \"run these operations atomically\" and the store\ndecides how. Swap Postgres for DynamoDB tomorrow and the service code doesn't change - you\njust implement Tx differently in the new storage package.\n\nThe full working example with an HTTP server and SQLite is [on GitHub].\n\nSee also:\n\n- [Do you need a repository layer on top of sqlc?]\n- [Repositories, transactions, and unit of work in Go]\n\n\n\n\n[Previously]:\n    /shards/2026/03/repository-layer-over-sqlc/\n\n[same Reddit thread]:\n    https://www.reddit.com/r/golang/comments/1rv65k9/\n\n[asked]:\n    https://www.reddit.com/r/golang/comments/1rv65k9/comment/obdrohe/\n\n[on GitHub]:\n    https://github.com/rednafi/examples/tree/main/repository-transactions\n\n[Do you need a repository layer on top of sqlc?]:\n    /shards/2026/03/repository-layer-over-sqlc/\n\n[Repositories, transactions, and unit of work in Go]:\n    /go/repo-txn-uow/",
  "title": "How do you handle transactions with the repository pattern?"
}