๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • ์žฅ์›์ต ๊ธฐ์ˆ ๋ธ”๋กœ๊ทธ
๐Ÿ”ฌweb application/- System architecture

CQRS ํŒจํ„ด์— ๋Œ€ํ•œ ์˜คํ•ด ํ’€๊ธฐ

by Wonit 2022. 6. 11.

๋ชฉ์ฐจ

  • ๋„์ž…
  • Query ์™€ Command ๋ž€?
  • CQRS ๋ž€
  • CQRS ์˜ ์žฅ๋‹จ์ 

๋„์ž…

 

ํšŒ์‚ฌ ์‹œ์Šคํ…œ์ด ์ „ํ†ต์ ์ธ CRUD ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Event ๊ธฐ๋ฐ˜์˜ ์‹œ์Šคํ…œ์œผ๋กœ ๋ฐ”๋€Œ์–ด ๊ฐ€๋Š” ๊ณผ์ •์— ํŒ€์— ํ•ฉ๋ฅ˜๋ฅผ ํ•˜๊ฒŒ ๋˜์–ด ๋‚˜์˜ ์ตœ๊ทผ ๊ฐ€์žฅ ํฐ ๊ด€์‹ฌ์‚ฌ๊ฐ€ ๋ฐ”๋กœ ์ด CQRS ์ด๋‹ค.

 

ํ•™๋ถ€ ์‹œ์ ˆ์—๋„ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉฐ ์ž ๊น ์ž ๊น ๋ดค๋˜ CQRS ๋Š” ์ด๋ฆ„ ๋ถ€ํ„ฐ ์ƒ์†Œํ•˜๊ธฐ์— ๊ฒ์„ ๋จน์—ˆ๋˜ ๊ธฐ์–ต์ด ๋‚œ๋‹ค.

 

ํ•˜์ง€๋งŒ CQRS ์˜ ์›๋ฆฌ ์ž์ฒด๋Š” ์‚ฌ์‹ค ๋˜๊ฒŒ ๊ฐ„๋‹จํ•˜๋‹ค

 

Command ์™€ Query ๋ฅผ ๋ถ„๋ฆฌํ•˜์ž!

 

Command ์™€ Query ๋ฅผ ๋จผ์ € ์ •์˜ํ•˜๊ณ  ์ด์•ผ๊ธฐ๋ฅผ ๊ณ„์† ํ•ด๋ณด์ž

 

Query ์™€ Command ๋ž€?

 

Query ์™€ Command ์— ๋Œ€ํ•ด์„œ ์ด์•ผ๊ธฐ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” CQS ์— ๋Œ€ํ•ด์„œ ๋จผ์ € ์•Œ์•„๋ณผ ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

 

 

CQS ๋Š” Design By Contract ๋ผ๋Š” ์šฉ์–ด๋ฅผ ๋งŒ๋“  ๋ฒ„ํŠธ๋ž€๋“œ ๋ฉ”์ด์–ด, Betrand Meyer ๊ฐ€ ์†Œ๊ฐœํ•œ ๊ฐœ๋…์ด๋‹ค.

 

ํ•จ์ˆ˜๋Š” ํŠน์ • ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ฝ”๋“œ ๋ธ”๋ก์„ ์˜๋ฏธํ•˜๋Š”๋ฐ, ํ•จ์ˆ˜์˜ ๋ชฉ์ ์— ๋”ฐ๋ผ์„œ ๋‘๊ฐ€์ง€๋กœ ๋ถ„๋ฅ˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๊ทธ๊ฒƒ์ด ๋ฐ”๋กœ Command ์™€ Query ์ด๋‹ค.

 

Command

 

Command ๋Š” ์‹œ์Šคํ…œ์— ์–ด๋– ํ•œ side effect, ์ฆ‰ ๋ณ€๊ฒฝ์„ ๊ฐ€ํ•˜๋Š” ํ–‰์œ„๋ฅผ ํ•˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.

 

๊ทธ๋ž˜์„œ Command ์„ฑ ํ•จ์ˆ˜ ๋ผ๊ณ  ํ•œ๋‹ค๋ฉด ๋ณ€๊ฒฝ์„ ๊ฐ€ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Command ์„ฑ ํ•จ์ˆ˜๋Š” ์‹œ์Šคํ…œ์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚ค๋Š” ๋Œ€์‹  ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.

 

