카테고리 없음

Spring Boot 3.x Embedded MongoDB Transaction + Replica set Test

수수한개발자 2024. 3. 31.
728x90

 

 

1. 소개

 

스프링 부트와 자바를 사용하고 있고 데이터 베이스는 몽고 디비를 사용하는 곳에서 테스트하기 위한 방법은 저번에 한 번 글로 다룬 적이 있습니다.

 

https://techjisu.tistory.com/180

 

MongoDB 테스트하기(TestContainer 및 memory DB)

이번에 몽고 디비를 공부하면서 어떻게 테스트할까라는 생각으로 공부하고 글을 작성하게 되었습니다. 기존에는 RDB(MySQL, PostgreSQL)를 사용하면서 hibernate를 사용했으므로 프로덕트 코드는 RDB에

techjisu.tistory.com

 

여기서 테스트 컨테이너를 사용하면 되지만 도커가 없는 환경에서는 사용할 수 없다는 생각에 mongo-java-server 사용하여 테스트 코드를 작성하였는데요.

테스트 컨테이너를 사용하지 않은 이유는 테스트 코드를 실행하기전 도커가 실행되어 있는지, 또 CI 환경에 도커가 없다면 

이 테스트 코드는 실패하게 됩니다.

테스트 자체가 외부에 환경에 따라 성공 / 실패 여부가 달라지기 때문에 안정적인 테스트 환경이라 보기 어렵다고 생각하고,

인메모리 몽고 디비를 사용하게 되었습니다.

 

2. 문제 상황

하지만 회사에서 트랜잭션을 사용하게 되어서 @Transactional 어노테이션을 붙이면서 문제가 발생하게 됩니다.

다음 예제를 통해서 알아보겠습니다.

 

유저를 생성하는 UserService 의 createUser 메서드입니다.

 

@Transactional
public UserDto createUser(UserDto userDto) {
    long id = sequenceGeneratorService.generateSequence(User.SEQUENCE_NAME);
    User savedUser = userRepository.save(userDto.toEntity(id));
    return UserDto.from(savedUser);
}

 

유저를 생성하는 서비스 메서드에 @Transactional 어노테이션을 붙입니다.

이후에 테스트 코드를 실행해봅니다.

 

 

 

위와 같이 유저를 생성한 후에  일어나는 모든 연관되어 있어 있는 테스트가 깨지게 됩니다.

왜 이럴까요?

 

 

https://www.mongodb.com/docs/manual/reference/method/Session.startTransaction/

 

몽고 디비 공식문서를 보니 하나의 세션당 하나의 트랜잭션을 가질 수 있다고 합니다.

 

https://github.com/bwaldvogel/mongo-java-server?tab=readme-ov-file#transactionshttps://github.com/bwaldvogel/mongo-java-server?tab=readme-ov-file

 

몽고 자바 서버에서 트랜잭션 어노테이션 붙은 메서드에서 몽고 디비 세션을 열려고 하니 지원이 안된다고 합니다.

 

 

그래서 해당 mongo-java-server 깃허브에 들어가 보니 트랜잭션에 대해서 지원하지 않으니 테스트 컨테이너를 사용하거나 임베디드 몽고 디비를 사용하라고 안내해 줍니다...

 

그럼 위에서 말한 대로 테스트 컨테이너는 사용 안 하기로 했으니 Embedded MongoDB를 사용해 보도록 하겠습니다

 

https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo

 

해당 깃허브 레포에 들어가면 친절하게 maven 디펜던시부터 사용 방법까지 친절하게 안내해 줍니다.

천천히 따라가 보겠습니다.

 

 

해당 의존 성을 build.gradle에 추가해 줍니다.

이러고 테스트를 돌리면 27017 포트로 몽고 디비가 실행되어 자동으로 연결되는 방식인 것 같다.

 

테스트를 실행했지만 연결하지 못한다는 로그가 계속 찍히고 있었다.

근데 위와 관련해서는 구글링을 해도 잘 나오지 않는데 다음 저장소를 발견하게 되었다.

 

https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo.spring

 

