07.CQRS

๋ณต์žกํ•œ ์กฐ๊ฑด์œผ๋กœ ํ•„ํ„ฐ๋งํ•˜๊ฑฐ๋‚˜ ๋‹ค์–‘ํ•œ ์ง‘๊ณ„ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ถฉ์กฑ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋ช…๋ น๊ณผ ์กฐํšŒ ์ฑ…์ž„ ๋ถ„๋ฆฌ(CQRS)๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ด๋ฒคํŠธ ์†Œ์‹ฑ์„ ์‚ฌ์šฉํ•œ ์‹œ์Šคํ…œ์—์„œ CQRS๋Š” ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜

  • CQRS๋Š” ํ˜„์žฌ ์ƒํƒœ๋งŒ ๊ธฐ๋กํ•˜๋Š” ์‹œ์Šคํ…œ์—์„œ๋„ ์œ ์šฉํ•˜๊ฒŒ ํ™œ์šฉ ๊ฐ€๋Šฅ

๋ช…๋ น๊ณผ ์กฐํšŒ ์ฑ…์ž„ ๋ถ„๋ฆฌ

CQS๋Š” ๊ฐ์ฒด๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์†Œ๋“ค๋ฅด ๊ฐ์ฒด์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์ปค๋งจ๋“œ์™€ ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ฟผ๋ฆฌ๋กœ ๊ตฌ๋ถ„

CQS and CQRS

  • CQRS๋Š” ๋งŽ์€ ํ…Œ์ด๋ธ” ์กฐ์ธ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ์กฐํšŒ์— ์ตœ์ ํ™”์‹œํ‚จ ํ…Œ์ด๋ธ”์„ ๋ถ„๋ฆฌํ•ด์„œ ์„ค๊ณ„ํ•˜๊ณ  ์ปค๋งจ๋“œ ์ฒ˜๋ฆฌ๋ฅผ ์™„๋ฃŒํ•œ ํ›„ ๋ฐœํ–‰ํ•œ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ์—์„œ ํ•„์š”ํ•œ ์†์„ฑ์„ ๋ณต์‚ฌํ•ด ์กฐํšŒ ์ „์šฉ ํ…Œ์ด๋ธ”์— ํ•œ๋ฒˆ ๋” CUD๋ฅผ ์‹คํ–‰

๊ตฌ์ฒดํ™”๋œ ๋ทฐ์™€ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ

๋ทฐ๊ฐ€ ๋…ผ๋ฆฌ์ ์ธ ํ…Œ์ด๋ธ”์ธ ๋ฐ˜๋ฉด ๊ตฌ์ฒดํ™”๋œ ๋ทฐ(Materialized View)๋Š” ๋ฌผ๋ฆฌ์ ์œผ๋กœ ์กด์žฌํ•˜๋Š” ํ…Œ์ด๋ธ”

  • ๊ตฌ์ฒดํ™”๋œ ๋ทฐ๋Š” ์ฃผ๋กœ ๋ณต์žกํ•œ ์กฐ์ธ๊ณผ ๊ฐ™์ด ๋งŽ์€ ๋น„์šฉ์ด ๋””๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ ์‚ฌ์šฉ

  • ๊ตฌ์ฒดํ™”๋œ ๋ทฐ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ AVG, SUM, COUNT ๊ฐ™์€ ์ง‘๊ณ„ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ๋‹ค์–‘ํ•œ ์กฐ๊ฑด์„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

.

๋ทฐ์™€ ๊ตฌ์ฒดํ™”๋œ ๋ทฐ์˜ ํŠน์ง•๊ณผ ์ฐจ์ด์ 

๋ทฐ

  • ๋ทฐ๋Š” ๋ฏธ๋ฆฌ ๋„ค์ด๋ฐํ•œ ์ฟผ๋ฆฌ๋กœ ์–ด๋–ค ๊ฒƒ๋„ ์ถ”๊ฐ€ ์ €์žฅ ๋ถˆ๊ฐ€

  • ๋ทฐ์— ์งˆ์˜ํ•˜๋ฉด ๋ทฐ ์ •์˜์— ๊ธฐ๋ฐ˜ํ•ด ์‹ค์ œ ํ…Œ์ด๋ธ”์˜ ๋ฐ์ดํ„ฐ ์กฐํšŒ

  • ๋ทฐ๋Š” ํ…Œ์ด๋ธ” ์ฟผ๋ฆฌ๋ณด๋‹ค ๋А๋ฆผ

  • ์›๋ž˜์˜ ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฌธ์€ ๋ทฐ ์ •์˜๋กœ ์ด๋™ํ•  ๋ฟ ์œ ์ง€๋ณด์ˆ˜ ๋น„์šฉ์€ ๊ฐ์†Œํ•˜์ง€ ์•Š์Œ