// O, ์ƒํƒœ๋งŒ ๋ณ€๊ฒฝ์‹œํ‚ด
void updateUser(User user) {
  user.updateAge(12);
}

// X, ๊ฐ’์„ ๋ฐ˜ํ™˜
User updateUser(User user) {
  return user.updateAge(12);
}

 

Query

 

์ด์— ๋ฐ˜ํ•ด์„œ Query ๋Š” ์‹œ์Šคํ…œ์˜ ์ƒํƒœ๋ฅผ ๊ด€์ฐฐํ•  ์ˆ˜ ์žˆ๋Š” ํ–‰์œ„๋ฅผ ํ•˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.

 

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Query ์„ฑ ํ•จ์ˆ˜๋ผ๊ณ  ํ•œ๋‹ค๋ฉด ๋‹จ์ง€ ์‹œ์Šคํ…œ์˜ ์ƒํƒœ๋งŒ ํ™•์ธํ•˜๋Š” ํ•จ์ˆ˜๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Query ์„ฑ ํ•จ์ˆ˜๋Š” ์‹œ์Šคํ…œ์˜ ์ƒํƒœ๋ฅผ ๋‹จ์ง€ ๋ฐ˜ํ™˜ํ•˜๊ธฐ๋งŒ ํ•˜๊ณ  ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚ค์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.

 

// O, ๊ฐ’๋งŒ ๋ฐ˜ํ™˜
User getUser(Long userId) {
  return users.get(userId);
}

// X, ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ
void getUser(Long userId) {
  User user = users.get(userId);
  user.updateLastQueriedAt();
  return user;
}

 

CQS ๋Š” Command Query Separation ์ด๋‹ค

 

 

๋ฒ„ํŠธ๋ž€๋“œ ๋งˆ์ด์–ด๋Š” ์œ„์˜ Command ์™€ Query ๋ฅผ ๋ถ„๋ฆฌํ•ด์•ผ ํ•˜๋ฉฐ ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๋Š” ์ด ์„ฑ๊ฒฉ์„ ๋„์–ด์•ผ ํ•œ๋‹ค๊ณ  ํ–ˆ๋‹ค.

 

์ฆ‰, ์–ด๋– ํ•œ ํ•จ์ˆ˜๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ทธ ํ•จ์ˆ˜๋Š” Command ๋˜๋Š” Query ์ค‘ ํ•˜๋‚˜์˜ ์—ญํ• ๋งŒ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•œ๋‹ค.

 

๋งŒ์•ฝ ํ•˜๋‚˜์˜ ํ•จ์ˆ˜์—์„œ Command ์™€ Query ๊ฐ€ ๋ชจ๋‘ ๋™์‹œ์— ์ผ์–ด๋‚˜๊ฒŒ ๋œ๋‹ค๋ฉด, ์ด๋Š” ์†Œํ”„ํŠธ์›จ์–ด์˜ 3๊ฐ€์ง€ ์›์น™ ์ค‘ ๋ณต์žกํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค๋Š” KISS ๊ฐ€ ์ง€์ผœ์ง€์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค.

 

์ด๋Ÿฐ ๊ด€์ ์—์„œ ์—ฐ์žฅ์„ ์ƒ์— ์žˆ๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ Command Query Responsibility Segregation ์ด๋‹ค.

 

CQRS ๋ž€?

 

CQRS ๋Š” Greg Young ์ด ์†Œ๊ฐœํ•œ ๋ง์ด๊ณ , CQS์— ๋น„ํ•ด ์กฐ๊ธˆ ๋” ํฐ ๋ ˆ๋ฒจ์—์„œ์˜ Command ๋ชจ๋“ˆ๊ณผ Query ๋ชจ๋“ˆ์˜ ์ฑ…์ž„์„ ๋ถ„๋ฆฌํ•˜์ž๋Š” ๋ง์ด๋‹ค.

 

