회원과 비회원을 함께 다루는 조회 모델
공개 리액션 수와 회원별 상태를 하나의 조회 흐름으로 반환하되, 선택적 사용자 정보와 필수 인증의 경계는 분명히 나눈다.
출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다.
gpt-5.6-sol모델을 사용해 생성·편집했습니다.
리액션 버튼에는 두 가지 답이 필요하다. 몇 명이 반응했는지와 현재 사용자가 반응했는지다. 첫 번째 값은 공개할 수 있지만 두 번째 값은 로그인한 회원에게만 의미가 있다. 조회 API가 인증된 사용자만 받으면 비회원은 공개돼야 할 수도 있는 합계까지 볼 수 없다. 반대로 모든 API가 사용자 정보가 없을 때 조용히 게스트로 처리하면, 반드시 회원이어야 하는 작업의 의미가 흐려진다.
실용적인 설계 문제는 선택 인증과 필수 인증의 차이를 지우지 않으면서 조회 흐름을 공유하는 데 있다. 컨트롤러 시그니처만 보아도 비회원 접근을 허용하는 조회인지, 반드시 회원이어야 하는 작업인지 드러나야 한다.
화면에 필요한 응답에서 시작한다
클라이언트가 리액션을 그리고 토글하려면 응답에 전체 수와 reactedByMe 같은 불리언 값을 담을 수 있다. 비회원도 회원과 같은 응답 모양을 받는다. 합계는 보이고 개인 상태는 false다. 로그인한 회원에게는 합계와 함께 그 회원의 리액션 레코드로 계산한 상태를 돌려준다.
취소 기능을 자연스럽게 만들려면 먼저 이 상태를 조회할 수 있어야 한다. 클라이언트는 버튼을 눌렀을 때 추가 API를 부를지 해제 API를 부를지, 아이콘을 채워 표시할지를 알아야 한다. 리액션 변경에 로그인이 필요하다고 해서 공개 합계 조회까지 로그인이 필요한 것은 아니다.
비회원용 API를 따로 만드는 방법도 있다. 게스트와 회원의 동작이 실제로 다르다면 맞는 선택이다. 하지만 같은 리소스를 반환하고 선택적인 개인화만 다르다면, 구현 경로가 하나 더 생기고 백엔드 구조가 UI의 두 상태에 묶일 수 있다.
선택적 사용자 정보를 경계 타입으로 드러낸다
HTTP 경계에서는 하나의 개념에 모든 경우를 욱여넣지 말고 두 가지를 구분한다.
- 비회원을 거부해야 하는 API가 받는 인증 사용자 인자
- 익명 접근을 허용하되 사용자 정보가 있으면 개인화하는 API가 받는 게스트-또는-사용자 인자
인자 리졸버가 인증 쿠키를 확인할 수 있다. 두 번째 개념에서 쿠키가 없으면 게스트 표현으로, 유효한 쿠키가 있으면 회원 ID로 변환한다. 그러면 컨트롤러는 그 API의 계약을 설명하는 명시적인 값을 받는다. 합계 조회는 그대로 두고, 프레젠테이션 계층에서 사용자별 상태와 조합한다.
원래 구현에서는 -1 같은 ID로 게스트를 표현했다. 실제 사용자가 가질 수 없는 값이므로 평범한 리액션 조회는 행을 찾지 못하고 false를 반환한다. 비회원과 회원에게 같은 조회를 실행하면서 안쪽 쿼리를 단순하게 유지하는 방법이다. 단, 그 값은 실제 사용자에게 절대 배정되지 않아야 한다.
회원 전용 API의 보장을 약하게 만들지 않는다
기존 인증 사용자 리졸버에 게스트 폴백을 넣고, 쿠키가 없을 때마다 센티널을 돌려주면 클래스 몇 개는 줄어든다. 그러나 중요한 보장도 사라진다. 예를 들어 개인 리액션 목록은 익명 방문자를 특이한 회원처럼 취급한 채 진행해서는 안 된다.
리졸버 타입을 둘로 유지하면 각 컨트롤러 시그니처에서 정책이 보인다. 인증 사용자를 받는 코드는 사용자 정보가 있다고 전제할 수 있다. 게스트-또는-사용자를 받는 코드는 공개 경로를 처리해야 한다. 여기서 보여 준 구현은 쿠키가 없을 때만 게스트로 바꾼다. 쿠키 값이 조작됐거나 더는 사용자를 식별하지 못하면 인증 실패로 남긴다.
우연한 UI 분기가 아니라 동작을 재사용한다
조회 흐름 안의 로직은 평범하게 유지할 수 있다. 대상의 활성 리액션 수를 센다. 해석된 회원 ID로 활성 리액션을 찾는다. 없거나 게스트라면 false를 반환한다. 프레젠테이션 경계에서 두 값을 하나의 응답 모델로 조합한다.
이 구조는 화면이 로그인과 로그아웃 두 상태로 보인다는 이유만으로 도메인 조회를 복제하지 않는다. 동시에 바깥 경계의 중요한 차이는 보존한다. 어떤 조회는 공개이면서 선택적으로 개인화되고, 어떤 동작은 회원에게만 허용된다.
모든 서비스에 반드시 이 패턴을 적용할 필요는 없다. 비회원과 회원의 동작이 실제로 다르다면 API를 나누는 편이 명확할 수 있다. 선택은 그 동작을 따라야 한다. 차이가 개인 상태의 존재뿐이라면 하나의 조회 모델과 명시적인 선택 사용자 타입이 구현을 작게 유지하면서도 비회원과 회원이 같다고 가장하지 않게 해 준다.