본문 바로가기
개발 지식/Spring Framework

[Spring] SpringBoot + WebFlux + Kotlin + ReactiveMongo 프로젝트 생성

by 에르주 2022. 5. 1.
반응형

실무 개발 및 서비스를 제공함에 있어 서버에 많은 요청이 있을 경우 Tread Pool 과 Timeout 등 Spring Web의 설정을 각 상황마다 수정하곤 하였다.

이에 Spring 5부터 등장한 Reactvie 스타일 모듈인 Webflux에 대해 관심이 생기기 시작하였고 간단한 Sample Case 맛보기 정도로 진행해보려한다. 

앞으로 이 토이 프로젝트에 관심 있는 여러가지 모듈 및 프로토콜 들을 더해 갈 예정이다. 

 

New project

Project SDK로는 amazon에서 제공하는 corretto-11을 사용하였다. corretto는 OpenJDK로 Java SE 표준과 호환된다.

또한 Dependencies는 다음과 같이 설정하였다.

Reactive 등등

 

Webflux를 MVC 모듈로 비교하자면

 

MVC : Controller - Sevice - Repository

WebFlux : Router - Handler - Service - Repository 

 

로 이루어져 있다. 

 

1. Router

Router는 MVC 패턴에서 Controller 클래스 및 메소드에 @GetMapping 또는 @PostMapping과 같은 API endPoint를 지정한다.

@Configuration
class BaseRouter(private val handler: BaseHandler) {

    @Bean
    fun routerFunction() =
        router {
            "v1".nest {
                listOf( // API EndPoint
                    GET("/test", handler::get),
                    GET("/retrieveAll", handler::getAll),
                    GET("/retrieve/{baseId}", handler::findByBaseId)
                )
            }
        }
}

 

routerFunction을 통해 경로를 지정하며 Bean 등록을 통해 모든 구성 클래스 내에서 생성 및 주입 할 수 있다.

 

또한 Get메소드를 파라미터값으로 URI와 요청 값을 반환하는 Handler 클래스를 명시한다.

 

2. Handler

@Component
class BaseHandler(private val repo: BaseRepository, val baseService: BaseService)  {

//    @Autowired
//    private lateinit var baseService: BaseService;


    // Router Handler 연결 테스트
    fun get(req: ServerRequest): Mono<ServerResponse> = ok()
        .contentType(MediaType.APPLICATION_JSON)
        .body<String>(Mono.just("테스트 중입니다.."))


    // MongoDB 전체 조회 Test
    fun getAll(req: ServerRequest): Mono<ServerResponse> {
        return ok().contentType(MediaType.APPLICATION_JSON)
            .body<BaseDocument>(baseService.getAll())
            .switchIfEmpty(ServerResponse.notFound().build())
    }

    // BaseId값에 따른 조회 Test
    fun findByBaseId(req: ServerRequest): Mono<ServerResponse> {
        return ok().contentType(MediaType.APPLICATION_JSON)
            .body<BaseDocument>(baseService.getDataByBaseId(req.pathVariable("baseId")))
            .switchIfEmpty(ServerResponse.notFound().build())
    }

}

 

handler 클래스는 ServerRequest에 따라 비지니스 로직 즉 Service Class 값 실행 및 ResposeEntity값을 생성해주는 역할을 한다.

 

클래스 선언시 파라미터값으로 Repository와 Service 값을 받고 있는데 이는 Java 클래스 다음과 같다.

public class BaseHandler() {

    @Autowired
    private BaseRepoisotry repo;

    @Autowired
    private Baservie baseSevice;

}

 

ok() 메소드는 ServerReponse.bodyBuilder를 반환하는 메소드로 Resonse Code값을 200 그리고 body값을 통해 데이터를 반환한다.

또한 <BaseDocument> 는 Java의 강제 형변환과 같은 기능이다.

 

 

3. Service

@Service
class BaseService(private val baseRepository: BaseRepository) {

    fun getAll(): Flux<BaseDocument> {
        // TODO Logic
        return baseRepository.findAll()
    }

    fun getDataByBaseId(baseId: String): Flux<BaseDocument> {
        // TODO Logic
        return baseRepository.findByBaseId(baseId)
    }
}

 

4.  Document (Entity)

Document는 MongoDB 컬렉션 안의 데이터 매핑값이며 생성자를 생성하거나 클래스 내부 변수로 선언할 수 있다.

단,  주의점은 다음과 같다.

 

생성자 생성

1) 생성자 내부에서 변수 선언된 값이 MongoDB Collection안에 데이터가 없을 경우 Mapping이 되지 않는다.
org.springframework.data.mapping.model.MappingInstantiationException:

 : 해당 변수값을 초기화하면 정상 조회 로직 진행

 

클래스 내부 변수

1) Id값은 val 선언을 하지 않는다. (id값은 MongoDB 내의 로직에 의해 수행되기 때문)

2) private 선언시에는 Mapping이 되지 않는다.
(삽질의 원흉..)

3) Kotlin의 val 선언은 java에서 final로 정의하는 것과 같기 때문에 Mapping이 되지 않는다.

No accessor to set property private final java.lang.String com.er.kotlintoy.document.BaseDocument.baseId!"

 

// 생성자 X val 선언시 select 불가
// 생성자 val 선언시 select 가능

@Document(value = "testCollection")
class BaseDocument(

    @Id
    val id: Any?,
    val baseId: String,
    val baseName: String,
    var baseNumber: String = "sd",
    var baseAge: String

)

// 위 아래 선언은 동일하다.

@Document(value ="testCollection") {

    @Id
    var id: Any?
    var baseId: String
    var baseName: String
    var baseNumber: String = "sd"
    var baseAge: String

}

 

 

5. Repository

@Repository
interface BaseRepository : ReactiveMongoRepository<BaseDocument, String> {
    fun findByBaseId(baseId: String): Flux<BaseDocument>
}

Repository는 JPA 활용처럼 interface 생성만 하면 요청시 자동으로 Query를 날려준다.

 

끝.

 

 

 

 

 

 

반응형

댓글