처음에는 상영 중인 영화만 검색할 수 있도록 기능을 구성했었다. 근데 가만 생각해보니까,
"영화 검색이라는 게 꼭 상영 중인 영화만 대상으로 해야 하는 걸까?"
라는 생각이 들었다.
단순히 예매만 하려는 게 아니라, 이미 본 영화나 시리즈 전작이 궁금할 수도 있고, 개봉 예정작을 찾아보려는 사용자가 분명 있을 거라고 생각했다. 예를 들어 <해리포터: 혼혈왕자>을 보고 나서 전편이 뭐였지? 하며 검색할 수도 있는 거고, 상영 끝난 영화가 입소문 타면서 다시 궁금해질 수도 있는 거니까.
그래서 사용자의 검색 목적은 단순히 예매가 아니라 더 넓은 탐색에 있다고 판단했고, 상영 중 영화로만 범위를 제한하는 건 오히려 불편함을 줄 수 있다는 생각에 전체 영화 검색 기능을 추가하게 됐다.
전체 영화 검색 기능을 붙이려 했을 때 가장 먼저 부딪힌 건, 검색 API에서 상영 중 여부 정보를 아예 주지 않는다는 거였다.
처음에는 /movie/now_playing이랑 /search/movie 구조가 비슷하겠거니 하고 하나의 DTO로 처리하려 했는데, 막상 써보니까 그렇지도 않았다.
그래서 그냥 다 옵셔널로 만들자니... ViewModel에서 매번 ?? 처리해줘야 하고, 화면에서 꼭 필요한 값도 항상 체크해야 하니 매번 번거로울수 있겠다고 생각했다.
처음엔 TMDB에서 주는 JSON 그대로 쓰면 되지 않을까? 싶었는데, 갈수록 그런 방식이 불편해졌다.
예매 가능 여부를 판단하려면 isNowPlaying 같은 필드를 써야 했는데, 이건 서버에서 안 주니까 클라이언트에서 직접 판단해야 하잖아.
그러다 보니 "아, 외부 데이터를 그대로 쓰기보단, 앱 안에서 내가 쓰기 편한 구조로 가공하는 게 낫겠다"는 생각이 들었고, 그래서 Movie라는 도메인 모델을 따로 만들게 됐다.
이 방식이 훨씬 유지보수나 확장성 측면에서도 깔끔했다.
검색된 영화가 상영 중인지 아닌지 빠르게 판단해야 하는데, 하나하나 비교해서 확인하면 성능에 부담이 될 수도 있다고 생각했다.
그래서 나는 상영 중인 영화 ID를 따로 Set으로 모아두고, 검색 결과의 각 영화 ID가 이 Set<집합>에 포함되는지만 체크하는 방식으로 처리했다.
왜 Set이냐면, 중복 제거가 자동이고 배열보다 검색 속도가 훨씬 빠르기 때문이다 (Hashable을 채택한 Hash 기반이라 탐색 속도 빠름)
이 구조 덕분에 예매 가능 여부를 판단하는 데 있어서도 성능 걱정 없이 쓸 수 있었다.
같은 영화인데 ID가 다르게 나오는 경우가 있었다.
TMDB에서 같은 영화라도 국가별, 언어별로 다른 ID를 부여하는 경우가 있었던 거다.
결국 ID 매칭이 안 돼서 상영 중인데도 예매가능하다고 뜨지 않는 문제가 생긴 거다.
이름이랑 출시일로 비교해보는 것도 고려했지만, 동명이인 영화나 비슷한 날짜 영화들이 있어서 판단 기준이 애매했다.
그래서 나는
"문자열 기반 유사도 비교는 하지 말자."
라는 결론을 내리고, 대신 UI 구조를 좀 더 명확하게 나누기로 했다.
처음엔 필터 기능이나 드롭다운도 생각했는데,
선택지가 딱 두 개(전체 / 상영 중)뿐이라 그런 방식은 오히려 과하다고 느꼈다.
그래서 더 직관적이고 명확한 구조인 세그먼트(Segmented Control)로 결정했다.
화면도 깔끔하고, UX 흐름상 사용자 입장에서도 이해하기 쉬웠다.
처음에는 /now_playing API의 1페이지 정도만 불러오면 충분하다고 생각했었다.
그게 상영 중 영화의 전부인 줄 알았기 때문이다.
그런데 TMDB 문서를 다시 보니, 상영 중 영화가 최대 100편 가까이 존재할 수 있다는 걸 알게 됐다.
그렇다고 앱이 실행되자마자 모든 페이지를 호출하는 건 모든 사용자가 검색 기능을 사용하는 것도 아닌데 리소스를 낭비하는 구조라고 생각했다.
그래서 구조를 이렇게 조정했다.
앱 초기에는 메인 화면에 필요한 1페이지 분량의 상영 중 영화만 호출하고,
이 데이터를 Repository와 ViewModel 양쪽에 전달해서 공유 상태로 보관했다.
그 이유는 이미 불러온 데이터를 Search 과정에서 또 다시 호출하는 비효율을 줄이기 위해서였다.
사용자가 검색 화면에 진입했을 때, 만약 전체 상영 중 영화 데이터가 필요한 상황이라면 Repository는 이미 받아둔 페이지를 제외하고 나머지 페이지만 추가로 호출한다.
그리고 전체 데이터가 준비되면, ViewModel에 데이터를 복사해서 내부 상태로 유지하고, ViewController는 이 상태를 구독하면서 UI에 반영할 수 있게 된다.
이런 구조를 통해 앱 초기 로딩은 가볍게 유지되고 이미 받아둔 데이터를 재활용하면서 필요한 시점에만 나머지를 효율적으로 불러오는 흐름을 만들 수 있었다.
상영 중 영화 검색은 예매 가능 여부와 연결된 핵심 기능이라 메인 흐름으로 구성했지만,
전체 영화 검색은 부가적인 탐색 경험을 위한 기능이라고 생각했다.
그래서 이 둘을 하나의 흐름으로 묶기보다는, 구조적으로 명확하게 나누는 게 UX 측면에서도 자연스럽고 설계적으로도 납득 가능했다.
이 작업을 하면서 단순히 "API가 데이터를 안 준다"고 불평하기보단,
"우리가 원하는 기능을 위해 이 데이터를 어떻게 가공해서 써야 할까?"
를 고민하는 게 훨씬 중요하다는 걸 알게 됐다.
이번 경험은 단순한 기능 구현이 아니라, 데이터를 바라보는 관점, 앱 구조 설계, 사용자 경험까지 포함한 큰 그림을 생각해보는 계기가 됐다.
🐍 Poetry 설치 중 PermissionError? 나도 그랬다 (0) | 2025.06.13 |
---|---|
TMDB에서 생긴일 -3 (PassthroughSubject) (0) | 2025.05.24 |
TMDB에서 생긴일 - 2 (0) | 2025.05.24 |