So in my domain layer, I have interfaces for a
BarRepo, and a repo for assigning a Bar to a Foo. All have an
Then I have a layer/package with concrete implementations of those repo’s.
Then in the next layer, I have a
FooBarService that requires an implementation of those repo’s (dependency injection), and will create a Foo, a Bar, and join them together. This all needs to happen in a transaction. Additionally, I feel this service layer shouldn’t know anything about the repo implementations, such as if they are using the same
sql.DB or not. With this in mind,
Transaction in the service and passing it to the repo makes no sense. So I think the repo should be responsible for it’s own transaction, and should just return a generic transaction object with
Rollback methods. I believe this is basically the Saga pattern in the command/orchestration style.
So to confirm, my service will call
Foo.Add, receive a transaction back.
Bar.Add, get a transaction, etc. Then, if all goes well, it
Commit‘s each transaction in order.
But, these repos ARE using the same sql.DB object. I believe this means each repo can’t do a
db.Begin() call, because I presume that will return a different
Transaction per repo, not 1 Transaction to cover all the repos? This won’t work for sqlite, as a transaction locks the database, so it can only have 1.
So I’ve considered wrapping the
sql.DB object, and having a
GetTransaction() method that either starts a new transaction (when in
Foo.Add), or reuses the active transaction (when in eg
Bar.Add). In the latter case, the transaction would have to be wrapped, and the
Rollback methods would have to be replaced with empty funcs, because you can’t “finish” the same transaction more than once. This would all be invisible to the service though, as it just commits or rolls-back the transaction received from each repo. So in that regard, it’s a plausible solution (if it works).
In the case of Sqlite, I wonder if I can use a mutex (I have not tried yer nor used before)? If
GetTransaction doesn’t find an active transaction, it would do a
Begin a transaction. When the transaction is first created, it too would have to be wrapped so that the
Rollback funcs unlock the mutex (not sure if this would work, delaying the unlock so long?)
But what about Postgresql, which allows concurrent transactions?! The mutex solution would artificially limit it. I doubt my wrapped
sql.DB can store a map of Transactions keyed by goroutine? That doesn’t sound like a thing. So, do I need to pass a context to my repo, and each repo in turn gives the context to the wrapped
sql.DB, which in turn handles a context scoped Transaction object?
Is this the answer, for also Sqlite?
I think it might be, but just wanted to get some opinions, because I haven’t seen any blogs or anything doing this. But maybe they are too “simplistic” in their demo implementation. Or is there an alternative?