Skip to content

Latest commit

ย 

History

History
160 lines (139 loc) ยท 8.62 KB

File metadata and controls

160 lines (139 loc) ยท 8.62 KB

WebFlux

๋ฆฌ์•กํ‹ฐ๋ธŒ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์˜ ํ•œ๋‹จ๊ณ„ ์ง„ํ™”ํ•œ ๋ชจ์Šต์ด๋‹ค. ๋ฆฌ์•กํ‹ฐ๋ธŒ ํŒจ๋Ÿฌ๋‹ค์ž„์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ธฐ์กด์˜ ์ „ํ†ต์ ์ธ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋Šฅ๊ฐ€ํ•˜๋Š” ๋Œ€์‘๋ ฅ, ๋ณต์›์„ฑ, ํƒ„๋ ฅ์ ์ธ ๋ฉ”์‹œ์ง€ ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•œ๋‹ค.

Table of Contents

Netty

๋„คํ‹ฐ๋Š” ์›๋ž˜ ๋„Œ๋ธ”๋กœํ‚น IO ์ž‘์—…์„ ์ˆ˜ํ•ด์•Ÿใ„น ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” Client-Server ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ๋งŒ๋“ค๋ ค๋Š” ์•„์ด๋””์–ด๋ฅผ ๊ฐ€์ง„ JBoss์— ์˜ํ•ด ๊ฐœ๋ฐœ๋˜์—ˆ๋‹ค, ์ด๋Ÿฐ ๊ธฐ๋Šฅ์„ ์œ„ํ•ด Reacotr ํŒจํ„ด์˜ ๋ฉ”์‹œ์ง€ ๊ธฐ๋ฐ˜ ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•œ๋‹ค. ๋„คํ‹ฐ๋Š” HTTPS, SSL/TLS ๋˜๋Š” DNS ๊ฐ™์€ ์ฃผ์š” ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ฐ ํ”„๋กœํ† ์ฝœ์„ ์ง€์›ํ•˜์ง€๋งŒ HTTP/2, Websocket, Google Protocol Butffer ๊ฐ™์€ ์ตœ์‹  ํ”„๋กœํ† ์ฝœ๋„ ์ง€์›ํ•œ๋‹ค.

์Šคํ”„๋ง ๋ถ€ํŠธ 1.X ๋Š” ์•„ํŒŒ์น˜ ํ†ฐ์บฃ์„ ๊ธฐ๋ณธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜/์›น ์„œ๋ฒ„๋กœ ์‚ฌ์šฉํ•˜์ง€๋งŒ ํ†ฐ์บฃ์€ ๋ธ”๋Ÿญํ‚น ์ž‘์—…๋งŒ ์ง€์›ํ•œ๋‹ค. ์Šคํ”„๋ง ๋ถ€ํŠธ 2.0์—์„œ๋Š” ๋„Œ๋ธ”๋กœํ‚น IO ๊ธฐ๋Šฅ์˜ ๋ฆฌ์•กํ‹ฐ๋ธŒ ์„œ๋น„์Šค๋ฅผ ์œ„ํ•ด ๋„คํ‹ฐ๋ฅผ ๋Œ€์‹  ์„ ํƒํ–ˆ๋‹ค.

์ด ๊ธฐ์ˆ ์„ ์„ ํƒํ•˜๋ฉด ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋Š” ๋” ๋งŽ์€ ๋ถ€ํ•˜๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ ์–ด๋А๋•Œ๋ณด๋‹ค ํšจ๊ณผ์ ์ด ๋  ๊ฒƒ์ด๋‹ค.

NodeJS, Nginx, Apache Mina, Vert.X, Akka๋“ค๋„ ๋„Œ๋ธ”๋Ÿญํ‚น IO ์‹œ์Šคํ…œ์ด๋‹ค.

๋ฆฌ์•กํ‹ฐ๋ธŒ ์„œ๋น„์Šค ๋งŒ๋“ค๊ธฐ

Publish & Subscribe

