명령과 조회 책임 분리
전통적인 CRUD 기반 애플리케이션에서는 같은 모델로 데이터를 읽기(Query)도 하고, 쓰기(Command)도 수행한다.
CQRS는 이 두가지를 명확히 분리하는 아키텍처 패턴.
복잡한 도메인에서 유용함
Command : 데이터를 변경하는 요청(CUD)
Query : 데이터를 조회하는 요청(R)
Java/Spring 기반
src
├── command
│ ├── controller
│ ├── service
│ └── domain
├── query
│ ├── controller
│ ├── service
│ └── dto
// Command: 사용자 생성
@RestController
@RequestMapping("/users")
public class UserCommandController {
@PostMapping
public ResponseEntity<Void> createUser(@RequestBody CreateUserRequest request) {
userCommandService.createUser(request);
return ResponseEntity.ok().build();
}
}
// Query: 사용자 조회
@RestController
@RequestMapping("/users")
public class UserQueryController {
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userQueryService.getUserById(id));
}
}
DDD: 복잡한 도메인을 도메인 중심으로 설계하는 방법론
CQRS: 명령과 조회를 역할에 따라 분리하는 아키텍처 패턴
src
├── api
│ ├── UserCommandController.java
│ └── UserQueryController.java
├── application
│ ├── command
│ │ ├── CreateUserCommand.java
│ │ └── CreateUserHandler.java
│ └── query
│ └── GetUsersHandler.java
├── domain
│ └── user
│ ├── User.java
│ ├── UserId.java
│ ├── UserNickname.java
│ ├── UserRepository.java
├── infrastructure
│ └── jpa
│ └── UserJpaRepository.java
│ └── UserReadModelRepository.java
└── querymodel
└── UserReadModel.java
Command(쓰기)
Query(읽기)
플로우
| 장점 | 내용 |
|---|---|
| 책임 분리 | Command는 비즈니스 규칙 중심, Query는 성능 중심으로 설계 |
| 확장성 | 쓰기/읽기 사이드를 독립적으로 확장하거나 배포 가능 |
| 성능 최적화 | 읽기 전용 DB나 캐시 사용으로 조회 성능 향상 |
| 도메인 모델 명확화 | Command 영역에서는 풍부한 DDD 모델을 유지 가능 |
CQRS는 역할 분리를 명확히 하여 DDD를 더욱 구조적으로 구현할 수 있게 해준다.
DDD는 CQRS의 Command 영역에 깊이 있는 도메인 로직을 표현하는데 적합하다.