๊ตฌ์ฒดํ™”๋œ ๋ทฐ

  • ๊ตฌ์ฒดํ™”๋œ ๋ทฐ๋Š” ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ์ฃผ๊ธฐ์ ์œผ๋กœ ๊ฐฑ์‹ 

  • ๊ตฌ์ฒดํ™”๋œ ๋ทฐ์— ์งˆ์˜ํ•˜๋ฉด ๊ตฌ์ฒดํ™”๋œ ๋ทฐ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜

  • JSON์„ ํฌํ•จํ•ด ๋‹ค์–‘ํ•œ ์กฐ๊ฑด์˜ SELECT ์ฟผ๋ฆฌ๋ฅผ ์œ„ํ•œ ๋น ๋ฅธ ์„ฑ๋Šฅ์„ ์ œ๊ณต

  • ์ฟผ๋ฆฌ ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ ์‹คํ–‰ ๊ณ„ํš์„ ์ˆ˜๋ฆฝํ•  ๋•Œ ๋” ๋น ๋ฅธ ๋ฐฉ๋ฒ•์„ ์„ ํƒ

.

CQRS ํ•ต์‹ฌ ๊ฐ์ฒด์™€ ์˜์กด์„ฑ

  • ๊ตฌ์ฒดํ™”๋œ ๋ทฐ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ„ ๊ด€๊ณ„

  • ์• ๊ทธ๋ฆฌ๊ฒŒ์ดํŠธ๊ฐ€ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๋ฉด ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•ด ๋ทฐ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜

  • ViewStore๋Š” ๋ทฐ ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฒดํ™”๋œ ๋ทฐ ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘ํ•œ JPA ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ

  • ์กฐํšŒ ๋ชฉ์ ์— ๋”ฐ๋ผ ๊ฐœ๋ณ„ ํ…Œ์ด๋ธ”๋กœ ๋ถ„๋ฆฌํ•ด ์„ค๊ณ„ํ•˜๊ฑฐ๋‚˜ ๋™์ผ ์กฐํšŒ ์กฐ๊ฑด์„ ๊ฐ€์ง„ ์—ฌ๋Ÿฌ ๋ทฐ ํด๋ž˜์Šค๋ฅผ ํ•˜๋‚˜์˜ ํ…Œ์ด๋ธ”๋กœ ํ†ตํ•ฉ ๊ฐ€๋Šฅ

.

์ƒํ’ˆ ํŒ๋งค๋Ÿ‰ ๋ทฐ

์ฃผ๋ฌธ ์• ๊ทธ๋ฆฌ๊ฒŒ์ดํŠธ๊ฐ€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ๋ฉ”์‹œ์ง€ ๋ฆด๋ ˆ์ด๊ฐ€ OrderCompleted ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰

  • OrderCompleted ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•˜๋Š” ProductOrderViewHandler๋Š” ์ƒํ’ˆ๋ณ„ ํŒ๋งค ์ˆ˜๋Ÿ‰ ์†์„ฑ์„ ํฌํ•จํ•˜๋Š” ProductORderView๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ

๋ฐ˜๋Œ€๋กœ ์ฃผ๋ฌธ์„ ์ทจ์†Œํ•˜๋ฉด ํ•ด๋‹น ์ƒํ’ˆ ํŒ๋งค๋Ÿ‰ ๋ทฐ๋ฅผ ์‚ญ์ œ

  • ProductOrderViewHandler๋Š” OrderCanceled ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•ด OrderNo๋กœ ๋ทฐ ํ…Œ์ด๋ธ”์˜ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฐพ์•„ ์‚ญ์ œ

.

