Notepad

스프링 부트를 활용한 데이터 액세스

리액티브 데이터 스토어의 요건
이커머스 애플리케이션 도메인 객체 정의
객체를 저장하고 조회할 리포지토리 생성
상기 내용 서비스에 적용

리액티브 데이터 스토어 요건

  • 리액티브 프로그래밍을 사용하려면 모든 과정이 리액티브여야 함
    • 웹 컨트롤러, 서비스 계층도 리액티브 방식으로 동작하게 만들었는데 블로킹 방식으로 연결되는 DB를 호출하면 리액티브가 무너짐
    • 블로킹 방식으로 DB 호출한 스레드는 응답을 받을 때까지 다른 작업을 하지 못한 채 기다려야함
    • 블로킹 되는 스레드가 많아지면 스레드가 모두 고갈되어서 결국 전체 애플리케이션이 DB로부터 결과를 기다리면서 아무런 일도 할 수 없음 상태가 되어 망가짐
    • 레거시 브롤킹 코드를 따로 감싸고 격리해서 문제를 해결할 수도 있지만 리액티브의 장점을 잃게 됨
  • 리액티브가 태생적으로 빠르다? (오해)
    • 작업을 수행하는 단일 스레드의 처리 속도 기준으로 보면 여러 가지 오버헤드를 수반하므로 성능 저하가 발생함
    • 사용자 수가 적고 데이터도 많지 않다면 불필요한 오버헤드를 감수하면서 리액티브를 사용하는 것은 낭비
    • 대규모 트래픽이 발생하고 백엔드에서 대용량의 데이터를 처리하는 환경에서는 리액티브 프로그래밍의 장점이 빛을 발휘함
    • 리액티브 프로그래밍에서 스레드는 어떤 작업이 끝날 때까지 블로킹되어 기다리지 않고 다른 작업을 수행할 수 있음
  • 자원 가용성에 반응함
    • 요청과 응답을 조율해서 시스템 자원이 허용하는 한도 내에서 스레드 사용 효율 극대화
  • 데이터베이스도 리액티브하게 동작해야 함
  • 리액티브 패러다임 지원 데이터베이스
    • 몽고디비
    • 레디스(레터스 드라이버 사용 시)
    • 아파치 카산드라
    • 엘라스틱서치
    • Neo4j
    • 카우치베이스
  • 관계형 데이터베이스를 사용하지 않는 이유
    • 블로킹 API(JPA, JDBC)
    • JDBC나 JPA를 감싸서 리액티브 스트림 계층에서 사용할 수 있게 해주는 솔루션도 있지만 반쪽짜리 솔루션
      • 일반적으로 숨겨진 내부 스레드 풀을 사용
      • 코어 수보다 많은 스레드를 사용하는 것은 장점이 거의 없음
        • 4코어 장비에 100개의 스레드라면 CPU 컨텍스트 스위칭 오버헤드가 증가하여 효율이 급격히 떨어짐
        • 4코어 장비라면 4개의 스레드로 구성된 스레드 풀을 사용하는 것이 좋음
      • 블로킹 API 앞에 스레드 풀을 두고 여러 스레드를 사용하는 방식은 일반적으로 포화지점에 도달하게 됨
      • 포화지점에 도달하면 스레드 풀 자체도 블로킹 됨
  • 100% 리액티브 애플리케이션을 만들려면 데이터베이스와 물리적 연결과 상호작용 과정에 비동기, 논블로킹 개념을 적용할 수 있는 데이터베이스 드라이버가 필요
    • 위의 리액티브 패러다임 지원 데이터베이스는 모두 비동기, 논블로킹을 지원하는 데이터베이스 드라이버를 가지고 있음

예제: 이커머스 애플리케이션 도메인 정의

의존성 추가

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
    </dependency>
    <dependency>
        <groupId>de.flapdoodle.embed</groupId>
        <artifactId>de.flapdoodle.embed.mongo</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>mongodb-driver-sync</artifactId>
    </dependency>
  • spring-boot-starter-data-mongodb-reactive
    • 스프링 부트 스타터로 아래의 리액티브 데이터스토어 모듈을 포함
    • spring-boot-starter: 스타터를 연결해서 사용할 수 있게 해주는 스프링 부트 핵심 모듈
    • spring-data-mongodb: 블로킹 방식 몽고디비 드라이버가 제외된 스프링 데이터 몽고디비
    • mongodb-driver-reactivestreams: 몽고디비 공식 리액티브 스트림 드라이버
    • reactor-core: 프로젝트 리액터 코어 모듈
  • de.flapdoodle.embed.mongo
    • 내장형 몽고디비
    • 테스트에 주로 사용하며 애플리케이션 초기 설계 단계에서 데이터 스토어로 사용할 수 있음
  • mongodb-driver-sync
    • 리액티브가 아닌 정통적인 몽고디비 드라이버

