
CQRS(Command Query Responsibility Segregation)는 소프트웨어 아키텍처 스타일 중 하나로, 시스템의 명령(Command)과 조회(Query)를 분리하여 각자의 책임을 명확히 정의합니다. 이는 대규모 애플리케이션에서 복잡성을 줄이고 성능을 최적화하기 위한 강력한 설계 방식으로, 다음과 같은 특징이 있습니다:
CQRS는 명령과 조회를 물리적으로 분리함으로써 성능 병목을 최소화하고, 데이터 모델을 각 작업에 최적화할 수 있는 유연성을 제공합니다.
@Transactional(readOnly = true) 어노테이션은 JPA와 같은 ORM 도구를 사용할 때 읽기 전용 작업의 성능을 최적화하는 데 중요한 역할을 합니다. 이 설정은 다음과 같은 이유로 기본값으로 사용됩니다:
readOnly 설정이 응답 시간을 단축시키는 데 효과적입니다.쓰기 작업은 데이터베이스 상태를 변경해야 하므로, 별도의 @Transactional 어노테이션이 필요합니다. 이는 다음과 같은 이유에서 중요합니다:
다음은 CQRS 원칙을 준수하며 서비스 레이어에서 읽기 및 쓰기 작업을 관리하는 예제 코드입니다:
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class ParkingService {
private final ParkingRepository parkingRepository;
private final ParkingTicketFactory parkingTicketFactory;
// 읽기 작업: readOnly 트랜잭션 적용
public List<ParkingTicketResponse> getActiveParkingTickets() {
List<ParkingTicket> tickets = parkingRepository.findAllByStatus(ParkingStatus.ACTIVE);
return tickets.stream().map(ParkingTicketResponse::of)
.collect(Collectors.toList());
}
// 쓰기 작업: 기본 트랜잭션(readOnly=false)
@Transactional
public ParkingTicketResponse createParkingTicket(ParkingCreateRequest request) {
String nextTicketNumber = parkingTicketFactory.createNextTicketNumber();
ParkingTicket ticket = request.toEntity(nextTicketNumber);
ParkingTicket savedTicket = parkingRepository.save(ticket);
return ParkingTicketResponse.builder()
.id(savedTicket.getId())
.ticketNumber(nextTicketNumber)
.status(savedTicket.getStatus())
.licensePlate(savedTicket.getLicensePlate())
.entryTime(savedTicket.getEntryTime())
.build();
}
}
서비스 레이어에서 @Transactional(readOnly = true)를 기본값으로 설정하고 쓰기 작업에만 명시적으로 트랜잭션을 추가하는 접근 방식은 다음과 같은 이점을 제공합니다:
CQRS는 명령과 조회를 분리하여 성능과 유지보수성을 동시에 향상시키는 강력한 설계 패턴입니다. 서비스 레이어에서 읽기 작업을 위한 @Transactional(readOnly = true)를 기본값으로 설정하고, 쓰기 작업에 별도의 트랜잭션을 명시적으로 정의하는 것은 CQRS 원칙을 실천하는 접근 방식입니다.
이 방식을 적용하면 성능 최적화, 데이터 안정성 보장, 확장성 강화 등의 효과를 얻을 수 있으며, 대규모 트래픽을 처리하는 시스템에서 더욱 강력한 아키텍처를 구축할 수 있습니다.
📌 이 글은 TDD 강의를 학습한 내용을 바탕으로 재구성하였습니다. 문제가 되는 부분이 있다면 수정하겠습니다.