영한갓님 JPA 실전 강의 들었는 데 CQS, CQRS 안다, 모른다?

2023. 9. 11. 22:38Java

728x90

 

Hail YoungHan

 

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 - 인프런 | 강의

스프링 부트와 JPA를 활용해서 API를 개발합니다. 그리고 JPA 극한의 성능 최적화 방법을 학습할 수 있습니다., 스프링 부트, 실무에서 잘 쓰고 싶다면? 복잡한 문제까지 해결하는 힘을 길러보세요

www.inflearn.com

영한갓님의 강의에서 회원의 정보를 수정하기 위해

MemberService에 update 메서드를 추가하던 와중이었습니다.

"update 메서드는 엔티티를 바꾸겠다는 변경성 메서드인데,
이 메서드의 return type 을 Member로 한다면 update로 조회를 하는 꼴이 돼요.

command와 query 가 같이 있는 꼴이 되죠.
그래서 저는 update 메서드의 return type을 void로 하거나 id 값 정도로 해줘요."

-갓영한님의 말씀-

영한님이 실무를 하시면서 되게 신경쓰는 규칙이라고 느껴지기도 하고,

뭔가 일종의 디자인 패턴인가 싶기도 하고,

대충 문맥은 알겠는 데 저 문장에서 command 와 query라는 단어가 정확히 무슨 뜻이지?

 

라는 각종 의문이 떠올랐습니다!

 

혹시나 하고 인프런의 질의응답 게시판을 찾아봤더니,

다른 분도 비슷한 의문을 품으시고, 영한님이 이에 대해서 답을 해주셨습니다 :)

영한갓님의 친절한 설명

위 답변을 정리한다면

CQS (Command Query Separation), 즉 앱 개발시에 
엄격한 기능 구분을 통해 불필요한 side effect 를 줄이도록 한다!

라고 정리를 할 수 있겠네요!

 

여전히 근데 Command & Query 가 무슨 뜻인지,
CQS는 또 뭐고 어디서 왔는지 

 

 

라는 의문이 풀리지 않아 추가적으로 찾아보게 되었습니다!

CQS 란?

여러 글을 찾아봤는 데, 누군가는 이를 디자인 패턴이라고도 하고

또 누군가는 이를 설계 원칙이라고도 합니다.

일단 CQS 라는 용어를 만드신 분이 정의한 뜻부터 볼까요?

Bertrand Meyer

[원문]
Applying a strict separation between commands and queries by prohibiting abstract side effects in functions is particularly appropriate for the development of large systems, where the key to success is to exert full control on every inter-module interaction.
...
The principle enjoins us not to mix commands (procedures), which change objects, and queries (functions and attributes), which return information about objects but do not change them. This precludes side-effect-producing functions.

[번역]
함수에서 추상적인 부작용을 금지함으로써 명령과 쿼리 사이에 엄격한 구분을 적용하는 것은
모듈 간 상호 작용을 완전히 제어하는 것이 성공의 열쇠인 대규모 시스템을 개발할 때 특히 적절하다.
...
이 원칙은 객체를 변경하는 명령(절차)과
객체에 대한 정보를 반환하지만 변경하지 않는 쿼리(함수와 속성)를 섞지 말라고 권고한다.
이것은 부작용을 유발하는 함수를 배제한다.

참고 : Object-Oriented Software Construction, Bertrand Meyer

즉, 이 원문을 통해 아까의 의문에 대답이 가능해요.

  1. Command & Query 란?
    Command는 객체를 바꾸는 쓰기에 해당하고, Query는 객체의 정보만을 반환하는 함수
  2. CQS 란?
    Command 와 Query의 분리를 통해 대규모 시스템시 유리한 원칙!

즉 CQS 는 특정 문제를 해결하기 위한 설루션인 디자인 패턴보다는

범용적인 원칙? 이라고 볼 수 있는 설계 원칙이라고 하는 게 맞다고 생각해요 :)

 

그래서 영한님도 위에서 update 함수의 return type을 void 나 id(int)로

한다고 생각합니다!