CQS ๋Š” ์ฝ”๋“œ ๋ ˆ๋ฒจ์—์„œ์˜ ๋ถ„๋ฆฌ๋ฅผ ๋งํ•œ๋‹ค๋ฉด CQRS ๋Š” ์กฐ๊ธˆ ๋” ๊ฑฐ์‹œ์ ์ธ ๊ด€์ ์—์„œ์˜ ๋ถ„๋ฆฌ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

 


 

์•ž์„œ ๋ฒ„ํŠธ๋ž€๋“œ ๋ฉ”์ด์–ด๊ฐ€ ๋งํ•œ๊ฒƒ ์ฒ˜๋Ÿผ command ํ˜•์ด๊ฑฐ๋‚˜ query ํ˜•์˜ ํ•จ์ˆ˜๋ฅผ ๋ถ„๋ฆฌ์‹œํ‚ค๋ฉด ์†Œํ”„ํŠธ์›จ์–ด๊ฐ€ ๋”์šฑ ๋‹จ์ˆœํ•ด์ง€๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์›Œ์ง„๋‹ค๊ณ  ํ–ˆ๋‹ค.

 

CQRS ๋Š” ์ด ์›์น™์„ ์ฐจ์šฉํ•œ๋‹ค.

 

์•„๋ž˜์˜ ๊ทธ๋ฆผ์€ CQRS Journey Guide ํ•œ๊ธ€ ๋ฒˆ์—ญ ์—์„œ ๊ฐ€์ ธ์˜จ ๊ทธ๋ฆผ์ด๋‹ค.

 

์ผ๋ฐ˜์ ์œผ๋กœ CQRS ํŒจํ„ด์„ ์ ์šฉํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋ฅผ ๋„๊ฒŒ ๋œ๋‹ค.

 

 

์œ„ ๊ทธ๋ฆผ์„ ๋ณด๋ฉด ํ•˜๋‚˜์˜ service interface ๋ฅผ ๋‘๊ณ  ๋‘๊ฐœ์˜ ์„œ๋กœ ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์กด์žฌํ•œ๋‹ค.

 

  1. read side
  2. write side

 

ํ•œ๊ฐ€์ง€ ์˜ˆ๋ฅผ ๋“ค๋ฉฐ ์ด์•ผ๊ธฐํ•ด๋ณด์ž.

 

์•„๋ž˜์˜ ๊ทธ๋ฆผ์€ ๊ฒŒ์ž„ ๋ณด๋“œ ๋ผ๋Š” ๊ฐ€์ƒ์˜ ๋„๋ฉ”์ธ์„ ๋ชจ๋ธ๋งํ•œ ๊ทธ๋ฆผ์ด๋‹ค.

 

  • ์‚ฌ์šฉ์ž๋Š” ์ •๋‹ต์„ ์ž…๋ ฅํ•œ๋‹ค.
  • ์ •๋‹ต์ด๋ผ๋ฉด ์ ์ˆ˜๋ฅผ ์˜ฌ๋ฆฌ๊ณ  ์˜ค๋‹ต์ด๋ฉด ์ ์ˆ˜๋ฅผ ๋‚ด๋ฆฐ๋‹ค.
  • ์‚ฌ์šฉ์ž์˜ ๋žญํ‚น ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์œ„์˜ ๊ตฌ์กฐ๋Š” ๋™์ผํ•œ ๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ฆ‰, ์กฐํšŒ์˜ ์ฑ…์ž„๊ณผ ๋ช…๋ น์˜ ์ฑ…์ž„์ด ํ•˜๋‚˜์˜ ๋„๋ฉ”์ธ์— ํฌํ•จ๋˜์–ด์žˆ๋‹ค๋Š” ์ด์•ผ๊ธฐ๋‹ค.

 

๊ทธ๋Ÿผ ๋ฌด์Šจ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ๊นŒ?

 

์œ„ ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” 3๊ฐ€์ง€์˜ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ๊ฐ€ ์กด์žฌํ•  ๊ฒƒ์ด๋‹ค.

 

  1. ๋ณต์žก์„ฑ
  2. ์„ฑ๋Šฅ
  3. ํ™•์žฅ์„ฑ

 

๋ฌธ์ œ1. ๋ณต์žก์„ฑ, ๋„๋ฉ”์ธ์ด ๋น„๋Œ€ํ•ด์ง„๋‹ค.

 

