๋ฆฌ์กํฐ๋ธ ๋ง์ดํฌ๋ก์๋น์ค๋ ๋ง์ดํฌ๋ก์๋น์ค์ ํ๋จ๊ณ ์งํํ ๋ชจ์ต์ด๋ค. ๋ฆฌ์กํฐ๋ธ ํจ๋ฌ๋ค์์ ๊ธฐ๋ฐ์ผ๋ก ๊ธฐ์กด์ ์ ํต์ ์ธ ์ํคํ ์ฒ๋ฅผ ๋ฅ๊ฐํ๋ ๋์๋ ฅ, ๋ณต์์ฑ, ํ๋ ฅ์ ์ธ ๋ฉ์์ง ๊ธฐ๋ฐ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ ๋ชฉํ๋ก ํ๋ค.
๋คํฐ๋ ์๋ ๋๋ธ๋กํน 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 ์์คํ ์ด๋ค.
๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ์ ์ผ๋ จ์ ์ด๋ฒคํธ๊ฐ ๊ฐ์ง๋๋ฉด ํ์ํ ์ฌ์ฉ์์๊ฒ ์ ์ก๋๋ ์ด๋ฒคํธ๋ชจ๋ธ(Event Model) ๋งค์ปค๋์ฆ์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ค. ์ด๋ฒคํธ๋ฅผ ์์ ํ๋ ค๊ณ ํ ๋, ์ ์ํ๋ ๊ฒ์ด ๊ตฌ๋ ์(Subscriber)์ด๋ค. ๊ตฌ๋ ์๋ ์ด๋ฒคํธ๊ฐ ์์ฑ๋ ๋ ์ด๋ฒคํธ๋ฅผ ์์ ํด์ผ ํ๋ ์ฑ ์์ด ์๋ ๊ฐ์ฒด๋ค. ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๋ ์ฑ ์์ด ์๋ ๊ฐ์ฒด๋ฅผ ๊ฒ์์(Publisher) ๋ผ๊ณ ํ๋ค. ๊ฒ์์์ ๊ตฌ๋ ์ ๊ฐ์ฒด๋ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ์์ ์ ๋ธ๋กํนํ์ง ์๋๋ค. ์ด๋ฐ ๊ฐ์ฒด๋ ํ์ํ ๋ ํธ์ถ๋๋ ์์ ๊ธฐ(Listener)์ด๋ค. ์คํ๋ง์ ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ ๋ชจ๋ ์ฌ์ฉ์์๊ฒ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๊ธฐ ์ํด ์ด๋ฒคํธ๋ฅผ ๊ตฌ๋ ํ๋ค, ์ด ๋ฐ์ดํฐ๋ ๋ฆฌ์กํฐ๋ธ ์คํธ๋ฆผ ์ฌ์์ ์ ์๋ ๋๋ก ๋ฆฌ์กํฐ๋ธ ์คํธ๋ฆผ์ผ๋ก ์ ์ก๋์ด ๋๋ธ๋กํน์ 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๊ฐ ๋ฐํ ํ๋ ๊ฒฝ์ฐ์ ์ฌ์ฉํ ์ ์๋ค. ํ์ง๋ง ํ๋๋ง ๋ฐํํ๋ ค๋ฉด ํ๋ญ์ค ๋์ ๋ชจ๋ ธ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข๋ค.
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)์ ์ ์ฉํด์ผ ํ๋ค. ํ๋์ ๋ ์ด์ด์ ๋ณํ์๋ ์ค์ง ํ ๊ฐ์ง ์ด์ ๋ง ์์ด์ผ ํ๋ค.