์ƒ๋‹ด์‚ฌ ์ผ์ผ ์ด ํ†ตํ™”์‹œ๊ฐ„ ๋ทฐ

  • ํ†ตํ™”๋ฅผ ์‹œ์ž‘ํ•˜๋ฉด BeginCall ์ปค๋งจ๋“œ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š” ์ƒ์„ฑ์ž๋ฅผ ํ˜ธ์ถœ

  • ์ƒ์„ฑ์ž๋Š” Talktime ๊ฐ’ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋ฉด์„œ ํ˜„์žฌ ์‹œ๊ฐ์„ start ๊ฐ’์œผ๋กœ ํ• ๋‹น

  • ํ†ตํ™”๋ฅผ ์ข…๋ฃŒํ•˜๋ฉด end ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  Talktime์˜ end์— ํ˜„์žฌ ์‹œ๊ฐ์„ ํ• ๋‹นํ•˜๋ฉด์„œ ํ†ตํ™”์‹œ๊ฐ„์„ ๊ณ„์‚ฐํ•ด CallEnded ์ด๋ฒคํŠธ์— ํฌํ•จ

  • ์กฐํšŒ ์š”์ฒญ์— ๋‹จ์ˆœ SELECT๋งŒ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ๋ฐฑ์—”๋“œ๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ํฐ ๋ถ€ํ•˜๊ฐ€ ์—†์Œ

  • ๋ฐ˜๋Œ€๋กœ ์ „ํ™” ์ƒ๋‹ด์‚ฌ๊ฐ€ ํ†ตํ™” ์ข…๋ฃŒ ์‹œ ์กฐํšŒ ์ „์šฉ ํ…Œ์ด๋ธ”์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ๋น„์šฉ์„ ๊ฐœ์„ ํ•ด์•ผ ํ•  ๋Œ€์ƒ

    • ์ด๋Š” ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค์— ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•ด์„œ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋‘ ๊ธฐ๋Šฅ์˜ ์‹คํ–‰ ์‹œ๊ฐ„์„ ๋ถ„๋ฆฌ ๊ฐ€๋Šฅ

๋ทฐ ์กฐํšŒ

์กฐํšŒ๋ฅผ ์œ„ํ•œ ํด๋ž˜์Šค๋Š” ์ ‘๋‘์–ด๋กœ Query๋ฅผ ์‚ฌ์šฉ

@RestController
@RequestMapping("/cart")
class CartEndpoint(
    private val httpSession: HttpSession,
    private val cartService: CartService,
    private val cartViewStore: CartViewStore
) {

    @GetMapping(headers = ["query=QueryCart"])
    fun queryCart(): Cart {
        return cartService.queryCart()
    }

    @GetMapping(headers = ["query=QueryCartItem"])
    fun queryItem(@RequestParam itemId: String): ItemView {
        val userId = httpSession.getAttribute("userId")?.toString() ?: throw IllegalStateException("User not logged in")
        val query = QueryCartItem(userId, itemId)
        return cartViewStore.queryItem(query)
    }
}

๋ทฐ ๋ณต์›

์กฐํšŒ ์ „์šฉ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•ด๋„ ์ด๋ฒคํŠธ ์Šคํ† ์–ด์— ๋ชจ๋“  ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ํ•„์š” ์‹œ ๋‹ค์‹œ ์กฐํšŒ ์ „์šฉ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑ ๊ฐ€๋Šฅ

  • ViewRecover๊ฐ€ ์ด๋ฒคํŠธ ์ €์žฅ์†Œ์—์„œ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ์กฐํšŒ ํ›„ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌํ•ด ์‹ค์ˆ˜๋กœ ์‚ญ์ œํ•œ ์ฟ ์ฒดํ™”๋œ ๋ทฐ๋ฅผ ๋‹ค์‹œ ์ƒ์„ฑ

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๋ชจ๋“ˆ

CQRS๋ฅผ ์ ์šฉํ•œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์˜ ๋ชจ๋“ˆ์€ view

  • view ํŒจํ‚ค์ง€๋Š” ์กฐํšŒ ์ „์šฉ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ชจ๋“  ํด๋ž˜์Šค๋ฅผ ํฌํ•จ

  • ์กฐํšŒ๋ฅผ ์œ„ํ•œ ํด๋ž˜์Šค๋Š” query ํŒจํ‚ค์ง€์— ์œ„์น˜

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ทฐ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๋ฉด view ํ•˜์œ„ ํŒจํ‚ค์ง€์— ์ถ”๊ฐ€ํ•˜์—ฌ ๊ตฌ๋ถ„