์šฐ๋ฆฌ์˜ ์‹œ์Šคํ…œ์—์„œ ๋„๋ฉ”์ธ์ด ๊ฐ–๋Š” ์˜๋ฏธ์— ๋Œ€ํ•ด์„œ ์ƒ๊ฐํ•ด๋ณด์ž.

 

๋„๋ฉ”์ธ์ด๋ž€ ๊ณง ๋น„์ฆˆ๋‹ˆ์Šค์ด๋‹ค.

 

๋น„์ฆˆ๋‹ˆ์Šค๋Š” ๋ณดํ†ต ํŠน์ •ํ•œ ๋ฐ์ดํ„ฐ์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ (create, update, delete) ์„ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด๋Ÿฌํ•œ ๋น„์ฆˆ๋‹ˆ์Šค๋Š” ์‹œ๊ฐ„์ด ์ฆ๊ฐ€ํ•˜๋ฉด์„œ ์ ์  ๋ณต์žก๋„๊ฐ€ ์˜ฌ๋ผ๊ฐ€๊ฒŒ ๋˜๊ณ , ๋งŽ์€ ์š”๊ตฌ์‚ฌํ•ญ๋“ค์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ–ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ query ๋Š” ์–ด๋– ํ• ๊นŒ?

 

query ๋Š” ๋‹จ์ˆœ ๋ฐ์ดํ„ฐ ์กฐํšŒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋น„์ฆˆ๋‹ˆ์Šค์™€ ๋ฌด๊ด€ํ•˜์ง€๋งŒ ๊ฐ€๋” query ๋ฅผ ์œ„ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ๋„๋ฉ”์ธ์— ์นจํˆฌํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธด๋‹ค.

 

์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋‚˜๋Š” UI ์˜ ๋ฐ์ดํ„ฐ ๋Œ€๋ถ€๋ถ„์€ ๋น„์ •ํ˜• ๋ฐ์ดํ„ฐ๋“ค์ผ ๊ฒƒ์ด๋‹ค. ์ฆ‰, ์‡ผํ•‘๋ชฐ์—์„œ user ์ •๋ณด์— ๋”ฐ๋ฅธ ๊ด€์‹ฌ ๋ฌผํ’ˆ, ์ตœ๊ทผ ๊ตฌ๋งค ๋‚ด์—ญ, AI ์ถ”์ฒœ ์ƒํ’ˆ ๋ชฉ๋ก ๋“ฑ ์ด๋Ÿฌํ•œ ๋ฐ์ดํ„ฐ๋Š” ํ•˜๋‚˜ ์ด์ƒ์˜ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ์™€ ๊ด€๋ จ์ด ์žˆ๊ฒŒ ๋œ๋‹ค.

 

๊ทธ๋Ÿผ ์ด๋Ÿฐ ๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ์‚ฌํ•ญ์ด ์ƒ๊ธธ๋•Œ๋งˆ๋‹ค ๋„๋ฉ”์ธ์„ ์ˆ˜์ •ํ•ด์•ผ ํ• ๊นŒ? ์ฟผ๋ฆฌ๋ฅผ ๋” ์ž˜ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„๋ฉ”์ธ์— ๊ด€๋ จ ํ–‰์œ„๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋Š”๊ฐ€?

 

์•ˆ ๊ทธ๋ž˜๋„ ๋„๋ฉ”์ธ ์ž์ฒด๋Š” ๋น„๋Œ€ํ•ด์ ธ ๊ฐ€๋Š”๋ฐ, ๋น„์ฆˆ๋‹ˆ์Šค ์ž์ฒด๋ฅผ ํ‘œํ˜„ํ•ด์•ผ ํ•˜๋Š” ๋„๋ฉ”์ธ์— query ๊ฐ€ ์นจํˆฌํ•œ๋‹ค? ์ฆ‰ ๋ณต์žก์„ฑ์ด ์˜ฌ๋ผ๊ฐ„๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

 

๋ฌธ์ œ2. ์„ฑ๋Šฅ

 

