오프라인 우선 채점 애플리케이션 아키텍처: 서버 계산, 하이드레이션 결합, 템플릿 필터링

From ERB to JavaScript - Server Computes, Hydration Joins, Templates Filter

작성자
발행일
2025년 11월 25일

핵심 요약

  • 1 라이브 댄스 대회의 불안정한 네트워크 환경을 극복하기 위해 '서버 계산, 하이드레이션 결합, 템플릿 필터링' 원칙을 적용한 오프라인 우선 채점 아키텍처를 구축했습니다.
  • 2 Ruby 3.4의 Prism 파서를 활용하여 ERB 템플릿을 JavaScript로 자동 변환함으로써 템플릿 중복을 제거하고 ERB를 단일 진실 공급원으로 활용했습니다.
  • 3 이 아키텍처는 이전 Web Components 방식 대비 52% 적은 코드로 구현되었으며, 강력한 디버깅 도구와 함께 오프라인 데이터 처리 및 템플릿 일관성을 보장합니다.

도입

본 글은 쇼케이스 애플리케이션의 오프라인 우선 채점 기능에 대한 세 번째 반복 구현과 최종 아키텍처를 문서화합니다. 이전 시도에서는 Web Components, Turbo MVC, 그리고 ERB-to-JavaScript 자동 변환을 다루었으며, 이 경험을 통해 불안정한 호텔 WiFi 환경에서도 심사위원들이 공연을 채점할 수 있는 견고한 오프라인 솔루션의 필요성을 확인했습니다. 궁극적인 목표는 Rails를 진실의 원천으로 유지하면서 오프라인으로 작동하는 단일 페이지 애플리케이션을 구현하는 것이었습니다. 이 글에서는 이러한 요구사항을 충족하는 '서버 계산, 하이드레이션 결합, 템플릿 필터링'이라는 핵심 통찰력을 바탕으로 한 아키텍처를 상세히 설명합니다.

라이브 댄스 대회 중 호텔 WiFi의 불안정성은 심사 과정에서 치명적인 실패로 이어질 수 있습니다. 이를 해결하기 위해 Rails를 진실의 원천으로 삼으면서 오프라인에서 작동하는 SPA를 구축하는 것이 중요했습니다. 이전 Web Components(4,025 라인), Turbo MVC(2,604 라인), ERB-to-JS 변환(1,933 라인) 시도는 오프라인 기능의 가능성을 입증하고 템플릿 중복 문제를 해결하는 데 기여했지만, 복잡성이나 불완전함이라는 한계가 있었습니다. 최종 아키텍처는 이러한 교훈을 바탕으로 자동 템플릿 변환과 깔끔한 데이터 파이프라인을 결합합니다.

아키텍처: 서버 계산, 하이드레이션 결합, 템플릿 필터링

이 원칙은 구현 전체를 안내합니다.

  1. 서버 계산 (Server Computes)
    • Rails는 심사위원이 이벤트에 필요한 모든 데이터를 관계형 데이터베이스처럼 정규화하여 추출합니다.
    • 중첩된 객체 대신 조회 테이블(예: heats, dances, entries, people, studios) 형태로 데이터를 전송하여 데이터 크기를 크게 압축합니다(예: 13MB에서 176KB로).
    • 복잡한 비즈니스 로직이 필요한 파생 값(예: dance_string)도 서버에서 미리 계산하여 전송합니다.
  2. 하이드레이션 결합 (Hydration Joins)
    • 클라이언트에서는 heat_hydrator.jsbuildLookupTables()hydrateHeat() 함수를 사용하여 ID를 해결하고 중첩된 객체를 재구성합니다.
    • 이를 통해 Ruby의 ActiveRecord 컬렉션처럼 heat.entry.lead.studio.name과 같이 데이터를 탐색할 수 있는 구조를 만듭니다.
  3. 템플릿 필터링 (Templates Filter)
    • ERB에서 자동으로 변환된 JavaScript 템플릿은 하이드레이션된 데이터를 받아 HTML을 렌더링합니다.
    • 템플릿은 계산 로직 없이 데이터를 필터링하고 형식화하는 역할만 수행하며, 수동 작성 없이 자동 변환되므로 ERB 원본과 동일합니다.

ERB-to-JavaScript 변환

Ruby 3.4의 Prism 파서는 ERB 템플릿을 추상 구문 트리(AST)로 파싱하고, 이를 순회하여 동등한 JavaScript 코드를 생성하는 자동 변환을 가능하게 합니다. Rails의 /templates/scoring.js 엔드포인트는 이 ErbPrismConverter를 사용하여 ERB 템플릿을 동적으로 JavaScript 모듈로 변환하여 제공합니다. @subjectsdata.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 패턴과 강력한 디버깅 도구를, 운영팀에게는 효율적인 데이터 전송과 비상 폴백을 제공합니다.

결론

결론적으로, '서버 계산, 하이드레이션 결합, 템플릿 필터링' 아키텍처는 오프라인 우선 채점 기능을 성공적으로 구현했습니다. 이 접근 방식은 Web Components 방식보다 52% 적은 코드로, 단일 ERB 템플릿 코드베이스를 유지하고 자동 변환을 통해 템플릿 간의 완벽한 일치성을 보장합니다. 또한, 중간 아티팩트를 활용한 강력한 디버깅 도구는 개발 과정을 효율적으로 만듭니다. 세 번의 반복 구현을 통한 여정은 낭비가 아니라, 문제 공간을 압축하고 진정으로 해결해야 할 것이 무엇인지 밝혀내는 필수적인 탐색 과정이었습니다. 이 경험은 복잡성을 제거하는 올바른 추상화를 찾는 것이 견고한 이벤트 애플리케이션을 구축하는 핵심임을 보여줍니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!