첫 화면 값을 한 응답으로 묶기: home-summary

첫 화면의 주차, 나들이, 예측과 출처 상태를 한 번에 내려주는 API 계약

한강자리 첫 화면에는 주차장, 현재 값, 예측, 혼잡도, 행사, 시설 요약, 길찾기 후보와 출처 확인 결과가 함께 들어온다. 클라이언트가 여러 응답을 조합하면 섹션마다 성공 여부와 갱신 시각이 어긋날 수 있다.

주차 섹션은 방금 갱신됐는데 행사 섹션은 오래된 값일 수 있다. 예측은 다른 horizon을 사용하고 위젯에는 앱과 다른 갱신 상태가 표시될 수도 있다.

서버는 첫 화면에 필요한 값을 한 응답으로 묶어 섹션과 위젯의 기준 시각을 맞춘다.

한 번에 받는 첫 화면 값

home-summary는 한 공원에 대해 앱과 위젯이 처음 표시할 값을 한 번에 제공한다.

flowchart LR
  API["공원별 첫 화면 응답"] --> Park["공원"]
  API --> Parking["주차 집계<br/>주차장 상태"]
  API --> Forecast["주차 예측<br/>공원 혼잡 예측"]
  API --> Outing["나들이 요약<br/>신호 · 시설 그룹 · 길찾기"]
  API --> Freshness["갱신 상태<br/>출처 확인"]

개별 호출 방식에서는 주차장 목록, 현재 값 목록, 예측 overview, 나들이 overview와 출처 확인 결과를 따로 불러야 한다. 이때 다음 문제가 생긴다.

  • 호출별 성공/실패가 서로 어긋난다.
  • 화면 일부가 이전 공원 값으로 남을 수 있다.
  • 위젯은 여러 네트워크 호출을 감당하기 어렵다.
  • cache와 갱신 상태 확인이 복잡해진다.
  • API가 바뀔 때 iOS decoding에서 방어해야 할 범위가 넓어진다.

서버는 한 payload 안에서 각 섹션의 값과 갱신 상태를 함께 결정한다.

home-summary의 필드는 “첫 화면에서 바로 필요한가”를 기준으로 정했다. 상세 화면에서만 필요한 긴 목록이나 원문 payload를 제외해 응답의 크기와 변경 범위를 제한한다.

네 묶음으로 나눈 응답

iOS에서 받는 값은 크게 네 부분으로 나뉜다.

필드역할
park현재 summary가 어떤 공원을 말하는지 확인
parking주차 aggregate, 추천 주차장, lots, statuses
forecasthorizon과 주차/공원 혼잡 예측 overview
outing일반 화면 신호, 시설 그룹, 길찾기, 출처 확인 결과

앱의 주차 화면은 HomeSummary에서 주차 overview를 꺼내고 일반 화면은 compact data를 재사용한다.

flowchart TB
  HomeSummary --> ParkingOverview["ParkingOverview 값"]
  HomeSummary --> OutingOverview["일부 OutingOverview 값"]
  HomeSummary --> WidgetParking["주차 위젯 스냅샷"]
  HomeSummary --> WidgetGeneral["일반 위젯 스냅샷"]

클라이언트는 서버가 묶어 준 값을 화면별로 나눠 쓴다. 주차 overview, 일반 공원 summary와 위젯 snapshot이 같은 응답에서 출발하므로 중복 호출과 화면 전환 중 시점 차이가 줄어든다.

기존 앱의 새 응답 호환성

iOS 앱은 서버보다 느리게 업데이트되므로 이전 앱이 새 서버 payload를 읽는 기간이 생긴다. home-summary는 이 기간에 기존 필드 의미를 유지한다.

지켜야 할 것은 다음이다.

  • 기존 required field를 유지한다.
  • 새 값은 optional field 또는 nested object 추가로 시작한다.
  • enum은 알 수 없는 값을 만났을 때의 동작을 갖는다.
  • 날짜와 최신성 관련 field는 의미를 바꾸지 않는다.
  • park ID가 요청한 값과 다르면 payload를 믿지 않는다.

위젯 로더도 이 방식을 따른다. 네트워크에서 받은 home-summary의 공원 ID가 요청과 다르면 빈 데이터로 처리해 캐시 오염을 막는다.

첫 화면 응답을 나누는 iOS 캐시

iOS는 받은 HomeSummary를 SwiftData에 저장하고 내부의 공원, 주차장, status와 forecast overview를 각 cache에 반영한다.

sequenceDiagram
  autonumber
  participant Store as 앱 상태 스토어
  participant Repo as 주차 저장소
  participant API as 한강자리 API
  participant Cache as SwiftData 캐시

  Store->>Repo: 저장된 첫 화면 값 확인
  alt 캐시 최신
    Repo-->>Store: 캐시된 첫 화면 값 반환
  else 오래됐거나 없음
    Repo->>API: 첫 화면 응답 요청
    API-->>Repo: 첫 화면 응답 반환
    Repo->>Cache: 첫 화면 값 저장
    Repo->>Cache: 공원/주차/상태/예측 캐시 갱신
    Repo-->>Store: 새 첫 화면 값 반환
  end

첫 화면과 상세 화면은 같은 응답에서 출발한다. 사용자가 주차 상세로 들어가도 방금 받은 값을 재사용한다.

서버 cache의 최신성 표시

서버는 home-summary에 빠른 응답용 cache와 마지막 성공 응답 cache를 둔다. 정상적으로 다시 만들면 두 cache를 같이 갱신한다. 다시 만들기에 실패하면 마지막 성공 응답을 반환할 수 있다.

HTTP 응답에는 Cache-Control: private와 ETag를 붙여 같은 앱 인스턴스의 짧은 반복 요청에 304로 응답할 수 있게 한다.

stale backup을 반환할 때도 payload의 갱신 상태와 출처 확인 결과를 유지한다.

앱과 위젯의 기준 시각

home-summary는 앱 화면, 위젯과 cache가 함께 쓰는 첫 화면 값을 한 응답으로 제공한다. 클라이언트는 요청한 공원과 응답 공원 ID를 검증하고 새 필드가 추가돼도 기존 의미를 유지한다. 서버가 stale cache를 반환하면 갱신 상태를 보존해 마지막 확인값임을 표시한다.

Comments

댓글

    이미지 확대