공통 집계와 사용자별 캐시 상태를 분리하라
리액션 상태를 재사용 가능한 조회로 조합하고, 한 회원의 개인 상태가 모두에게 노출되지 않도록 공통 합계만 분리해 캐시한다.
출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다.
gpt-5.6-sol모델을 사용해 생성·편집했습니다.
전체 리액션 수와 현재 사용자의 반응 여부는 하나의 상태 응답처럼 보인다. 그러나 둘을 한 덩어리로 캐시하면 위험하다. 합계는 모두가 공유하지만 reactedByMe는 한 사용자에게 속한다. 이 결합 응답을 호출자 사이에서 재사용하면 이후 모든 호출자에게 캐시를 채운 회원의 상태가 보일 수 있다.
이 문제는 캐시 키 하나를 잘못 정한 데서 끝나지 않는다. 재사용 범위가 다른 두 정보가 한 응답에 들어 있음을 드러낸다. 캐시를 붙이기 전에 코드가 그 차이를 보여 줘야 한다.
유스케이스가 상태를 조합하게 한다
카운트만 제공하던 서비스는 프레젠테이션 계층이 숫자를 조회해 API 응답에 넣는 형태로 시작할 수 있다. 현재 사용자의 상태까지 응답에 들어가면 이 작업은 리액션 상태 조회 유스케이스에 가까워진다. 비즈니스 계층이 두 읽기를 조율하는 것이 자연스럽다.
- 대상의 활성 리액션 수를 센다.
- 현재 사용자가 대상에 활성 리액션을 남겼는지 확인한다.
그러면 프레젠테이션 계층은 비즈니스 의미를 직접 만들지 않고 완성된 상태를 받는다. 사용자 정보를 유스케이스에 전달하면 자신의 콘텐츠에 남긴 리액션을 제외하는 정책도 나중에 다룰 수 있다. 자기 리액션을 허용할지는 제품이 결정할 일이다. 이 구조는 그런 규칙을 적용할 사용자 맥락을 알맞은 위치에 남겨 둘 뿐이다.
아래의 조회는 각각 쓸모 있게 유지한다
리더나 저장소까지 하나의 status 메서드를 밀어 넣고 합계와 개인 상태를 한 번에 반환하고 싶을 수 있다. 현재 규모에서는 어느 쪽도 동작하지만, 결합한 메서드는 재사용 범위를 줄인다.
어떤 호출자는 합계만 필요하고, 다른 호출자는 개인 상태나 마지막 리액션 시각만 필요할 수 있다. 앞으로 한 계산의 정책만 바뀔 수도 있다. 구현 경계에서는 카운트와 개인 상태 연산을 분리해 두면 유스케이스가 필요한 값만 조합하고, 관련 없는 호출자가 두 비용을 모두 치르지 않게 할 수 있다.
무조건 잘게 쪼개자는 뜻은 아니다. 경계는 팀이 정할 문제다. 이 단계의 코드에서는 합계나 개인 상태만 따로 필요할 수 있고, 마지막 리액션 시각 같은 값이 더해질 수도 있으므로 조회를 분리하는 편이 낫다. 이 리팩터링은 모든 쿼리를 별도 클래스로 만들지 않으면서도 그런 선택지를 남긴다.
공통인 부분만 캐시한다
리액션 합계는 많은 사용자가 같은 대상에 같은 질문을 하므로 캐시 후보가 될 수 있다. 개인 상태의 답은 사용자마다 다르고, 그 사용자가 반응하거나 취소할 때 바뀐다.
전체 API 응답이나 전체 상태를 사용자 사이에서 재사용하도록 캐시하면 이 범위를 섞게 된다. 한 회원이 반응한 상태에서 채운 캐시 때문에 만료될 때까지 모든 방문자의 버튼이 활성화돼 보일 수 있다. 숫자는 맞아도 개인화 불리언은 분명히 틀린다.
하나의 응답으로 반환한다는 이유만으로 한 단위로 캐시할 필요는 없다. 이 사례에서는 공통 합계만 캐시 구현을 사용하고 개인화 조회는 그 캐시 밖에 둘 수 있다.
캐시 동작이 보이는 구현을 둔다
캐시 위치는 호출자가 원본과 캐시 중 무엇을 받는지도 설명해야 한다. 캐시 저장소 구현을 둘 수도 있고, 원래 카운트 리더를 감싸는 캐시 리더를 둘 수도 있다. 클래스 이름 자체보다 동작을 알아볼 수 있게 하고 캐시 정책을 관련 없는 조율 코드와 섞지 않는 것이 중요하다.
어떤 경로는 캐시된 합계가 필요하고 다른 경로는 원본 값이 필요할 때 명시적인 구현이 유용하다. 캐시 조회와 원본 조회가 한 구현에 섞이면 호출자가 무엇을 받는지 구분하기 어려워진다. 별도 캐시 구현은 연결하는 시점에 그 선택을 보이게 한다.
경계는 서비스마다 달라진다. DB 조회 자체가 캐시 대상이면 캐시 저장소가 맞을 수 있고, 그 선택을 별도 구현으로 드러내고 싶다면 캐시 리더가 맞을 수 있다. 이 리액션 상태 흐름에서는 어느 쪽이든 공통 합계와 그 옆에 조합되는 사용자별 답을 분리한다.
지금처럼 합계 조회만 캐시 경계로 내리면 원본 값이 필요한 경로와 캐시 값을 쓰는 경로도 코드에서 구분할 수 있다. 나중에 캐시 전략이 달라져도 사용자별 상태 조회까지 함께 옮길 이유가 없다. 한 응답으로 조합되는 값이라도 저장과 재사용의 수명주기는 서로 다를 수 있다는 점을 구조에 남기는 것이다.