๋Œ€๋ถ€๋ถ„์˜ write ์—ฐ์‚ฐ์—์„œ ์šฐ๋ฆฌ๋Š” ์ผ๊ด€์„ฑ (consistency) ์— ๋Œ€ํ•ด์„œ ๋งŽ์€ ์‹ ๊ฒฝ์„ ์จ์•ผํ•œ๋‹ค.

 

๋Œ€๋ถ€๋ถ„์˜ ์ž˜ ์•Œ๋ ค์ง„ consistency ๋ฅผ ์ง€ํ‚ค๊ธฐ ์œ„ํ•œ ํ•ด๋ฒ• ์œผ๋กœ DB Locking ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

 

write ์—ฐ์‚ฐ์—์„œ ํ•œ๋ฒˆ lock ์„ ์žก๊ฒŒ ๋˜๋ฉด ๊ทธ ๋’ค์˜ read ์—ฐ์‚ฐ์ด ๋ชจ๋‘ ๋Œ€๊ธฐ๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉฐ ์ „๋ฐ˜์ ์ธ ์„ฑ๋Šฅ์ด ๋‚ฎ์•„์ง€๋Š” ๊ฒฐ๊ณผ๋ฅผ ์ดˆ๋ž˜ํ•  ์ˆ˜ ์žˆ๋‹ค. (๋ฌผ๋ก  lock ์˜ ๊ธฐ๋ฒ•์— ๋”ฐ๋ผ์„œ ๊ฒฐ๊ณผ๋Š” ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์ง€๋งŒ)

 

๋ฌธ์ œ 3. ํ™•์žฅ์„ฑ

 

๋งŽ์€ ์‹œ์Šคํ…œ์—์„œ ์ฝ๊ธฐ์™€ ์“ฐ๊ธฐ์— ๋Œ€ํ•œ ๋ถˆ๊ท ํ˜•์ด ์กด์žฌํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์€ ๊ฝค๋‚˜ ์ž์ฃผ ๋“ค๋ฆฌ๋Š” ์ด์•ผ๊ธฐ๋‹ค.

 

์“ฐ๊ธฐ ์ž‘์—…๊ณผ ์ฝ๊ธฐ ์ž‘์—…์˜ ๋น„์œจ์ด 1(write):1000(read) ๋ผ๊ณ  ํ•œ๋‹ค.

 

๊ทธ๋ ‡๋‹ค๋Š” ์ด์•ผ๊ธฐ๋Š” read side ์™€ write side ์˜ ์„œ๋ฒ„๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ๊ธฐ์ค€์œผ๋กœ ์„ค๊ณ„๊ฐ€ ๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

 

์ฆ‰, ๋…๋ฆฝ์ ์œผ๋กœ ํ™•์žฅ์ด ๊ฐ€๋Šฅํ•ด์•ผ ํ•˜๊ณ  ๊ฐ๊ฐ ๋ชฉ์ ์— ๋งž๋Š” ๋‹ค๋ฅธ ์†”๋ฃจ์…˜์ด ํ•„์š”ํ•˜๋‹ค๋Š” ์ด์•ผ๊ธฐ๋‹ค.

 

๋งŒ์•ฝ ์ด ๋‘˜์ด ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์ง€ ์•Š๊ณ  ํ•˜๋‚˜์˜ ์ปดํ“จํŒ… ์—”์ง„๋งŒ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ํ˜น์€ ํ•˜๋‚˜์˜ ๋ฐ์ดํ„ฐ์†Œ์Šค๋งŒ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋…๋ฆฝ์ ์ธ ํ™•์žฅ์ด ํž˜๋“ค ๊ฒƒ์ด๋‹ค.

 


 

ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ CQRS ๋ฅผ ์ ์šฉํ•ด์„œ ์ฑ…์ž„์— ๋”ฐ๋ฅธ Command ์™€ Query ์˜ ์—ฐ์‚ฐ์„ ๊ฐ๊ฐ ๋…๋ฆฝ์ ์œผ๋กœ ๋ถ„๋ฆฌ์‹œํ‚ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋ฅผ ๋„๊ฒŒ ๋œ๋‹ค.

 

 

์•ž์„œ ๋ณด์•˜๋˜ ์ผ๋ฐ˜์ ์ธ CQRS ์˜ ํ˜•ํƒœ์™€ ๋น„์Šทํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

 

