Backend

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

2026-01-013 min read

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

Spring 개발자를 위한 핵심 질문 @Transactional 어노테이션 하나로 메서드들을 하나의 트랜잭션으로 묶던 편리함, Go에서도 가능할까요?

@Transactional의 본질

Spring에서의 경험

@Transactional
public void transfer(String from, String to, BigDecimal amount) {
    accountRepository.withdraw(from, amount);  // 같은 TX
    accountRepository.deposit(to, amount);     // 같은 TX
    logRepository.save(new TransferLog(...));  // 같은 TX
}

핵심 편의성:

  • 어노테이션만 붙이면 끝
  • 메서드 내 모든 Repository 호출이 자동으로 같은 트랜잭션
  • 예외 발생 시 자동 롤백
  • 개발자는 비즈니스 로직에만 집중

Go의 현실

Go에는 AOP가 없으므로 @Transactional과 100% 동일한 경험은 불가능합니다. 하지만 비슷한 수준의 편의성을 달성할 수 있습니다.


목표: @Transactional과 유사한 경험

우리가 원하는 것

핵심 아이디어


구현 패턴

사용 코드 (UseCase)

Spring의 @Transactional과 유사하게, 래퍼 함수 한 줄로 트랜잭션 경계를 정의합니다:

func (u *TransferUseCase) Transfer(ctx context.Context, from, to string, amount int64) error {
    // WithTx 한 줄로 트랜잭션 시작 - @Transactional과 유사!
    return database.WithTx(ctx, u.db, func(ctx context.Context) error {
        // 이 안의 모든 Repository 호출은 같은 트랜잭션
        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, &TransferLog{...})
    })
    // 성공 시 자동 커밋, 실패 시 자동 롤백
}

Repository는 트랜잭션을 모름

func (r *AccountRepository) Withdraw(ctx context.Context, id string, amount int64) error {
    // ctx에서 TX가 있으면 사용, 없으면 일반 DB
    db := database.GetDB(ctx, r.db)
    return db.Model(&Account{}).Where("id = ?", id).
        Update("balance", gorm.Expr("balance - ?", amount)).Error
}

Spring vs Go 비교

측면Spring @TransactionalGo WithTx
문법어노테이션래퍼 함수
명시성암묵적명시적
트랜잭션 전파선언적 (REQUIRED 등)Context 전달
롤백 조건예외 타입 기반error 반환
학습 곡선AOP 이해 필요Context 이해 필요

Go의 장점

  • 명시적: 트랜잭션 경계가 코드에 보임
  • 테스트 용이: Mock Context 주입 가능
  • 디버깅 용이: 스택 트레이스 명확

Go의 단점

  • 반복 코드: 매번 WithTx 호출 필요
  • 규율 필요: GetDB 호출 누락 시 별도 TX 사용

Connection Pool 설정

핵심 설정

설정권장이유
MaxOpenConns25-50DB 동시 연결 제한 고려
MaxIdleConns10-25Open의 40-50%
ConnMaxLifetime5분방화벽/LB 타임아웃 고려

Spring 대응

SpringGo
HikariCP maximumPoolSizeMaxOpenConns
minimumIdleMaxIdleConns
maxLifetimeConnMaxLifetime

정리

요소역할
WithTx@Transactional 대응, 트랜잭션 경계
GetDBContext에서 TX 추출
RepositoryTX를 모름, Context만 받음

핵심 메시지: Go에서도 WithTx 래퍼 하나로 Spring의 @Transactional과 유사한 편의성을 얻을 수 있습니다.


다음 편 예고

6편: Resilient한 외부 통신에서는 Resilience4j에 대응하는 Go의 Circuit Breaker, Retry 패턴을 다룹니다.


참고 자료

Share

Related Articles

Comments

이 블로그는 제가 알고 있는 것들을 잊지 않기 위해 기록하는 공간입니다.
직접 작성한 글도 있고, AI의 도움을 받아 정리한 글도 있습니다.
정확하지 않은 내용이 있을 수 있으니 참고용으로 봐주세요.

© 2026 Seogyu Kim