Original post

So in my domain layer, I have interfaces for a FooRepo, a BarRepo, and a repo for assigning a Bar to a Foo. All have an Add() method.

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, Beginning a 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 Commit and 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 Connection and 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 Commit and 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 mutex.Lock, and Begin a transaction. When the transaction is first created, it too would have to be wrapped so that the Commit and 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?

submitted by /u/neRok00
[link] [comments]