๊ทธ๋ ‡๋‹ค๋ฉด ์ •๋‹ต๊ณผ ๊ด€๋ จ๋œ ๋น„์ฆˆ๋‹ˆ์Šค๋ฅผ ์ฑ…์ž„์ง€๋Š” ์œ—์ชฝ ๋„๋ฉ”์ธ์—๊ฒŒ๋Š” ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚ค๋Š” Command ์˜ ์ฑ…์ž„๋งŒ ์กด์žฌํ•˜๊ธฐ์— ๋น„์ฆˆ๋‹ˆ์Šค๋ฅผ ๊ทธ๋Œ€๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์—ญ์‹œ ์•„๋ž˜์˜ ๋„๋ฉ”์ธ์—๊ฒŒ๋Š” ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๋Š” Query ์˜ ์ฑ…์ž„ ๋งŒ ์กด์žฌํ•˜๊ฒŒ ๋œ๋‹ค.

 

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์–ด๋–ค ์žฅ์ ์ด ์žˆ์„ ์ˆ˜ ์žˆ์„๊นŒ?

 

๋‹จ์ˆœํžˆ ๊ฐ€์žฅ ๋จผ์ € ๋“œ๋Š” ์ƒ๊ฐ์€ Command ์™€ Query ์— ๊ฐ๊ธฐ ๋‹ค๋ฅธ Persistence Module ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊ฒƒ์ด๋‹ค.

 

  • Command Side ์—๋Š” ๊ฐ์ฒด ์ค‘์‹ฌ์ ์ธ ๊ฐœ๋ฐœ์ด ๊ฐ€๋Šฅํ•œ JPA ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • Query Side ์—๋Š” ์ตœ์ ํ™”๋œ ์ฟผ๋ฆฌ๋ฅผ ์œ„ํ•ด์„œ MyBatis ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

 

CQRS ๋” ๊ณ ๋„ํ™” ์‹œ์ผœ๋ณผ ์ˆ˜ ์žˆ๋‹ค

 

Command ์™€ Query ์˜ ์ฑ…์ž„์ด ๋ถ„๋ฆฌ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— Command ์™€ Query ๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ์ธํ”„๋ผ๊ฐ€ ๊ตฌ์„ฑ๋  ์ˆ˜ ์žˆ๋‹ค.

 

 

๊ทธ๋Ÿผ ์œ„์™€ ๊ฐ™์ด Polygrat ํ•œ Persistance Infra ๊ฐ€ ๊ตฌ์„ฑ๋  ์ˆ˜ ์žˆ๋‹ค.

 

๊ทธ๋Ÿผ ๋˜ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

 

  • Command infra ์—๋Š” write ์— ์ตœ์ ํ™”๋œ DB๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.
  • Query Side ์—๋Š” ๋”์šฑ ๋น ๋ฅธ ์ฟผ๋ฆฌ์„ ์œ„ํ•ด์„œ elasticsearch๋‚˜ opensearch ์™€ ๊ฐ™์€ ๊ฒ€์ƒ‰ ์—”์ง„์„ ๋„์ž…ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

 

๊ทธ๋ž˜์„œ ๋ณดํ†ต Query Side ์— Materialized View ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ๊ด€์ ์— ๋”ฐ๋ฅธ ์ •๋ณด ๋ทฐ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณค ํ•œ๋‹ค

 

๊ทธ๋ฆฌ๊ณ  Write Side ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ๋“ค์— ๋Œ€ํ•ด์„œ๋Š” ์ค‘๊ฐ„์— ๋ฉ”์‹œ์ง• ์ธํ”„๋ผ๋ฅผ ์ด์šฉํ•ด์„œ ๊ณ„์†ํ•ด์„œ ๋™๊ธฐํ™”๋ฅผ ์‹œ์ผœ์ฃผ๋Š” ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•œ๋‹ค.

 

์กฐ๊ธˆ ๋” ๊ณ ๋„ํ™”๋œ CQRS ํŒจํ„ด ๊ตฌํ˜„๋ฒ• ๋Œ€ํ•œ ์ž์„ธํ•œ ์ด์•ผ๊ธฐ๋Š” CQRS Journey guide ํ•œ๊ธ€ ๋ฒˆ์—ญ ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

