스프링 부트를 활용한 데이터 액세스
리액티브 데이터 스토어의 요건
이커머스 애플리케이션 도메인 객체 정의
객체를 저장하고 조회할 리포지토리 생성
상기 내용 서비스에 적용
리액티브 데이터 스토어 요건
- 리액티브 프로그래밍을 사용하려면 모든 과정이 리액티브여야 함
- 웹 컨트롤러, 서비스 계층도 리액티브 방식으로 동작하게 만들었는데 블로킹 방식으로 연결되는 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:
이것의 스트림을 다른 크기로 된저것의 스트림으로 바꾸는 함수형 도구
- map:
- 코드 생략(소스 코드 링크 참고)
서비스 추출
- 코드 생략(소스 코드 링크 참고)
데이터베이스 쿼리
- 스프링 데이터에서 정한 메소드 이름 규칙을 사용해서 리포지토리 메소드를 정의하면
일반적으로 필요한 쿼리문의 80% 정도를 직접 코드나 SQL문을 작성하지 않고 자동으로 만들어 낼 수 있음 - 코드 생략(소스 코드 링크 참고)
쿼리문 자동 생성 메소드로 충분하지 않을 때
@Query애너테이션 사용- 리포지토리 메소드 이름 규칙에 의해 자동으로 생성되는 쿼리문 대신
@Query내용으로 개발자가 직접 명시한 쿼리문을 사용
- 리포지토리 메소드 이름 규칙에 의해 자동으로 생성되는 쿼리문 대신
- 코드 생략(소스 코드 링크 참고)
Example 쿼리(Query by example)
- 여러 조건을 조립해서 스프링 데이터에 전달하면, 스프링 데이터가 필요한 쿼리문을 만들어줌
ReactiveQueryByExampleExecutor를 상속 받도록 추가
평문형 연산
- 평문형 연산: 몽고디비 쿼리를 보통 문장 같은 형식으로 사용할 수 있는 연산
- 스프링 데이터 몽고디비에서는
FluentMongoOperations의 리액티브 버전인ReactiveFluentMongoOperations를 통해 평문형 연산 기능 사용 가능
트레이드 오프
- 프로젝트 상황에 맞게 데이터 스토어 독립성과 데이터 스토어 최적성 사이에서 올바른 선택을 하는 것이 중요
쿼리 방법별 장단점
표준 CRUD 메소드
- 장점
- 미리 저으이돼 있음
- 소스 코드로 작성돼 있음
- 리액터 타입을 포함해서 다양한 반환 타입 지원
- 데이터 스토어 간 호환성
- 단점
- 1개 또는 전부에만 사용 가능
- 도메인 객체별로 별도의 인터페이스 작성 필요
메소드 이름 기반 쿼리
- 장점
- 직관적
- 쿼리 자동 생성
- 리액터 타입을 포함한 다양한 반환 타입 지원
- 여러 데이터 스토어에서 모두 지원
- 단점
- 도메인 객체마다 리포지토리 작성 필요
- 여러 필드와 조건이 포함된 복잡한 쿼리에 사용하면 메소드 이름이 매우 길어지고 불편
Example 쿼리
- 장점
- 쿼리 자동 생성
- 모든 쿼리 조건을 미리 알 수 없을 때 유용
- JPA, 레디스에서도 사용 가능
- 단점
- 도메인 객체마다 리포지토리 작성 필요
MongoOperations
- 장점
- 데이터 스토어에 특화된 기능까지 모두 사용 가능
- 도메인 객체마다 별도의 인터페이스 작성 불필요
- 단점
- 데이터 스토어에 종속적
@Query 사용 쿼리
- 장점
- 몽고QL 사용 가능
- 긴 메소드 이름 불필요
- 모든 데이터 스토어에서 사용 가능
- 단점
- 데이터 스토어에 종속적
평문형 API
- 장점
- 직관적
- 도메인 객체마다 별도의 인터페이스 작성 불필요
- 단점
- 데이터 스토어에 종속적
- JPA와 레디스에서도 사용할 수 있지만 호환은 안됨
참고
- "스프링 부트 실전 활용 마스터", 그렉 턴키스트 저(원서: "Hacking with Spring Boot 2.3 - Reactive Edition")
- 소스 코드
- https://docs.spring.io/spring-data/data-mongodb/docs/3.4.0/reference/html/#appendix.query.method.subject
'dev > Spring' 카테고리의 다른 글
| [정리] 스프링 부트 실전 활용 마스터(6) (0) | 2022.06.30 |
|---|---|
| [정리] 스프링 부트 실전 활용 마스터(5) (0) | 2022.06.23 |
| [정리] 스프링 부트 실전 활용 마스터(4) (0) | 2022.06.16 |
| [정리] 스프링 부트 실전 활용 마스터(3) (0) | 2022.05.26 |
| [정리] 스프링 부트 실전 활용 마스터(1) (0) | 2022.05.12 |