๋ฆฌ์•กํ‹ฐ๋ธŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ์ผ๋ จ์˜ ์ด๋ฒคํŠธ๊ฐ€ ๊ฐ์ง€๋˜๋ฉด ํ•„์š”ํ•œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ „์†ก๋˜๋Š” ์ด๋ฒคํŠธ๋ชจ๋ธ(Event Model) ๋งค์ปค๋‹ˆ์ฆ˜์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ๋‹ค. ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๋ ค๊ณ  ํ•  ๋•Œ, ์ •์˜ํ•˜๋Š” ๊ฒƒ์ด ๊ตฌ๋…์ž(Subscriber)์ด๋‹ค. ๊ตฌ๋…์ž๋Š” ์ด๋ฒคํŠธ๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•ด์•ผ ํ•˜๋Š” ์ฑ…์ž„์ด ์žˆ๋Š” ๊ฐ์ฒด๋‹ค. ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์ฑ…์ž„์ด ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ๊ฒŒ์‹œ์ž(Publisher) ๋ผ๊ณ ํ•œ๋‹ค. ๊ฒŒ์‹œ์ž์™€ ๊ตฌ๋…์ž ๊ฐ์ฒด๋Š” ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ž‘์—…์„ ๋ธ”๋กœํ‚นํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ด๋Ÿฐ ๊ฐ์ฒด๋Š” ํ•„์š”ํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ˆ˜์‹ ๊ธฐ(Listener)์ด๋‹ค. ์Šคํ”„๋ง์€ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•œ๋‹ค, ์ด ๋ฐ์ดํ„ฐ๋Š” ๋ฆฌ์•กํ‹ฐ๋ธŒ ์ŠคํŠธ๋ฆผ ์‚ฌ์–‘์— ์ •์˜๋œ ๋Œ€๋กœ ๋ฆฌ์•กํ‹ฐ๋ธŒ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ „์†ก๋˜์–ด ๋„Œ๋ธ”๋กœํ‚น์˜ Back-pressure๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

back-pressure

๋„ˆ๋ฌด ์ปค์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ ํž˜๋“  ๋ฐ์ดํ„ฐ Stream์„ ์กฐ์ ˆํ•˜๋Š” ๊ฒƒ back-pressure๋ฅผ ํ•˜๋Š” ๋ชฉ์ ์€ Subscriber๊ฐ€ ์‹ค์งˆ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์„ ๋งŒํผ์˜ ์ˆ˜์ค€์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณต๋ฐ›๋Š” ๊ฒƒ Publisher ์ชฝ์—์„œ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋Š” ๋ฒ„ํผ๊ฐ€ ๋˜๊ธฐ๋„ ํ•œ๋‹ค. pull-push hybrid approach to data stream - publisher๊ฐ€ ์ฒ˜๋ฆฌ ํ•  ๋งŒํผ์˜ ์ˆ˜์ค€๋งŒ ์š”์ฒญ ๋ฐ›๊ณ  ๋‹ค ์ฒ˜๋ฆฌํ•˜๋ฉด ๋‹ค์‹œ ์š”์ฒญ ๋ฐ›๋Š” ํ˜•์‹ Deley ๊ฐ€ ๋ฐœ์ƒํ•ด์„œ Latency๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ณ  ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋‹ค ์จ๋ฒ„๋ ค Out of Memory๊ฐ€ ๋ฐœ์ƒ ํ•  ์ˆ˜๋„์žˆ๋‹ค.

๋‹จ์ผ๊ฐ์ฒด ๊ฒŒ์‹œ

๋ฆฌ์•กํ„ฐ๋Š” ๋ชจ๋…ธ(Mono)๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ๋ฆฌ์•กํ‹ฐ๋ธŒ ๊ฒŒ์‹œ์ž๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ์ด ๊ฒŒ์‹œ์ž๋Š” ํ•˜๋‚˜์˜ ๊ฒฐ๊ณผ๋งŒ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

// ์ผ๋ฐ˜์ ์ธ Mono ์ƒ์„ฑ
val customerMono = Mono<Customer> = Mono.just(Customer(1, "Mono"))
// ๊ณ ์ฐจํ•จ์ˆ˜ 
val customerMono = Mono<Customer> = Customer(1, "Mono").toMono()
// ํƒ€์ž… ์ถ”๋ก 
val customer = Customer(1, "Mono").toMono()

