WebFlux, Coroutine환경에서 MongoDB Transaction 적용하기

TaeHye0n·2024년 3월 31일
1

pmeet

목록 보기
2/5
post-thumbnail
post-custom-banner

사이드 프로젝트 진행 중 트랜잭션이 제대로 안되서 해결한 경험 공유


  @Transactional
  suspend fun save(requestDto: SignUpRequestDto): UserResponseDto {
    val user = User(
      email = requestDto.email,
      name = requestDto.name,
      password = passwordEncoder.encode(requestDto.password),
      nickname = requestDto.nickname,
    )
    val save = userService.save(user)
    throw IllegalArgumentException("Test Ex")
    return UserResponseDto.from(save)
  }

위의 코드 처럼 예외 발생시 트랜잭션 롤백 테스트를 해보았는데, 데이터가 롤백이 되지 않는 것을 확인할 수 있었다.

그러면 어떻게 해야할까??

먼저, TransactionManager와 TransactionOperator를 빈으로 등록하자.

@Configuration
@EnableReactiveMongoRepositories(basePackages = ["xxx.xxxx"])
class MongoConfig {

  @Bean
  fun transactionManager(reactiveMongoDatabaseFactory: ReactiveMongoDatabaseFactory): ReactiveMongoTransactionManager {
    return ReactiveMongoTransactionManager(reactiveMongoDatabaseFactory)
  }

  @Bean
  fun transactionalOperator(reactiveTransactionManager: ReactiveMongoTransactionManager): TransactionalOperator {
    return TransactionalOperator.create(reactiveTransactionManager)
  }

}

그러면 제대로 되는가?? 아니다. 어플리케이션을 실행하면 예외가 발생한다.
MongoDB 트랜잭션 관리를 하기위해서는 Stand-alone이 아닌 Replica Set을 구성해야 한다.

1. Replica Set 내부 인증을 위한 키 생성

openssl rand -base64 756 > mongodb.key
chmod 400 mongodb.key
sudo chown 999:999 mongodb.key // ec2 환경에서 안할 경우 문제가 생길 수 있음

2. docker-compose.yml 작성

version: "3.8"
services:
  mongo1:
    image: mongo
    hostname: mongo1
    container_name: mongo1
    ports:
      - "27017:27017"
    volumes:
      - data-mongo1:/data/db
      - ./mongodb.key:/etc/mongodb.key
    environment:
      - MONGO_INITDB_ROOT_USERNAME=pmeet_user
      - MONGO_INITDB_ROOT_PASSWORD=pmeet_pwd
      - MONGO_INITDB_DATABASE=pmeet
    command: 'mongod --replSet myReplicaSet --keyFile /etc/mongodb.key --bind_ip_all'

  mongo2:
    image: mongo
    hostname: mongo2
    container_name: mongo2
    depends_on:
      - mongo1
    ports:
      - "27018:27017"
    volumes:
      - data-mongo2:/data/db
      - ./mongodb.key:/etc/mongodb.key
    environment:
      - MONGO_INITDB_ROOT_USERNAME=pmeet_user
      - MONGO_INITDB_ROOT_PASSWORD=pmeet_pwd
      - MONGO_INITDB_DATABASE=pmeet
    command: 'mongod --replSet myReplicaSet --keyFile /etc/mongodb.key --bind_ip_all'

  mongo3:
    image: mongo
    hostname: mongo
    container_name: mongo3
    depends_on:
      - mongo2
    ports:
      - "27019:27017"
    volumes:
      - data-mongo3:/data/db
      - ./mongodb.key:/etc/mongodb.key
    environment:
      - MONGO_INITDB_ROOT_USERNAME=pmeet_user
      - MONGO_INITDB_ROOT_PASSWORD=pmeet_pwd
      - MONGO_INITDB_DATABASE=pmeet
    command: 'mongod --replSet myReplicaSet --keyFile /etc/mongodb.key --bind_ip_all'

volumes:
  data: { }
  data-mongo1: { }
  data-mongo2: { }
  data-mongo3: { }

networks:
  default:
    name: mongodb_network

3. 컨테이너 진입하여 연결

docker exec -it mongo1 mongosh -u pmeet_user -p pmeet_pwd --authenticationDatabase admin

위 명령어로 컨테이너에 진입하여 아래의 명령어를 입력하면 된다.

rs.initiate({
_id: "myReplicaSet",
members: [
  { _id: 0, host: "mongo1" },
  { _id: 1, host: "mongo2" },
  { _id: 2, host: "mongo3" }
]
});


위와 같이 ok: 1 이 나오면 성공이다!

이제 롤백 테스트를 해보면 성공적으로 되는 것을 확인 할 수 있다.

[참고]

profile
왜? 에 대해 생각하려고 노력하는 개발자
post-custom-banner

0개의 댓글