lifecarelog
백엔드

감정 기록 API에서 '아직 안 씀'과 '기록 없음'을 나눈 이유

플랜티 감정 기록 API가 null 하나로 뭉개던 두 상태를 날짜 경계·상태 enum·인증 오류로 나눠 돌려주게 된 설계 이유를 정리했어요.

5분 읽기

감정 기록 API에서 '아직 안 씀'과 '기록 없음'을 나눈 이유

플랜티(plan-t) 감정 기록 화면을 만들다가 API 응답이 null 하나로 두 상황을 뭉개고 있다는 걸 알았어요. 오늘 아직 안 쓴 날과 이미 지나가서 기록이 없는 날은 사용자에게 완전히 다른 의미인데, 서버는 둘 다 "행이 없다"고만 말하고 있었거든요. 직접 만들어 보니 이 구분은 화면 카피 문제가 아니라 DB와 API 경계의 문제였어요.

DB에서는 둘 다 "행 없음"이에요

감정 기록 테이블은 사용자당 하루 1행을 기본으로 잡았어요. user_id와 local_date에 UNIQUE를 걸어두면 같은 날 두 번 저장되는 일은 DB가 막아줘요. 문제는 조회 쪽이에요. 오늘 행이 없는 것과 어제 행이 없는 것은 테이블 입장에선 똑같이 0건이라서, 의미를 가르는 기준은 데이터가 아니라 시간이에요. "오늘"이 아직 안 끝났으면 쓸 수 있는 상태고, 이미 끝났으면 되돌릴 수 없는 공백이에요.

테스트해 보면 이 시간 경계가 가장 먼저 어긋나요. 서버가 UTC 기준으로 날짜를 자르면 한국 시간 자정부터 오전 9시까지 9시간 동안 '오늘'이 어제로 계산돼요. 그래서 저장 시각은 UTC timestamp로 두고, 사용자 타임존으로 계산한 local_date를 별도 컬럼으로 분리했어요. 날짜 문자열을 그대로 Date로 파싱하지 않는 것도 같은 이유예요.

API는 null 대신 상태를 돌려줘요

적용해 봤더니 클라이언트가 null을 보고 시간 계산을 다시 하는 구조는 금방 어긋났어요. 앱과 서버가 각자 자정을 계산하면 둘 중 하나는 틀리거든요. 그래서 오늘 기록 조회는 항상 200으로 답하고, 본문에 상태 필드를 넣었어요. not_written_yet, no_record, written 3가지에 분류가 안 되는 경우를 위한 unknown을 하나 더 뒀어요. 시간 경계 판단은 서버 1곳에서만 하고, 클라이언트는 상태값으로 카피만 분기해요.

null 하나로 답하는 API와 상태 enum으로 답하는 API의 차이

세 번째 '없음'은 인증이에요

돌려 보니 '없음'이 하나 더 있었어요. RLS가 걸린 테이블은 권한이 없는 조회도 에러가 아니라 빈 결과로 돌아와요. 그러면 세션이 만료된 사용자도 "기록 없음" 화면을 보게 돼요. 이건 데이터 상태가 아니라 인증 실패라서, 서버에서 auth.getUser()가 실패하면 401로 먼저 끊고 상태 판단은 그 뒤에만 해요. 빈 결과를 곧바로 "없음"으로 해석하지 않는 게 핵심이었어요.

  • 시간 경계는 서버 1곳
  • null 대신 상태 enum
  • 인증 실패는 401로 분리
  • unknown은 로그로 격리

라이프케어로그의 다른 도구들도 같은 원칙으로 가려고 해요. 상태를 더 만드는 건 귀찮지만, null 하나에 여러 의미를 실어 보내면 결국 화면마다 추측 코드가 쌓여요. "없다"가 몇 종류인지 먼저 세어 보는 편이 훨씬 싸요.

#plan-t#api-design#postgres

라이프케어로그 서비스가 궁금하신가요?

AI 기반 건강·일정·재활 관리 앱을 직접 써보세요.

서비스 살펴보기

관련 글

댓글

아직 댓글이 없어요. 첫 댓글을 남겨주세요.