CQRS ์˜ ์žฅ์ ๊ณผ ๋‹จ์ 

 

์œ„์—์„œ ์šฐ๋ฆฌ๋Š” CQRS ์— ๋Œ€ํ•ด์„œ ๋Œ€๋žต์ ์œผ๋กœ ์•Œ์•„๋ณด์•˜๊ณ , level ๋ณ„๋กœ CQRS ๋ฅผ ๊ตฌ๋ถ„ํ•ด๋ณด์•˜๋‹ค.

 

CQRS ๋ผ๋Š” ๊ฒƒ์€ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋”ฐ๋ผ์„œ ๋ณต์žก์„ฑ์ด ์ฒœ์ฐจ๋งŒ๋ณ„์ด๋‹ค. ๊ผญ read side ์™€ write side ๋ฅผ ๋ฉ”์‹œ์ง• ์ธํ”„๋ผ๋กœ ์—ฐ๊ฒฐํ•˜์ง€ ์•Š๋”๋ผ๋„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋“ฏ ์‚ฌ์šฉํ•˜๋Š” ๋ชฉ์ ๊ณผ ์šฉ๋ก€๊ฐ€ ๋‹ค๋ฅด๋‹ค.

 

์ด์ œ CQRS ์— ๋Œ€ํ•œ ์žฅ๋‹จ์ ์„ ํ•œ๋ฒˆ ์ƒ๊ฐํ•ด๋ณด์ž

 

์žฅ์ 

  • ๋„๋ฉ”์ธ ๋กœ์ง์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค
    • Command ์™€ Query ๋ฅผ ๋ถ„๋ฆฌํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— OCP ๋ฅผ ์ค€์ˆ˜ํ•˜๋Š” ๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
    • ์ด๋ฅผ ํ†ตํ•ด ๊ฒฐ๊ตญ ๋„๋ฉ”์ธ ๋กœ์ง์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ง‘์ค‘์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค
  • ๋ฐ์ดํ„ฐ์†Œ์Šค์˜ ๋…๋ฆฝ์ ์ธ ํฌ๊ธฐ ์กฐ์ •์ด ๊ฐ€๋Šฅํ•˜๋‹ค
    • ๋ณดํ†ต read ์™€ write ์˜ ๋น„์œจ์€ 1000 : 1 ์ด๋‹ค.
    • ๊ทธ๋Ÿฌ๋ฏ€๋กœ write db ๊ฐ€ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๋‚˜๋‰˜์–ด์ ธ ์žˆ๋‹ค๋ฉด ํ•ด๋‹น db ์ธ์Šคํ„ด์Šค๋Š” ์ž‘๊ฒŒ ์œ ์ง€ํ•˜๊ณ  read db ์ธ์Šคํ„ด์Šค์— ๋” ๋†’์€ ํˆฌ์ž๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋‹จ์ˆœํ•œ ์ฟผ๋ฆฌ
    • Query side ์—์„œ๋Š” Materialized View ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋ฅผ ํ†ตํ•ด์„œ ๋ณต์žกํ•œ ์กฐ์ธ ์ฟผ๋ฆฌ ์—†์ด ๋‹จ์ˆœํ•œ ์ฟผ๋ฆฌ๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์ •๋ณด๋ฅผ ์–ป์–ด์˜ฌ ์ˆ˜ ์žˆ๋‹ค

 

๋‹จ์ 

  • ๋ณต์žก์„ฑ์ด ์˜ฌ๋ผ๊ฐ„๋‹ค
    • command side ์™€ query side ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋ถ„๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ณต์žก์„ฑ์ด ์˜ฌ๋ผ๊ฐ„๋‹ค.
  • ์ฆ‰์‹œ์ ์ธ ์ผ๊ด€์„ฑ์ด ๋ณด์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค
    • command ์— ๋”ฐ๋ฅธ data ์˜ ๋ฌด๊ฒฐ์„ฑ์ด ์ž ์‹œ๋™์•ˆ ๊นจ์งˆ ์ˆ˜ ์žˆ๋‹ค.
    • ์ด ๋ง์€ ๋ฐ์ดํ„ฐ์˜ consistency ๊ฐ€ ํ•ญ์ƒ ๋™์ผํ•˜์ง€ ์•Š๋‹ค
    • ํ•˜์ง€๋งŒ ์ตœ์ข…์ ์œผ๋กœ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋งž์ถฐ์งˆ ๊ฒƒ์ด๋‹ˆ Eventual Consistency๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

