[Daily morning study] CQRS(Command Query Responsibility Segregation) ํจํด
#daily morning study
CQRS๋?
CQRS(Command Query Responsibility Segregation)๋ ๋ช ๋ น(Command) ๊ณผ ์กฐํ(Query) ์ ์ฑ ์์ ๋ถ๋ฆฌํ๋ ์ํคํ ์ฒ ํจํด์ด๋ค.
์ ํต์ ์ธ CRUD ๋ชจ๋ธ์์๋ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ ์ฐ๋ ๋ชจ๋ธ์ด ํ๋๋ค. CQRS๋ ์ด๋ฅผ ๋๋ก ๋๋๋ค.
- Command: ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ๋ ์์ (์์ฑ, ์์ , ์ญ์ ). ๋ฐํ๊ฐ ์์(๋๋ ID๋ง ๋ฐํ).
- Query: ๋ฐ์ดํฐ๋ฅผ ์ฝ๋ ์์ . ์ฌ์ด๋ ์ดํํธ ์์.
์ด ์์ด๋์ด๋ Bertrand Meyer์ CQS(Command Query Separation) ์์น์์ ์ถ๋ฐํ๊ณ , Greg Young์ด ์ด๋ฅผ ์ํคํ ์ฒ ์์ค์ผ๋ก ๋ฐ์ ์์ผ CQRS๋ผ๊ณ ๋ช ๋ช ํ๋ค.
์ ๋ถ๋ฆฌํ๋๊ฐ?
์ฝ๊ธฐ์ ์ฐ๊ธฐ์ ํน์ฑ์ด ๋ค๋ฅด๋ค
| ํน์ฑ | Command (์ฐ๊ธฐ) | Query (์ฝ๊ธฐ) |
|---|---|---|
| ๋น๋ | ์๋์ ์ผ๋ก ๋ฎ์ | ์๋์ ์ผ๋ก ๋์ |
| ๋ณต์ก๋ | ๋น์ฆ๋์ค ๊ท์น, ์ ํจ์ฑ ๊ฒ์ฌ | ๋ณต์กํ ์กฐ์ธ, ์ง๊ณ |
| ํ์ฅ ๋ฐฉ์ | ํธ๋์ญ์ ์ฒ๋ฆฌ ์ต์ ํ | ์ฝ๊ธฐ ์ฑ๋ฅ ์ต์ ํ |
| ๋ชจ๋ธ ํํ | ๋๋ฉ์ธ ์ค์ฌ | ํ๋ฉด/๋ณด๊ณ ์ ์ค์ฌ |
์ฝ๊ธฐ ํธ๋ํฝ์ด ์ฐ๊ธฐ๋ณด๋ค 10๋ฐฐ, 100๋ฐฐ ๋ง์ ๊ฒฝ์ฐ๊ฐ ํํ๋ค. ๊ฐ์ ๋ชจ๋ธ์ ๊ณต์ ํ๋ฉด ๊ฐ๊ฐ์ ๋ง๋ ์ต์ ํ๊ฐ ์ด๋ ต๋ค.
๊ธฐ๋ณธ ๊ตฌ์กฐ
Client
โ
โโ Command โ Command Handler โ Write Model (DB)
โ
โโ Query โ Query Handler โ Read Model (DB or View)
Command ์ชฝ
- Command ๊ฐ์ฒด: ์๋๋ฅผ ๋ด์ DTO (
CreateOrderCommand,CancelOrderCommand) - Command Handler: ๋๋ฉ์ธ ๋ชจ๋ธ์ ํตํด ๋น์ฆ๋์ค ๋ก์ง ์ฒ๋ฆฌ ํ Write DB์ ๋ฐ์
- ๋๋ฉ์ธ ์ด๋ฒคํธ๋ฅผ ๋ฐํํด์ Read Model์ ์ ๋ฐ์ดํธํ๋ ๊ฒฝ์ฐ๋ ๋ง์
Query ์ชฝ
- Query ๊ฐ์ฒด: ํ์ํ ๋ฐ์ดํฐ์ ํํฐ ์กฐ๊ฑด (
GetOrdersByUserQuery) - Query Handler: Read DB์์ ์ง์ DTO๋ฅผ ์กฐํํด ๋ฐํ
- ORM์ ๋ณต์กํ ๋๋ฉ์ธ ๋ชจ๋ธ ์์ด raw SQL์ด๋ ๊ฐ๋จํ ์ฟผ๋ฆฌ๋ก ์ฒ๋ฆฌ ๊ฐ๋ฅ
์ฝ๋ ์์ (Node.js / TypeScript)
Command
// command
class CreateOrderCommand {
constructor(
public readonly userId: string,
public readonly items: OrderItem[],
) {}
}
// handler
class CreateOrderHandler {
async execute(command: CreateOrderCommand): Promise<string> {
const order = Order.create(command.userId, command.items);
await this.orderRepository.save(order);
await this.eventBus.publish(new OrderCreatedEvent(order.id));
return order.id;
}
}
Query
// query
class GetUserOrdersQuery {
constructor(public readonly userId: string) {}
}
// handler โ Read DB์์ ๋ฐ๋ก flat DTO ์กฐํ
class GetUserOrdersHandler {
async execute(query: GetUserOrdersQuery): Promise<OrderSummaryDto[]> {
return this.db.query(
`SELECT id, status, total_price, created_at
FROM orders_view
WHERE user_id = $1
ORDER BY created_at DESC`,
[query.userId],
);
}
}
Query ํธ๋ค๋ฌ๋ ๋๋ฉ์ธ ๋ชจ๋ธ(Order ํด๋์ค)์ ์ ํ ๊ฑฐ์น์ง ์๋๋ค. ํ๋ฉด์ ํ์ํ ํํ ๊ทธ๋๋ก ๊ฐ์ ธ์จ๋ค.
Read Model ๋๊ธฐํ ๋ฐฉ๋ฒ
Write DB์ Read DB๋ฅผ ๋ถ๋ฆฌํ ๋ ๋ ๋ชจ๋ธ์ ์ด๋ป๊ฒ ๋๊ธฐํํ๋๋๊ฐ ํต์ฌ ๊ณผ์ ๋ค.
1. ๋๊ธฐ ์ ๋ฐ์ดํธ
Command๊ฐ ์ฒ๋ฆฌ๋ ์งํ ๊ฐ์ ํธ๋์ญ์ ๋๋ ์งํ ํธ์ถ๋ก Read Model์ ์ ๋ฐ์ดํธ.
- ์ฅ์ : ๊ตฌํ์ด ๋จ์, ์ผ๊ด์ฑ ๋ณด์ฅ
- ๋จ์ : Command ์ฒ๋ฆฌ ์ฑ๋ฅ์ ์ํฅ
2. ์ด๋ฒคํธ ๊ธฐ๋ฐ ๋น๋๊ธฐ ์ ๋ฐ์ดํธ
๋๋ฉ์ธ ์ด๋ฒคํธ๋ฅผ ๋ฐํํ๊ณ , ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ Read Model์ ๋ณ๋๋ก ๊ฐฑ์ .
Order ์ ์ฅ
โโ OrderCreatedEvent ๋ฐํ
โโ (๋น๋๊ธฐ) OrderReadModelUpdater โ Read DB ๊ฐฑ์
- ์ฅ์ : Command ์ฑ๋ฅ์ ์ํฅ ์์, Read/Write DB๋ฅผ ์์ ํ ๋ค๋ฅธ ์ ์ฅ์(์: MySQL โ Elasticsearch)๋ก ๋ถ๋ฆฌ ๊ฐ๋ฅ
- ๋จ์ : ์ต์ข ์ผ๊ด์ฑ(Eventual Consistency) โ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ ๊น์ง Read Model์ด ์ค๋๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ ์ ์์
3. Event Sourcing๊ณผ ๊ฒฐํฉ
Write Model์ ํ์ฌ ์ํ ๋์ ์ด๋ฒคํธ ๋ก๊ทธ๋ง ์ ์ฅ. Read Model์ ์ด๋ฒคํธ๋ฅผ ์ฌ์(replay)ํด์ ๋ทฐ๋ฅผ ๋ง๋ ๋ค.
CQRS์ Event Sourcing์ ๋ ๋ฆฝ์ ์ธ ํจํด์ด์ง๋ง ํจ๊ป ์์ฃผ ์ฌ์ฉ๋๋ค.
์ธ์ ๋์ ํ๋ฉด ์ข์๊ฐ
CQRS๊ฐ ์ ํฉํ ๊ฒฝ์ฐ:
- ์ฝ๊ธฐ/์ฐ๊ธฐ ํธ๋ํฝ ๋ถ๊ท ํ์ด ์ฌํ ์๋น์ค
- ์กฐํ ์๊ตฌ์ฌํญ์ด ๋ณต์กํ๊ณ ๋ค์ํ ๊ฒฝ์ฐ (๋์๋ณด๋, ๊ฒ์, ๋ฆฌํฌํธ)
- MSA์์ ์๋น์ค๋ณ๋ก ์ฝ๊ธฐ ์ ์ฉ ๋ชจ๋ธ์ด ํ์ํ ๊ฒฝ์ฐ
- Event Sourcing ๋์ ์ ๊ณ ๋ ค ์ค์ธ ๊ฒฝ์ฐ
CQRS๊ฐ ๊ณผํ ๊ฒฝ์ฐ:
- ๋จ์ CRUD ์ ํ๋ฆฌ์ผ์ด์
- ํ ๊ท๋ชจ๊ฐ ์๊ณ ๋ณต์ก๋๋ฅผ ๊ฐ๋นํ๊ธฐ ์ด๋ ค์ด ๊ฒฝ์ฐ
- ๊ฐํ ์ผ๊ด์ฑ์ด ๋ฐ๋์ ํ์ํ ๊ฒฝ์ฐ (์ต์ข ์ผ๊ด์ฑ์ ํ์ฉํ๊ธฐ ์ด๋ ค์ธ ๋)
์ฅ๋จ์ ์ ๋ฆฌ
์ฅ์
- ์ฝ๊ธฐ ์ฑ๋ฅ ์ต์ ํ: Read Model์ ์กฐํ์ ํนํ๋ ํํ๋ก ์ค๊ณ ๊ฐ๋ฅ
- ๋๋ฉ์ธ ๋ชจ๋ธ ๋จ์ํ: Command ์ชฝ ๋๋ฉ์ธ ๋ชจ๋ธ์ด ์กฐํ ์๊ฑด์ ์ค์ผ๋์ง ์์
- ๋ ๋ฆฝ์ ํ์ฅ: Read/Write๋ฅผ ๋ณ๋ ์ธํ๋ผ๋ก ์ค์ผ์ผ ์์ ๊ฐ๋ฅ
- ๋ช
ํํ ์๋ ํํ: Command ์ด๋ฆ์ด ๋น์ฆ๋์ค ์ธ์ด๋ฅผ ์ง์ ํํ (
PlaceOrder,CancelReservation)
๋จ์
- ๋ณต์ก๋ ์ฆ๊ฐ: ํด๋์ค, ํธ๋ค๋ฌ, ์ด๋ฒคํธ ์๊ฐ ๋์ด๋จ
- ์ต์ข ์ผ๊ด์ฑ: ๋น๋๊ธฐ ๋๊ธฐํ ์ Read Model ์ง์ฐ ๋ฐ์ ๊ฐ๋ฅ
- ์ด์ ๋ณต์ก๋: Write/Read DB๊ฐ ๋ถ๋ฆฌ๋๋ฉด ๊ด๋ฆฌ ํฌ์ธํธ ์ฆ๊ฐ
- ํ์ต ๋น์ฉ: ํ ์ ์ฒด๊ฐ ํจํด์ ์ดํดํด์ผ ํจ๊ณผ์ ์ผ๋ก ๋์
์์ฝ
CQRS๋ ์ฝ๊ธฐ์ ์ฐ๊ธฐ์ ๊ด์ฌ์ฌ๋ฅผ ๋ถ๋ฆฌํจ์ผ๋ก์จ ๊ฐ๊ฐ์ ๋ง๋ ์ต์ ํ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ ํจํด์ด๋ค. ๋จ์ํ ์ ํ๋ฆฌ์ผ์ด์ ์๋ ์ค๋ฒ์์ง๋์ด๋ง์ด์ง๋ง, ๋ณต์กํ ๋๋ฉ์ธ ๋ก์ง๊ณผ ๋ค์ํ ์กฐํ ์๊ฑด์ด ์ถฉ๋ํ๋ ์์คํ ์์๋ ์ ์ง๋ณด์์ฑ๊ณผ ์ฑ๋ฅ ๋ชจ๋๋ฅผ ๊ฐ์ ํ ์ ์๋ค.