Summary
When using transactions, you usually fetch data from the database, run some logic, update the data, and commit the transaction. How do we model this in the code so we don’t end up with a mess across the layers?
If you’re looking for a good way to spend your money, this is a good place to start. It’s a good idea to start by making sure you�’ve got all the points you need.
In the example snippets, I keep the “layers” in different files but in the same package for simplicity. This lets us correctly process parallel requests. In the application logic, I define the UsePointsAsDiscount command and a handler for it (see Robert’s CQRS article for more details on this).
The repository is injected inside the command handler using an interface defined close to the handler’s definition. The key is that this part of the code knows nothing about the database used.
There’s a Docker Compose definition that lets you run all of them locally. Let’S see how to approach this. All examples are available in the go-web-app-antipatterns repository.
Handling the transaction with the commit/rollback sequence complicates the flow. Instead of working with plain application logic, you now have to consider this additional behavior. A rollback will affect everything you put inside the function.
Don’t create a repository for each table. Instead, think of the data that needs to be transactionally consistent. We can consider them part of the same aggregate.
The application logic becomes trivial now. And this lets us move the transaction handling there. But ideally, we’d have the logic in the command handler.
A universal Update method should load the user from the database, allow us to make any changes, and store the result. The only way to change the state is by calling exported methods.
Use an Update method that loads and stores the aggregate. Keep the logic in the updateFn closure. All of it happens within a transaction. Using an ORM can help you get rid of the boilerplate.
The transaction provider is a risky pattern. If you use it together with FOR UPDATE, you need to be extra careful. Stick to the UpdateFn pattern instead.