GitHub - flapdoodle-oss/de.flapdoodle.embed.mongo.spring: embedded mongo spring integration

embedded mongo spring integration. Contribute to flapdoodle-oss/de.flapdoodle.embed.mongo.spring development by creating an account on GitHub.

github.com

 

같은 Flapdoodle OSS 조직 밑에 스프링만을 위한 저장소가 따로 만들어져 있었다.

 

또 스프링 3.x.x 버전을 위한 새로운 버전도 생긴 것 같았다.

 

<dependency>
	<groupId>de.flapdoodle.embed</groupId>
	<artifactId>de.flapdoodle.embed.mongo.spring3x</artifactId>
	<version>4.12.2</version>
</dependency>


testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring3x:4.12.2'

 

기존의 것에서 spring3x를 붙이면 된다.

위의 의존성으로 바꾸고 테스트 실행..

 

 

 

또 안된다...

 

 

 

여기서 그냥 포기할까 하다가 진짜 위의 레포지토리를 처음부터 다 살펴봤다...

 

https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo.spring/issues/2

 

Spring Boot 3.0 support? · Issue #2 · flapdoodle-oss/de.flapdoodle.embed.mongo.spring

It looks like the binaries built from the 2.7.x branch do not work on Boot 3.0 snapshots as they refer to API removed. Will there be an updated version compatible with 3.0 at some point?

github.com

 

해당 이슈는 제목부터 날 설레게 만든다.. 

 

근데 살펴보니 나랑 다른 부분은 크게 없고 두 곳의 코드 변경이 일어났다고 나온다.

 

 

나는 build.gradle 파일은 바꿨으니 yml 파일에 저 설정만 넣으면 되나? 하고 일단 가져왔다.

 

해당 레포지토리에 나 같은 사람을 위한 떡하니 예제 코드를 제공하고 있었다.

 

https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo.spring?tab=readme-ov-file#canary-project

 

위의 저 설정 값을 넣어주면 된다고 합니다.

해당 예제에는 4.4.0이 적혀 있으니 작성해 줍니다.

 

de:
  flapdoodle:
    mongodb:
      embedded:
        version: 4.4.0

 

 

또 몽고 디비의 트랜잭션은 replica set에서만 지원되므로 test 디렉터리에 다음 코드로 레플리카셋 환경을 만들어 줄 수 있도록 작성해주어야 합니다.

 

@TestConfiguration
public class TransactionalConfig {

  @Bean
  MongoTransactionManager mongoTransactionManager(MongoDatabaseFactory dbFactory) {
    return new MongoTransactionManager(dbFactory);
  }

  @Bean
  MongodArguments mongodArguments() {
    return MongodArguments.builder()
      .replication(Storage.of("test", 10))
      .build();
  }
}

 

 

@TestConfiguration으로 한 이유는 테스트 상황에서만 사용되는 빈이므로 사용하게 되었습니다.

또한 프로덕션 코드에 MongoTransactionManager가 있으면 해당 빈을 오버라이드하여 테스트 환경 내에서는 위의 빈을 사용하도록 해야 됩니다.

 

만약 그냥 위의 빈을 만들어주면 BeanDefinitionOverrideException이 발생하게 됩니다.

스프링 부트 2.1부터 빈을 오버라이드하여 발생할 수 있는 문제를 방지하기 위해 방어적인 목적으로 기본 값이 false입니다.

 

그래서 다음과 같이 spring.main.allow-bean-definition-overriding=true를 추가해서 테스트 환경에서 트랜잭션 매니저를 오버라이딩 하게 합니다.

de:
  flapdoodle:
    mongodb:
      embedded:
        version: 4.4.0
spring.main.allow-bean-definition-overriding=true

 

 

 

 

 

3. 결론

이제 스프링 부트 3 이상 버전에서  임베디드 몽고디비를 레플리카 셋으로 구성해 트랜잭션 코드가 통과할 수 있는 환경을 만들었습니다.

 

위의 전체 소스 코드는 GitHub에서 찾을 수 있습니다.

728x90

댓글