도메인 정의

  • 판매 상품(Inventory Item): 일련번호, 가격, 설명 필요
  • 장바구니(Cart): 장바구니 식별자와 장바구니에 담긴 상품 목록 필요
  • 구매 상품(Item in a Cart): 장바구니에 담긴 판매 상품의 구매 수량 필요
  • 코드 생략(소스 코드 링크 참고)

리포지토리 만들기

  • 스프링 데이터 사용 장점
    • 템플릿이라는 이름이 붙은 다양한 도구 제공
    • 데이터 스토어별 맞춤형 템플릿으로 해당 데이터베이스의 풍부한 기능을 모두 활용 가능
      • MongoTemplate, ReactiveMongoTemplate
    • 저장, 조회, 삭제 같은 단순하고 공통적인 연산을 추상화해서 표준하여 리포지토리로 제공
  • 코드 생략(소스 코드 링크 참고)

테스트 데이터 로딩

  • MongoOperations
    • MongoDB 작업의 Basic Set을 지정하는 인터페이스
    • 자주 사용되지 않지만 확장성과 테스트성을 위한 유용한 옵션
    • MongoTemplate이 이 인터페이스의 구현체
  • 코드 생략(소스 코드 링크 참고)

장바구니 보여주기

  • hidden으로 지정된 웹 메소드 처리 활성화
    • spring.webflux.hiddenmethod.filter.enable=true
  • 코드 생략(소스 코드 링크 참고)

장바구니에 상품 담기

  • map과 flatMap 비교
    • map: 이것저것으로 바꾸는 함수형 도구
    • flatMap: 이것의 스트림을 다른 크기로 된 저것의 스트림으로 바꾸는 함수형 도구
  • 코드 생략(소스 코드 링크 참고)

서비스 추출

  • 코드 생략(소스 코드 링크 참고)

데이터베이스 쿼리

  • 스프링 데이터에서 정한 메소드 이름 규칙을 사용해서 리포지토리 메소드를 정의하면
    일반적으로 필요한 쿼리문의 80% 정도를 직접 코드나 SQL문을 작성하지 않고 자동으로 만들어 낼 수 있음
  • 코드 생략(소스 코드 링크 참고)

쿼리문 자동 생성 메소드로 충분하지 않을 때

  • @Query 애너테이션 사용
    • 리포지토리 메소드 이름 규칙에 의해 자동으로 생성되는 쿼리문 대신 @Query 내용으로 개발자가 직접 명시한 쿼리문을 사용
  • 코드 생략(소스 코드 링크 참고)

Example 쿼리(Query by example)

  • 여러 조건을 조립해서 스프링 데이터에 전달하면, 스프링 데이터가 필요한 쿼리문을 만들어줌
  • ReactiveQueryByExampleExecutor를 상속 받도록 추가

평문형 연산

  • 평문형 연산: 몽고디비 쿼리를 보통 문장 같은 형식으로 사용할 수 있는 연산
  • 스프링 데이터 몽고디비에서는 FluentMongoOperations의 리액티브 버전인 ReactiveFluentMongoOperations를 통해 평문형 연산 기능 사용 가능

트레이드 오프

  • 프로젝트 상황에 맞게 데이터 스토어 독립성과 데이터 스토어 최적성 사이에서 올바른 선택을 하는 것이 중요

쿼리 방법별 장단점

표준 CRUD 메소드

  • 장점
    • 미리 저으이돼 있음
    • 소스 코드로 작성돼 있음
    • 리액터 타입을 포함해서 다양한 반환 타입 지원
    • 데이터 스토어 간 호환성
  • 단점
    • 1개 또는 전부에만 사용 가능
    • 도메인 객체별로 별도의 인터페이스 작성 필요

메소드 이름 기반 쿼리

  • 장점
    • 직관적
    • 쿼리 자동 생성
    • 리액터 타입을 포함한 다양한 반환 타입 지원
    • 여러 데이터 스토어에서 모두 지원
  • 단점
    • 도메인 객체마다 리포지토리 작성 필요
    • 여러 필드와 조건이 포함된 복잡한 쿼리에 사용하면 메소드 이름이 매우 길어지고 불편

Example 쿼리

  • 장점
    • 쿼리 자동 생성
    • 모든 쿼리 조건을 미리 알 수 없을 때 유용
    • JPA, 레디스에서도 사용 가능
  • 단점
    • 도메인 객체마다 리포지토리 작성 필요

MongoOperations

  • 장점
    • 데이터 스토어에 특화된 기능까지 모두 사용 가능
    • 도메인 객체마다 별도의 인터페이스 작성 불필요
  • 단점
    • 데이터 스토어에 종속적

@Query 사용 쿼리

  • 장점
    • 몽고QL 사용 가능
    • 긴 메소드 이름 불필요
    • 모든 데이터 스토어에서 사용 가능
  • 단점
    • 데이터 스토어에 종속적

평문형 API

  • 장점
    • 직관적
    • 도메인 객체마다 별도의 인터페이스 작성 불필요
  • 단점
    • 데이터 스토어에 종속적
    • JPA와 레디스에서도 사용할 수 있지만 호환은 안됨

참고

profile

Notepad

@Apio

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!