๋ชจ๋…ธ๋Š” ์‹ค์ œ๋กœ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ์ธ์Šคํ„ด์Šค๊ฐ€ ์•„๋‹ˆ๋ผ ์•ž์œผ๋กœ ์–ป์œผ๋ ค๊ณ  ํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ์•ฝ์†์ด๋‹ค. Mono<Customer>๋กœ ์„ ์–ธํ•  ๋–„๋Š” ์ด ๊ฒŒ์‹œ์ž๊ฐ€ ์•ž์œผ๋กœ Customer๋ฅผ ๊ฒŒ์‹œํ•  ๊ฒƒ์ž„์„ ๋‚˜ํƒ€๋‚ผ ๋ฟ์ด๋‹ค. ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ํ•ด๋‹น ๊ฒŒ์‹œ์ž๋ฅผ ๋“ฑ๋กํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ์–ป๊ฒŒ ๋œ๋‹ค.

์—ฌ๋Ÿฌ ๊ฐ์ฒด ๊ฒŒ์‹œ

๋ฆฌ์•กํ„ฐ๋Š” 0์—์„œ ๋ฌดํ•œ๋Œ€์˜ ์š”์†Œ๋ฅผ ๊ฐ€์ง„ ๊ฒŒ์‹œ์ž๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ํด๋ž˜์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ํด๋ž˜์Šค์˜ ์ด๋ฆ„์€ Flux์ด๋‹ค. ๋‹จ์ˆœ ํ”Œ๋Ÿญ์Šค๋ฅผ ๋งŒ๋“ค๋ ค๋ฉด ๋‹ค์Œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

val customer = Flux.fromIterable(listOf(Customer(1, "One"), Customer(2, "two")))
// like kotlin
val customerFlux = listOf(Customer(1, "One"), Customer(2, "two")).toFlux()

Flux๋„ Mono์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๊ตฌ๋… ์‹œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ์•ฝ์†์ด๋‹ค. Controller๊ฐ€ Flux๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์Šคํ”„๋ง์€ ์ž๋™ ๊ตฌ์„ฑ์œผ๋กœ ์ƒˆ๋กœ์šด ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ๊ตฌ๋…ํ•˜๊ฒŒ ๋œ๋‹ค.

๋ชจ๋…ธ๋Š” ๊ฒฐ๊ณผ๋ฅผ ํ•˜๋‚˜๋งŒ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ํ”Œ๋Ÿญ์Šค๋Š” ๊ฒฐ๊ณผ๋ฅผ O-n๊ฐœ ๋ฐ˜ํ™˜ ํ•˜๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ํ•˜๋‚˜๋งŒ ๋ฐ˜ํ™˜ํ•˜๋ ค๋ฉด ํ”Œ๋Ÿญ์Šค ๋Œ€์‹  ๋ชจ๋…ธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹๋‹ค.

RouterFunction ์‚ฌ์šฉ

Annotation๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œใ„ด๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•ด ์ปจํŠธ๋กค๋Ÿฌ ๋Œ€์‹  RouterFunction์„ ์‚ฌ์šฉํ•œ๋‹ค.

// CustomerRouter.kt
@Component
class CustomerRouter(private val customHandler: CustomHandler) {
    @Bean
    fun customerRoutes(): RouterFunction<*> = router {
        "/functional".nest {
            "/customer".nest {
                GET("/{id}", customerHandler::get)
                POST("/", customerHandler::create)
            }
            "/customers".nest {
                GET("/", customerHandler::search)
            }
        }
    }
}

// CustomHandler.kt
@Component
class CustomerHandler(val customerService: CustomerService) {
    fun get(serverRequest: ServerRequest): Mono<ServerResponse> =
        customerService.getCustomer(serverRequest.pathVariable("id").toInt())
            .flatMap { ok().body(fromObject(it)) }
            .switchIfEmpty(notFound().build())

    fun search(serverRequest: ServerRequest) =
        ok().body(
            customerService.searchCustomers(
	            serverRequest.queryParam("nameFilter").orElse("")),
            Customer::class.java
        )

    fun create(serverRequest: ServerRequest) =
        customerService.createCustomer(serverRequest.bodyToMono()).flatMap {
            status(HttpStatus.CREATED).body(fromObject(it))
        }.onErrorResume(Exception::class) {
            badRequest().body(
                fromObject(
                    ErrorResponse(
                        "error createing customer",
                        it.message ?: "error"
                    )
                )
            )
        }
}

