Backend
Enterprise Go 시리즈 #5: 데이터베이스 연동 패턴
Go에서 트랜잭션 경계를 명확하게 관리하는 패턴을 정리합니다.
Enterprise Go Series(5 / 9)
Enterprise Go 시리즈 #5: 데이터베이스 연동 패턴
문제 정의
Spring의 @Transactional처럼 선언형으로 처리하던 트랜잭션을 Go에서는 명시적으로 다뤄야 합니다.
핵심은 "트랜잭션 경계가 코드에서 보이게" 만드는 것입니다.
권장 패턴: WithTx 래퍼
func (u *TransferUseCase) Transfer(ctx context.Context, from, to string, amount int64) error {
return dbtx.WithTx(ctx, u.db, func(ctx context.Context) error {
if err := u.accountRepo.Withdraw(ctx, from, amount); err != nil {
return err
}
if err := u.accountRepo.Deposit(ctx, to, amount); err != nil {
return err
}
return u.logRepo.Save(ctx, from, to, amount)
})
}
성공 시 커밋, 에러 시 롤백을 한곳에서 보장합니다.
Repository 규칙
Repository는 트랜잭션 생성 책임을 갖지 않습니다.
호출자가 준 ctx에서 트랜잭션 핸들을 얻어 실행합니다.
func (r *Repo) Save(ctx context.Context, m *Model) error {
db := dbtx.FromContextOrDB(ctx, r.db)
return db.Create(m).Error
}
풀 설정 기본값(출발점)
MaxOpenConns: DB 서버 여유와 동시성에 맞춰 제한MaxIdleConns:MaxOpenConns의 30~50%ConnMaxLifetime: LB/방화벽 timeout보다 짧게
정답값은 없고, 지표 기반으로 맞춰야 합니다.
자주 하는 실수
- UseCase 밖에서 트랜잭션 경계를 흩뿌림
- Repository가 내부에서 임의 트랜잭션 시작
- 긴 트랜잭션에서 외부 API 호출까지 포함
요약
Go DB 연동의 핵심은 ORM 선택이 아니라 트랜잭션 경계 통제입니다.
WithTx 패턴으로 경계를 고정하면 롤백 일관성과 테스트 용이성이 함께 올라갑니다.