Skip to content
This repository has been archived by the owner on Oct 13, 2021. It is now read-only.

Database transactions #32

Open
adamfdl opened this issue Nov 18, 2019 · 4 comments
Open

Database transactions #32

adamfdl opened this issue Nov 18, 2019 · 4 comments

Comments

@adamfdl
Copy link

adamfdl commented Nov 18, 2019

Where should I put database transactions. Let's say I am building an e-commerce website, and I want to create a feature to order an item with a third-party payment. The simplified flow:

  1. Deduct item stock in database
  2. Charge to Paypal
  3. Create invoice in database

Where should I put the transaction implementation? It should not be in the repository layer no?

@xescugc
Copy link

xescugc commented Nov 18, 2019

Transactions with this model are hard, what I do is create a Unit of Work implementation, in which you pass a list of repositories that have to work together, and basically behind the scene create a TX for all of them, so all the calls, on that context, to the Repositories will be done on a TX.

This allows to have different TX (MySQL, FS) and other implementations of the repositories you have implement it's own way of having TX if any.

type StartUnitOfWork func(ctx context.Context, uowFn func(uow UnitOfWork) error, repositories ...interface{}) error
type UnitOfWork interface { 
  Users user.Repository
}
err := s.startUnitOfWork(ctx, func(uow unitwork.UnitOfWork) error { 
  uow.Users().Create(...)
}, s.users)

This is what I use and it works really good for me, I had to invest a big amount of time to find a good implementation that would fit the patterns ans till have separation without attaching the logic to an specific repository implementation (MySQL for example).

This is just an small example but to give ideas on how I solved it.

EDIT: This is only for same MS TX, for multiple MS TX then you need something totally different and much more hard to do (normally involving queues an potentially manual locks) but I would not invest time on that except if it's mandatory for you to do it but there are ways too https://microservices.io/patterns/data/transactional-outbox.html

@adamfdl
Copy link
Author

adamfdl commented Nov 19, 2019

Hi @xescugc, I haven't thought of that, and that is a really cool implementation. A couple of questions, where is startUnitOfWork()'s function being declared? Is it in the domain? Do you have a sample application that you can share?

@fr3fou
Copy link

fr3fou commented Mar 5, 2020

@marcusolsson what's your way of handling this? I managed to think of a solution but it involes leaking an abstraction of my underlying store

type UserStore interface {
    Save(tx sql.Tx, u *User) error
}

this works but I'm constraining myself to sql stores only :/

@xescugc
Copy link

xescugc commented Mar 6, 2020

@adamfdl Sorry I lost the notification, this will also help @fr3fou.

I have one example of the Unit of Work that I was mentioning. The definition is https://github.com/xescugc/rebost/blob/master/uow/unit_of_work.go and the implementation is https://github.com/xescugc/rebost/blob/master/boltdb/unit_of_work.go for boltdb and https://github.com/xescugc/rebost/blob/master/fs/unit_of_work.go for filesystem. Basically what this allows is the Domain to be abstracted of the implementation behind a Repository, just make a UoW and be able to work.

I have MySQL implementation too but are from work so I can not release the info hehe but with those 2 examples you get the idea.

With this 2 implementation I can have the domain logic deal with the Repositories, and they deal with the implementation for rollbacks and transactions (the fs one is manual but it's the same Idea), you can see them in use in https://github.com/xescugc/rebost/blob/master/volume/volume.go#L348

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants