본문 바로가기

프로그래밍

CQS(Command-Query Separation)

  • CQS는 Bertrand Meyer에 의해 제안되었다
  • CQS의 목적은 커맨드와 쿼리를 분리하는 것이다
    • command(명령)은 결과를 반환하지 않는다. 시스템의 상태를 변경한다
      • command라는 용어대신 컨텍스트에 따라 modifier, mutator라고 부를 수 있다
    • query(질의)는 결과를 반환한다. 시스템의 관찰 가능한 상태를 변경하지 않는다(사이드 이펙트가 없다)
    • 사이드 이펙트의 정의는 맥락마다 다르지만 여기서 쓰인 의미는 내부에 변경이 일어나는지 아닌지를 말하는 것이다. 즉, 내부에 변경이 발생하지 않으면 사이드 이펙트가 없는 것이다.
    • 예외사항
      • CQS의 예외사항으로 좋은 사례는 stackpop이다
      • stack의 pop은 가장 최근에 push된 값을 반환하고 stack에서는 제거하는 명령이다
      • pop은 command(stack에서 값이 제거되므로 stack의 상태 변경)와 query(stack에 가장 최근에 추가된 값이 반환)가 합쳐진 것이다
      • 마틴 파울러는 할 수 있는 한 CQS 원칙을 지키려 하겠지만 stack의 pop과 같이 매우 유용한 상황에서는 얼마든지 CQS 원칙을 깨뜨릴 준비가 되어있다고 말했다
      • trade off를 생각하고 유연한 사고로 설계하며 구현하자
    • JPA에서 활용하는 방법
      • insert는 id만 반환
        • insert는 DB의 상태에 변경을 일으키지만 id를 반환한다. 이는 필요에 의해 CQS 원칙을 깬거라고 볼 수 있다.
        private EntityManager em;
        
        @Transactional
        public Long save(Item item) {
            em.persist(item);
            Item item = em.find(Item.class, id);
            return item.getId();
        }
      • update는 아무것도 반환하지 않음
        • DB의 데이터에 변경이 발생하지만 아무것도 반환하지 않으므로 command
        private EntityManager em;
        
        @Transactional
        public void update(Item item) {
            em.persist(item);
        }
      • select는 내부 변경없이 결과값만 반환
        • DB의 데이터에 변경이 발생하지 않고 sql query에 대한 결과값만 반환하므로 query
        @Transactional
        public Item findOne(Long id) {
            Item foundItem = em.find(Item.class, id);
            return foundItem;
        }

참고자료

  • https://martinfowler.com/bliki/CommandQuerySeparation.html
  • Meyer likes to use command-query separation absolutely, but there are exceptions. Popping a stack is a good example of a query that modifies state. Meyer correctly says that you can avoid having this method, but it is a useful idiom. So I prefer to follow this principle when I can, but I'm prepared to break it to get my pop.
  • https://hyeonk-lab.tistory.com/43
  • 변경(사이드 이펙트): 사이드 이펙트라는 단어가 비슷하면서도 서로 다르게 정의 내려지지만 핵심은 '상태 변경'이다.
  • https://www.inflearn.com/questions/27795
  • 이게 웹 애플리케이션에서 얻는 이점이 있다기 보다는, 개발 전반에 기본개념으로 깔고 가시는 것이 좋습니다.그렇게 되면 데이터 변경 관련 이슈가 발생했을 때, 변경이 일어나는 메서드만 찾아보면 됩니다.변경 메서드도 변경에만 집중하면 되기 때문에 유지보수가 더 좋아집니다.
  • 제가 권장하는 방법은 insert는 id만 반환하고(아무것도 없으면 조회가 안되니), update는 아무것도 반환하지 않고, 조회는 내부의 변경이 없는 메서드로 설계하면 좋습니다.
  • 정말 크리티컬한 이슈들은 대부분 데이터를 변경하는 곳에서 발생하지요.
  • 이 메서드를 호출 했을 때, 내부에서 변경(사이드 이펙트)가 일어나는 메서드인지, 아니면 내부에서 변경이 전혀 일어나지 않는 메서드인지 명확히 분리하는 것이지요.