sayu.day
Backend

Enterprise Go 시리즈 #5: 데이터베이스 연동 패턴

Go에서 트랜잭션 경계를 명확하게 관리하는 패턴을 정리합니다.

발행 2026년 1월 1일1200

문제 정의

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 패턴으로 경계를 고정하면 롤백 일관성과 테스트 용이성이 함께 올라갑니다.

다음 읽기

이 생각이 이어지는 방향

Backend 더 보기
공유

읽은 뒤의 대화

읽은 뒤의 생각을 이어갑니다

질문, 반론, 조용한 후속 메모를 이 글 아래에 남길 수 있습니다.

sayu.day는 생각과 작업의 흔적을 천천히 정리하는 개인 출판물입니다.
직접 겪고 검토한 내용, 다시 읽을 만한 아이디어, 작업하며 남긴 메모를 모읍니다.
시간이 지난 글은 현재의 판단과 다를 수 있어 업데이트 맥락을 함께 남깁니다.

© 2026 sayu.day