그렇다면 CQRS는?

저도 그랬고, 많은 사람들이 CQS와 CQRS를 되게 헷갈려해서

이왕 찾아보는 김에 CQRS까지 찾아봤어요 :)

 

이번에도, CQRS는 대체 누가 정의했나 싶어 찾아보니

Greg Young이라는 분이 정의하셨습니다 :)

Greg Young

[원문]
Command and Query Responsibility Segregation uses the same definition of Commands and Queries that Meyer used and maintains the viewpoint that they should be pure. The fundamental difference is that in CQRS objects are split into two objects, one containing the Commands one containing the Queries.

[번역]
CQRS에서는 Meyer가 사용한 것과 동일한 명령 및 쿼리 정의를 사용하며 순수해야 한다는 관점을 유지합니다.
근본적인 차이점은 CQRS에서는 객체가 두 개의 객체로 분할되는데,
하나는 명령이 포함된 객체이고 다른 하나는 쿼리가 포함된 객체입니다.

[출처]
https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf, page 17
즉 CQS의 컨셉을 가져오되, 객체를 2가지로 분리하는 디자인 패턴이다!

 

위처럼 정리를 할 수 있겠죠!

근데 대체 무슨 객체를 2 가지로 분리한다는 말일까요?

개발자라면 코드가 더 보기 편하죠?(ㄹㅇㅋㅋ)

@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;
    
    public Long join(Member member){...} // C
    
    public List<Member> findMembers() {...} // R
    
    public void update(Long id, String name) {...} // U
    
    public id delete(Long id) {...} // D
    
	...
}

위처럼 MemberService 가 있다고 가정해 볼게요.

 

CQRS 패턴은 객체를

1. command 끼리 모아둔 객체 하나,

2. query 끼리 모아둔 객체 하나,

이렇게 총 2개의 객체로 나눠 관심사의 분리를 이루는 패턴이에요.

@Service
@Transactional
@RequiredArgsConstructor
public class MemberCommandService {

    private final MemberRepository memberRepository;

    public Long join(Member member) {
        // 로직
        return ...;
    }

    public void update(Long id, String name) {
        // 로직
    }

    public id delete(Long id) {
        // 로직
        return ...;
    }

    // 기타 상태를 변경하는 메서드들
}
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberQueryService {

    private final MemberRepository memberRepository;

    public List<Member> findMembers() {
        // 로직
        return ...;
    }

    // 기타 조회 로직 메서드들
}

이렇게 서비스를 분리하면 각각의 서비스가 하나의 책임만을 가지게 되어 코드의 유지보수성이 향상됩니다.

또한, 성능 최적화 확장성 측면에서도 이점이 있습니다.

예를 들어, MemberQueryService 캐싱 등의 기술을 적용하기 쉽습니다.

 

무조건 저거 적용해야 해?

하지만 스프링이나 다른 프레임워크를 공부하신 분들이라면

이렇게 분리를 한 경우를 많이 못 보셨을 거예요.

보통은 CRUD 메서드를 전부 하나에 다 넣으니까요.

 

CQRS는 위에서도 말했듯 디자인 패턴이에요.

즉, 특정 문제가 생겼을 시에 적용하는 방법인 거죠!

 

그러니 알고만 있다가, 추후 관심사의 분리 및 읽기 성능 향상등의

문제를 해결해야 할 때 적용하면 좋은 패턴임을 알고 있으면 좋다! 

가 결론입니다 :)

정리

오늘은 JPA 강의를 듣다 CQS, CQRS의 의문이 생겨

어떻게 나온 단어들인지, 그리고 간단한 코드 예시와 함께 알아보았습니다.

긴 글 읽어주시느라 고생 많으셨고, 다들 하트 한 번씩만요....!

 

참고

https://www.inflearn.com/questions/27795/cqrs

https://www.eventstore.com/cqrs-pattern

https://bertrandmeyer.com/wp-content/upLoads/OOSC2.pdf

https://www.dotnetcurry.com/patterns-practices/1461/command-query-separation-cqs

https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf

728x90