CQRS ํ™œ์šฉ ์‚ฌ๋ก€

์ด๋ฒคํŠธ ์ฃผ๋„ ์•„ํ‚คํ…์ฒ˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ํ™œ์šฉํ•ด ์„œ๋น„์Šค ๊ฐ„์˜ ์˜์กด์„ฑ์„ ์ตœ์†Œํ™”ํ•ด ๋А์Šจํ•˜๊ฒŒ ๊ฒฐํ•ฉํ•œ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.

  • ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ์ด๋ฒคํŠธ๋ฅผ ์ ๊ทน์ ์œผ๋กœ ๋„์ž…ํ•˜๋ฉด ๋‹ค์–‘ํ•œ ์กฐํšŒ ๊ธฐ๋Šฅ์˜ ์ถ”๊ฐ€/๋ณ€๊ฒฝ์ด ์ž์œ ๋กœ์›Œ ํ™•์žฅ์„ฑ์„ ๋†’์ด๋ฉด์„œ ๋…๋ฆฝ์ ์ธ ๊ฐœ์„ ์ด ๊ฐ€๋Šฅ

์ด๋ฒคํŠธ ์†Œ์‹ฑ๊ณผ ๋ทฐ ์ผ๊ด€์„ฑ

CQRS ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์™€ ํŠธ๋žœ์žญ์…˜ ๋ถ„๋ฆฌ

  • ์ด๋ฒคํŠธ ์†Œ์‹ฑ์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ์• ๊ทธ๋ฆฌ๊ฒŒ์ดํŠธ๊ฐ€ ์ฒ˜๋ฆฌํ•˜๊ณ  ํ…Œ์ด๋ธ”๋“ค์„ ์กฐ์ธํ•ด ์กฐํšŒํ•˜๋Š” ์ฑ…์ž„์„ CQRS๋กœ ๋ถ„๋ฆฌ

  • CQRS์—์„œ ์กฐํšŒ ์ „์šฉ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ์›์น™์ ์œผ๋กœ ๋น„๋™๊ธฐ๋กœ ๋ฐ˜์‘ํ•ด์•ผ ํ•จ

CQRS ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์™€ ํŠธ๋žœ์žญ์…˜ ํ†ตํ•ฉ

  • ํ•œ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์—์„œ ์ฒ˜๋ฆฌ๋จ์„ ๋ณด์žฅ

  • ๋‹จ, ํ•œ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋ฅผ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ๋‹จ์ผ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋‹จ์ 

์š”์•ฝ

์ด๋ฒคํŠธ ์†Œ์‹ฑ์„ ์ ์šฉํ•œ ์‹œ์Šคํ…œ์—์„œ CQRS๋ฅผ ์ด์šฉํ•ด ๋ณต์žกํ•œ ์กฐํšŒ๋ฅผ ์ง€์›ํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ทฐ์™€ ๊ตฌ์ฒดํ™”๋œ ๋ทฐ

  • ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•ด ๊ตฌ์ฒดํ™”๋œ ๋ทฐ์—์„œ ์กฐํšŒ ์ „์šฉ ๋ฐ์ดํ„ฐ๋ฅผ ๋น„๋™๊ธฐ๋กœ ์ƒ์„ฑ ๊ฐ€๋Šฅ

  • ๋‹จ์ˆœ ์กฐํšŒ์šฉ์—์„œ ์‹œ์ž‘ํ•ด ๋ณต์žกํ•œ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๊นŒ์ง€ CQRS๋ฅผ ํ™œ์šฉ

  • ๊ตฌ์ฒดํ™”๋œ ๋ทฐ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์€ ๋น„๋™๊ธฐ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ํ•„์š”์— ๋”ฐ๋ผ ๋‹จ์ผ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๊ตฌ์ฒดํ™”๋œ ๋ทฐ๋ฅผ ์ƒ์„ฑ

  • CQRS๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๊ธฐ์กด ์„œ๋น„์Šค ์ˆ˜์ • ์—†์ด ์ƒˆ๋กœ์šด ์„œ๋น„์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๊ฐ€๋Šฅ

Last updated