1

I have the following models

type UsersModel struct {
    db           *pgx.Conn
}

func (u *UsersModel) SignupUser(ctx context.Context, payload SignupRequest) (SignupQueryResult, error) {
    err := u.db.Exec("...")
    return SignupQueryResult{}, err
}
type SessionsModel struct {
    db           *pgx.Conn
}
 
func (s *SessionsModel) CreateSession(ctx context.Context, payload CreateSessionRequest) error {
    _, err := s.db.Exec("...")
    return err
}

and my service calls UsersModel.SignupUser as follows

type SignupService struct {
    userModel signupServiceUserModel
}

func (ss *SignupService) Signup(ctx context.Context, request SignupRequest) (SignupQueryResult, error) {
    return ss.userModel.SignupUser(ctx, request)
}

Now, I need to tie SignupUser and CreateSession in a transaction instead of isolated operations, not sure what the best way to structure this is, and how to pass transaction around while maintaining that abstraction of DB specific stuff from services. Or should I just call the sessions table insert query(which I'm putting in *SessionsModel.CreateSession directly in *UsersModel.SignupUser?

For reference, transactions in pgx happen by calling *pgx.Conn.Begin() which returns a concrete pgx.Tx , on which you execute the same functions as you would on *px.Conn , followed by *pgx.Tx.Commit() or *pgx.Tx.Rollback()

Questions I have are:

  • Where to start transaction - model or service?
  • If in service, how do I do that while abstracting that there's an underlying DB from service?
  • How do I pass transaction between models?
Ayush Gupta
  • 8,716
  • 8
  • 59
  • 92

1 Answers1

0

There is no right or wrong answer for this since there are multiple ways to do it. However, I share how I'd do it and why.

make sure to keep the service layer clean of any concrete DB implementation, so if you switch to a completely new DB you do not need to change other pieces.

about the solution, I would create a completely new method called SignupUserAndCreateSession that encloses all the logic you need. I wouldn't worry because you have the two original methods in one, as per my understanding in this scenario both of them are tightly coupled by design, so this would not be an anti-pattern.

I would avoid moving around the *pgx.Tx between methods since anyway you would depend on another level that makes sure to commit or roll back, and this might cause errors in future implementations.

Dharman
  • 30,962
  • 25
  • 85
  • 135
cperez08
  • 709
  • 4
  • 9