// CustomerServiceImpl.kt
@Component
class CustomerServiceImpl : CustomerService {
    companion object {
        val initialCustomers = arrayOf(
            Customer(1, "Kotlin"),
            Customer(2, "Spring"),
            Customer(3, "MicroService", Telephone("+44", "198273128"))
        )
    }

    val customers = ConcurrentHashMap<Int, Customer>(initialCustomers.associateBy(Customer::id))

    override fun getCustomer(id: Int) = customers[id]?.toMono() ?: Mono.empty()

    override fun searchCustomers(nameFilter: String): Flux<Customer> =
        customers.filter {
            it.value.name.contains(nameFilter, true)
        }.map(Map.Entry<Int, Customer>::value).toFlux()

    override fun createCustomer(customerMono: Mono<Customer>): Mono<Customer> =
        customerMono.map {
            customers[it.id] = it
            it
        }.toMono()
}

์œ„์™€ ๊ฐ™์ด ์ •์˜ํ•˜๋ฉด ๋ผ์šฐํ„ฐ๊ฐ€ /functional ๊ฒฝ๋กœ์˜ ๋ชจ๋“  ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.

์ด ๋ผ์šฐํ„ฐ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์Šคํ”„๋ง์ด ์›นํ”Œ๋Ÿญ์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ฝ”ํ‹€๋ฆฐ DSL(Domain Specific Language)์„ ์‚ฌ์šฉํ•œ๋‹ค, https://kotlinlang.org/docs/reference/type-safe-builders.html

  • Router : ๋ฆฌ์•กํ‹ฐ๋ธŒ ์„œ๋น„์Šค๊ฐ€ ์‘๋‹ตํ•˜๋Š” ๊ฒฝ๋กœ์™€ ๋ฉ”์†Œ๋“œ๋ฅผ ์ฒ˜๋ฆฌ
  • Handler : ๊ถค์ ์ธ ์š”์ฒญ์„ ์‘๋‹ต์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋กœ์ง์„ ์ˆ˜ํ–‰
  • Service : ๋„๋ฉ”์ธ์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์บก์Аํ™”

์ด๋Ÿฐ ๋ณ„๋„์˜ ๋ ˆ์ด์–ด๊ฐ€ ์žˆ์œผ๋ฉด ์ƒˆ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์žˆ๋Š” ๊ณณ์„ ๋ณ€๊ฒฝํ•  ๋•Œ ๋„์›€์ด ๋œ๋‹ค. ๋ผ์šฐํ„ฐ๋Š” ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์„ ์œ„ํ•ด ๋™์ผํ•œ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•ธ๋“ค๋Ÿฌ๋Š” ์—ฌ๋Ÿฌ ์„œ๋น„์Šค๋ฅผ ๊ฒฐํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•œ ๋ ˆ์ด์–ด๋ฅผ ๋ณ€๊ฒฝํ•ด๋„ ๋‹ค๋ฅธ ๋ ˆ์ด์–ด์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์„œ๋น„์Šค์—์„œ ๋„๋ฉ”์ธ ๋กœ์ง์„ ๋ณ€๊ฒฝํ•˜๋”๋ผ๋„ ๋ผ์šฐํ„ฐ ๋˜๋Š” ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ณ€๊ฒฝํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค์—์„œ ์ปคํ”Œ๋ง(Coupling)์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•˜๋Š”๊ฒƒ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ ˆ์ด์–ด๋ฅผ ๋งŒ๋“ค ๋•Œ๋„ ์ปคํ”Œ๋ง์„ ํ”ผํ•ด์•ผ ํ•œ๋‹ค. ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค ๋•Œ ์ด๋Ÿฐ ๋ ˆ์ด์–ด์™€์˜ ์ƒํ˜ธ ์ž‘์šฉ ๋ฐฉ์‹์„ ์ƒ๊ฐํ•˜์ž. ๋‹จ์ผ ์ฑ…์ž„ ์›์น™(Single Responsibility Principle)์„ ์ ์šฉํ•ด์•ผ ํ•œ๋‹ค. ํ•˜๋‚˜์˜ ๋ ˆ์ด์–ด์˜ ๋ณ€ํ™”์—๋Š” ์˜ค์ง ํ•œ ๊ฐ€์ง€ ์ด์œ ๋งŒ ์žˆ์–ด์•ผ ํ•œ๋‹ค.