Replies: 4 comments 5 replies
-
For Post-process function, more cons:
|
Beta Was this translation helpful? Give feedback.
4 replies
-
+1 for the post/pre transaction function approach (Have used it with good results in many places) |
Beta Was this translation helpful? Give feedback.
0 replies
-
The other approach is 2PC, if the traffic is huge enough that we cannot do external call within txn block |
Beta Was this translation helpful? Give feedback.
0 replies
-
We can also think about outbox pattern |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Golang
database/sql
package has an easy-to-use interface to implement transcation. There are also a lot of packages that extenddatabase/sql
functionalities e.g. sqlx, gorm, goqu, pgx, etc. This discussion won't focus to any of that and should besql-extension-agnostic
enough to discuss. We can assume to use puredatabase/sql
only since all extensions should have compatibility to that.To implement a 'clean architecture', one should breakdown the code into some layers. Each layer should only be allowed to interact to the components one layer below or in the same layer.
We (in odpf) tend to name the layers to this
Transaction is an implementation specific that only exist in repository implementation. But not all storage system have or support transaction. Service layer, where the business logic happen, does not need to know whether the repository does transaction or not.
It is not that complicated to implement transaction in the code in repository layer. Once could make the dependencies between one repository and other and clubbed the each query/repository function into a single atomic task. However, it will become more challenging if an atomic task consists of interaction to DB (writing data to DB) and interaction to the foreign system (calling external API). Foreign system is any system outside the storage system that provide transaction. This writing covers how are the pattern to implement transactional DB implementation when there is a dependency to foreign system. In ODPF, there are some cases where we need this.
To ease our understanding, let's scope our problem to only solve the base scenario with 2 tasks. We need to wrap these 2 tasks into a transaction or these 2 tasks should be considered atomic (either all of them success or all of them are failing)
Let's define a hypothetical scenario. We have 2 domains,
User
andAudit
.Everytime
we create a new user, we need to push the data to external API (could be kafka or some HTTP API).Each domain has its components.
User
Audit
No Transaction
Here is the example code when we don't consider both tasks to be atomic, there is no transaction. Hence if user repository successfully write the data to DB and the external call failed, the data in DB and external audit system is inconsistent.
Here are the patterns to implement transaction
1. Post-process function
Transaction is initiated within repository layer and repository provide a post-process callback function that could execute any function on the layer above (service layer).
Pros
CreateAtomic
to make it clearer.Cons
CreateWithFn
needs a post-process function that will be pass as a function closure argument. If we mock the repository, t is not straight forward to test function closure argument.2. Transactor interface
Pros
Cons
So What?
Beta Was this translation helpful? Give feedback.
All reactions