라이브 댄스 대회 중 호텔 WiFi의 불안정성은 심사 과정에서 치명적인 실패로 이어질 수 있습니다. 이를 해결하기 위해 Rails를 진실의 원천으로 삼으면서 오프라인에서 작동하는 SPA를 구축하는 것이 중요했습니다. 이전 Web Components(4,025 라인), Turbo MVC(2,604 라인), ERB-to-JS 변환(1,933 라인) 시도는 오프라인 기능의 가능성을 입증하고 템플릿 중복 문제를 해결하는 데 기여했지만, 복잡성이나 불완전함이라는 한계가 있었습니다. 최종 아키텍처는 이러한 교훈을 바탕으로 자동 템플릿 변환과 깔끔한 데이터 파이프라인을 결합합니다.
아키텍처: 서버 계산, 하이드레이션 결합, 템플릿 필터링
이 원칙은 구현 전체를 안내합니다.
- 서버 계산 (Server Computes)
- Rails는 심사위원이 이벤트에 필요한 모든 데이터를 관계형 데이터베이스처럼 정규화하여 추출합니다.
- 중첩된 객체 대신 조회 테이블(예:
heats,dances,entries,people,studios) 형태로 데이터를 전송하여 데이터 크기를 크게 압축합니다(예: 13MB에서 176KB로). - 복잡한 비즈니스 로직이 필요한 파생 값(예:
dance_string)도 서버에서 미리 계산하여 전송합니다.
- 하이드레이션 결합 (Hydration Joins)
- 클라이언트에서는
heat_hydrator.js의buildLookupTables()및hydrateHeat()함수를 사용하여 ID를 해결하고 중첩된 객체를 재구성합니다. - 이를 통해 Ruby의 ActiveRecord 컬렉션처럼
heat.entry.lead.studio.name과 같이 데이터를 탐색할 수 있는 구조를 만듭니다.
- 클라이언트에서는
- 템플릿 필터링 (Templates Filter)
- ERB에서 자동으로 변환된 JavaScript 템플릿은 하이드레이션된 데이터를 받아 HTML을 렌더링합니다.
- 템플릿은 계산 로직 없이 데이터를 필터링하고 형식화하는 역할만 수행하며, 수동 작성 없이 자동 변환되므로 ERB 원본과 동일합니다.
ERB-to-JavaScript 변환
Ruby 3.4의 Prism 파서는 ERB 템플릿을 추상 구문 트리(AST)로 파싱하고, 이를 순회하여 동등한 JavaScript 코드를 생성하는 자동 변환을 가능하게 합니다. Rails의 /templates/scoring.js 엔드포인트는 이 ErbPrismConverter를 사용하여 ERB 템플릿을 동적으로 JavaScript 모듈로 변환하여 제공합니다. @subjects가 data.subjects로, <% @subjects.each do |subject| %>가 data.subjects.forEach(subject => {로 변환되는 등 Rails 헬퍼까지 JavaScript에 상응하는 형태로 처리됩니다.
데이터 흐름: 온라인 및 오프라인
-
초기 로드: Stimulus 컨트롤러 연결, 템플릿 및 정규화된 데이터 로드, 조회 테이블 구축, 현재 히트 렌더링.
-
히트 탐색 (온라인): 가벼운 버전 확인 후 변경 없으면 캐시된 데이터 사용, 변경 시 새 데이터 페치.
-
히트 탐색 (오프라인): 네트워크 오류 시 캐시된 데이터로 폴백, 점수 업데이트는 IndexedDB에 대기열로 저장.
-
점수 입력: 온라인 시 즉시 POST, 오프라인 시 IndexedDB에 ‘DirtyScoresQueue’로 저장 후 오프라인 표시.
-
재연결: 연결 복원 감지 시 대기열에 있는 점수 일괄 업로드, 성공 시 대기열 비우고 데이터 최신화.
서비스 워커 및 디버깅
서비스 워커는 ‘네트워크 우선’ 전략을 사용하여 온라인 시 최신 데이터를, 오프라인 시 캐시된 응답을 제공합니다. render_erb_and_js.rb 디버깅 스크립트는 ERB와 JavaScript 렌더링 결과를 나란히 비교하고, 중간 아티팩트(생성된 JS, 템플릿 데이터)를 제공하여 디버깅 시간을 크게 단축시킵니다.
코드 비교 및 결과
최종 구현은 Web Components 방식 대비 52% 적은 코드(841라인의 ERB-to-JS 컨버터, 270라인의 Stimulus 컨트롤러 등)로 단일 템플릿 코드베이스를 유지하며 템플릿 패리티를 보장합니다. 이는 심사위원에게는 WiFi 장애 중에도 채점 및 탐색이 가능한 경험을, 개발자에게는 친숙한 Rails 패턴과 강력한 디버깅 도구를, 운영팀에게는 효율적인 데이터 전송과 비상 폴백을 제공합니다.