CQRS ํŒจํ„ด์˜ ์žฅ๋‹จ์ ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ด์•ผ๊ธฐ๋Š” CQRS Journey guide ํ•œ๊ธ€ ๋ฒˆ์—ญ ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ด๋Ÿฐ ์žฅ๋‹จ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋„๋ฉ”์ธ ๋˜๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์ด ๋‹จ์ˆœํ•œ ๊ณณ์—์„œ๋Š” CQRS ๋ฅผ ํ•˜๊ธฐ ํž˜๋“ค๋‹ค.

 

ํ•˜์ง€๋งŒ ๋งŽ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ๋™์ผํ•œ ๋ฐ์ดํ„ฐ์— ๋ณ‘๋ ฌ๋กœ ์ ‘๊ทผํ•˜๋Š” ๊ฒฝ์šฐ๋‚˜ read ์—ฐ์‚ฐ์ด write ์—ฐ์‚ฐ๋ณด๋‹ค ๋งŽ์€ ๊ฒฝ์šฐ๋Š” CQRS ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํšจ๊ณผ์ ์ด๋‹ค.

 

๋งˆ์น˜๋ฉฐ

 

์œ„์—์„œ ๋ณด์•˜๋“ฏ CQRS ์ž์ฒด๋Š” ๋˜๊ฒŒ ๊ฐ„๋‹จํ•˜์ง€๋งŒ ๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ ๋ฌธ์„œ๋ฅผ ํฌํ•จํ•˜์—ฌ ์—ฌ๋Ÿฌ ๊ณณ์—์„œ CQRS ๋ฅผ ์„ค๋ช…ํ•  ๋•Œ, ์˜คํ•ด์— ์—ฌ์ง€๊ฐ€ ์žˆ๊ฒŒ ์„ค๋ช…์„ ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

 

CQRS ๋Š” ์‚ฌ์‹ค ๋ฐ์ดํ„ฐ์†Œ์Šค์™€ ํฌ๊ฒŒ ์—ฐ๊ด€์ด ์žˆ์ง€ ์•Š๋‹ค.

 

๋ง ๊ทธ๋Œ€๋กœ CQRS ๋Š” Command ์™€ Query ๋ฅผ ๋ถ„๋ฆฌ์‹œํ‚ค๋Š” ๊ฒƒ์ด๋‹ค.

 

ํ•˜์ง€๋งŒ ์—ฌ๋Ÿฌ ๊ณณ์—์„œ CQRS ์™€ Datasource ๋ฅผ ์—ฎ์–ด์„œ ํ˜น์€ Event sourcing ๊ณผ ์—ฎ์–ด์„œ ์„ค๋ช…์„ ํ•˜๊ณ  ์žˆ์—ˆ๊ธฐ์— ์ดˆ๋ฐ˜์— ๋งŽ์€ ์˜คํ•ด๋ฅผ ํ–ˆ์—ˆ๋‹ค..

 

์˜คํ•ด๋ฅผ ํ’€์–ด์„œ ์‹ ๋‚œ ๊ธฐ๋…์œผ๋กœ ์‚ฌ๋‚ด์— ๊ณต์œ 

 

๊ทธ๋ž˜์„œ ์ด ๊ธ€๋กœ ์ตœ๋Œ€ํ•œ CQRS ๋ฅผ Datasource ์™€ ์—ฎ์—ฌ์žˆ๋‹ค๋Š” ์˜คํ•ด๋ฅผ ํ’€๊ณ ์‹ถ์—ˆ๊ณ , ๋‚˜์™€ ๊ฐ™์ด ์˜คํ•ด๋ฅผ ํ–ˆ๋˜ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์ด ๊ธ€์„ ๋ฐ”์น˜๊ณ ์‹ถ๋‹ค

๋Œ“๊ธ€