LiveView는 Svelte와 가장 잘 어울림
(blog.sequin.io)- Sequin 팀은 React SPA와 순수 Phoenix LiveView를 모두 써본 뒤, LiveSvelte로 LiveView와 Svelte를 결합하는 방식이 가장 생산적이라고 판단함
- LiveView는 서버 상태와 DOM 증분 업데이트로 SPA 없이 풍부한 UI를 만들 수 있지만, 클라이언트 상태가 필요한 순간 JS 모듈·훅·LiveView 상태가 섞이며 경계가 흐려짐
- 순수 LiveView의 LiveView, LiveComponent, Component는 수명주기·props 전달·통신 방식이 달라 리팩터링과 상태 배치가 React보다 번거로움
- LiveSvelte는 Elixir가 props를 내려보내고 Svelte가
live.pushEvent로 이벤트를 올리는 명확한 양방향 경계를 제공함 - 이 조합에서는 라우팅·비즈니스 로직·데이터 준비를 서버에 두고, UI 상태·애니메이션·조건 렌더링은 Svelte가 맡아 SPA의 복잡도를 줄일 수 있음
LiveView가 제공하는 개발 모델
- 전통적인 서버 렌더링 앱은 서버가 상태를 갖지 않고, 클라이언트 요청마다 페이지를 렌더링함
- SPA는 클라이언트가 페이지 구성을 맡고, 백엔드 API로 데이터를 읽고 쓰며, React의
useState같은 방식으로 클라이언트 상태를 관리함 - LiveView는 서버가 페이지 렌더링을 계속 담당하면서도 상태를 유지하고, 프런트엔드 액션을 처리한 뒤 DOM을 SPA처럼 증분 업데이트함
- SPA의 복잡성은 클라이언트 JS 앱이 사실상 하나의 프런트엔드 마이크로서비스처럼 동작하는 데서 커짐
- 프런트엔드 전용 요구사항 때문에 백엔드 라우트와 컨트롤러가 늘어남
- 한 요청이
onMount, API 호출, 응답 파싱, 라우터, 인증 플러그, 컨트롤러, 도메인 함수, 응답 준비 계층을 거칠 수 있음
- LiveView의 장점은 프런트엔드 마이크로서비스 없이도 풍부한 클라이언트 경험을 만들 수 있다는 점임
- 테이블 렌더링 함수 근처에서 데이터베이스를 조회할 수 있음
- 새 행이 들어오면 테이블에 push하고, LiveView가 클라이언트를 업데이트함
- 요청마다 전체 상태를 다시 만드는 기존 서버 렌더링 방식보다 상태ful 프런트엔드 프레임워크에 가까운 상호작용을 만들 수 있음
순수 LiveView에서 부딪힌 마찰
-
클라이언트 측 상태는 피할 수 없음
- LiveView는 서버와 사용자 사이의 왕복 지연을 피할 수 없음
- 애니메이션, 툴팁, DOM 표시·숨김, 폼 필드 비활성화 같은 작업은 클라이언트에서 처리해야 하는 경우가 생김
- 두 개의 의존적인 드롭다운이 있는 폼에서는 첫 번째 드롭다운 선택 직후 두 번째 드롭다운을 즉시 비활성화하고, 서버가 새 목록을 채운 뒤 다시 활성화해야 좋은 UX가 됨
- 이 동작을 LiveView만으로 구현하려면 여러 개념이 함께 필요함
- JS, 훅, LiveView 상태가 함께 쓰이면 DOM 상태 일부가 LiveView 바깥에 존재하게 되고, 일관된 패턴을 잡기 어려움
-
React와 다른 상태 경계
- React는 상태와 액션이라는 핵심 개념으로 대부분의 전환을 처리하며, DOM 상태와 컴포넌트 상태의 경계가 비교적 선명함
- 클라이언트 액션과 클라이언트 상태 사이에 지연이 없기 때문에 React는 상태 패러다임으로 액션과 전환을 다룰 수 있음
- LiveView의 상태는 서버 측에 있으므로, 클라이언트 액션과 서버 상태 사이의 지연을 고려해야 함
- 입력 필드 예시에서 React는 문자가 입력되기 전에 상태 경로를 거치고, 컴포넌트가 매 키 입력마다 다시 렌더링됨
- LiveView는 사용자가 먼저 입력 필드를 바꾸고, 짧은 시간이 지난 뒤 서버 상태가 이를 알게 되어 반응하는 모델에 가까움
- 지연이 거의 없으면 React처럼 보이지만, 지연이 커질수록 두 모델의 차이가 분명해짐
세 가지 컴포넌트가 만드는 복잡성
- LiveView에는 LiveViews, LiveComponents, Components라는 세 가지 컴포넌트 유형이 있음
- LiveViews와 LiveComponents는 React의 상태ful 컴포넌트에 가깝고, Components는 함수형 컴포넌트에 가까움
- LiveView는 항상 최상위 부모 컴포넌트가 되며, 그 아래에 LiveComponents와 Components가 렌더링됨
- React에서는
useState훅을 추가하거나 제거하는 방식으로 상태ful 컴포넌트와 함수형 컴포넌트 사이를 쉽게 오갈 수 있음- 둘 다 props를 같은 방식으로 받음
- 상태를 제외하면 기능 세트가 동일함
- DOM 이벤트 등록과 응답 방식도 같음
- LiveView에서는 세 컴포넌트 유형의 차이가 커서 LiveView를 LiveComponent로 리팩터링하기 번거로움
- LiveViews와 LiveComponents는 렌더링 및 props 전달 문법이 다름
- 수명주기가 다름
- 통신 옵션이 다르며, LiveViews에는
send, LiveComponents에는send_update를 사용함 - LiveComponents는 프로세스가 아니어서 LiveViews처럼 시스템의 다른 부분과 상호작용할 수 없음
LiveComponent의 제약
- LiveView는 하나의 Elixir 프로세스이므로
pid, 상태, inbox를 갖고 Elixir/OTP 시스템의 다른 프로세스처럼 동작할 수 있음 - LiveView에서는 pub/sub를 사용해 시스템 전역 변경을 구독할 수 있음
- LiveComponent는 자체 프로세스가 아니라 LiveView가 호출하는 모듈임
- 부모 LiveView 프로세스가 하위 컴포넌트의 상태를 보유하므로, LiveView가 자식 LiveComponents의 메시지 라우팅까지 처리해야 함
- LiveComponents가 독립적인 상태 관리와 액션 처리 능력을 가지려면 각각이 프로세스여야 하지만, 현재 모델은 그렇지 않음
- 실제 개발에서는 LiveComponent에 이벤트나 액션을 보내고 싶어도 좋은 방법이 없고,
send_update를 액션 전달에 쓸지 상태 패치에 쓸지 애매해짐- 상태 패치에 사용하면
update절에서 마운트와 업데이트를 구분하는 방식도 고민해야 함
- 상태 패치에 사용하면
LiveSvelte가 만드는 경계
- LiveSvelte는 LiveView에서 Svelte 컴포넌트를 렌더링할 수 있게 해줌
- LiveView의
render안에서 Elixir 데이터 구조를 프런트엔드용 plain map으로 인코딩한 뒤 Svelte 컴포넌트의 props로 전달할 수 있음 - Svelte 컴포넌트는
export let으로 props를 받고, LiveSvelte가 제공하는liveprop을 통해 LiveView와 통신함 - Svelte 쪽에서는
live.pushEvent로 서버에 이벤트를 보낼 수 있음- 예시에서는 Svelte의 reactive block이
form변경 시"form_updated"이벤트를 서버로 보냄 - LiveView는
handle_event("form_updated", ...)에서 프런트엔드 파라미터를 디코딩하고 changeset을 갱신함 - changeset 검증 오류는 다시
errorsprop을 통해 프런트엔드로 전달됨
- 예시에서는 Svelte의 reactive block이
- 데이터는 Elixir에서 props로 Svelte에 내려가고, Svelte에서는 웹소켓 이벤트로 LiveView에 올라감
- LiveView 프로세스는 언제든 props를 업데이트해 Svelte 컴포넌트를 다시 렌더링하게 만들 수 있음
LiveView와 Svelte 조합의 핵심 속성
- 이 조합의 핵심은 상태ful 백엔드 프로세스와 상태ful 프런트엔드 컴포넌트가 함께 동작한다는 점임
- 세 가지 속성이 조합을 뒷받침함
- 백엔드가 프런트엔드 컴포넌트의 props를 제어함
- 프런트엔드와 백엔드가 모두 상태를 가짐
- 양쪽이 서로 메시지를 시작할 수 있는 비공개 양방향 통신 채널을 가짐
- 첫 번째 속성은 LiveView의 렌더링 방식 덕분에 가능함
- 서버 재렌더링 결과가 클라이언트에 자동으로 push되고 적용됨
- 서버가 JS 부모 컴포넌트처럼 컴포넌트 props를 업데이트할 수 있음
- 두 번째 속성은 LiveView가 Elixir 프로세스라는 점에서 나옴
- Elixir 프로세스는 상태를 캡슐화하는 방식임
- 세 번째 속성은 LiveView가 제공하는 지속적인 웹소켓 연결로 가능함
SPA와의 차이
- 브라우저 라우팅이 모두 백엔드에서 이뤄져 구조가 단순해짐
- 일반 SPA에서는 브라우저 라우트와 API 라우트라는 두 종류의 라우트를 유지해야 함
- 백엔드는 상태를 갖고 현재 라우트와 작업 중인 리소스를 알고 있음
- 각 액션은 상태를 처음부터 다시 만드는 대신, 백엔드 자신의 상태 변경으로 처리될 수 있음
- 프런트엔드와 백엔드의 통신은 비공개이고 결합되어 있음
- 단일 컴포넌트를 지원하기 위한 RPC 호출로 서버의 공개 라우트를 늘리지 않음
- 클라이언트에서
pushEvent를 보면 해당 핸들러가 협업하는 Elixir 모듈 안에 있음을 알 수 있음
- 기능은 주로 두 파일에 나뉨
- 백엔드 모듈은 데이터베이스 조회 같은 백엔드 함수를 호출함
- 프런트엔드는 컴포넌트와 스타일을 가져옴
- 프런트엔드와 백엔드 왕복이 API 모듈, 라우터, 컨트롤러 스택을 지나지 않음
- 통신 절차도 줄어듦
- 백엔드는 props 업데이트만으로 프런트엔드 변경을 알릴 수 있음
- 프런트엔드는 만료 토큰, 타임아웃, 장애 처리용 핸들러 없이
pushEvent를 보낼 수 있음 - 웹소켓이 열려 있으면 서버가 동작 중이고, 닫혀 있으면 LiveView가 전역 “disconnected” 배너를 보여줌
책임 분리와 코드 구조
- 이 구조에서는 프런트엔드 마이크로서비스가 제거된 것처럼 동작함
- 비즈니스 로직은 백엔드에 둠
- 데이터 로딩 방식
- 어떤 데이터를 불러올지
- 정렬과 필터링 방식
- 검증기
- Svelte 프런트엔드 코드는 단순해짐
- 조건 렌더링을 위한
if/end블록 - 애니메이션
- 서버로 보내는 간단한
pushEvent함수
- 조건 렌더링을 위한
- 일반적인 SPA 프런트엔드에는 서버 데이터 처리, 표시용 데이터 준비, 서버 전송용 데이터 준비를 위한
map,reduce,filter로직이 많아질 수 있음 - LiveSvelte 앱에서는 이런 처리를 서버 쪽에서 수행할 수 있음
- LiveView가 Svelte 컴포넌트가 필요한 형태로 데이터를 준비함
- 복잡성이 서버 언어, 서버 데이터 구조, 서버 테스트 스위트 안에 머무름
- LiveView와 Svelte 컴포넌트는 느슨히 결합된 두 요소라기보다 한 기능의 두 절반에 가까움
- LiveView는 해당 Svelte 컴포넌트만 렌더링함
- 그 Svelte 컴포넌트는 해당 LiveView에 의해서만 렌더링됨
순수 LiveView 대비 달라지는 점
- LiveSvelte 방식은 프런트엔드의 상태와 상태 전환을 받아들임
- 프런트엔드와 백엔드 사이의 경계층이 명확해짐
- Svelte의 성숙한 컴포넌트 패러다임을 활용할 수 있음
- 순수 LiveView만으로는 접근하기 어려운 JavaScript 프레임워크 생태계도 사용할 수 있음
- Svelte는 애니메이션 primitives를 제공함
- 더 많은 책임을 프런트엔드로 옮기면서 어색한 중간 지대에 걸친 느낌이 줄어듦
Svelte를 선택한 이유와 LiveView의 위치
- LiveSvelte를 선택한 이유는 React에 비슷하게 완성도 있는 LiveView 라이브러리가 없었기 때문임
- Svelte는 기본 상태와 반응성에서 가볍고 빠른 프런트엔드 프레임워크로 평가됨
- LiveView가 상태 관리의 큰 부분을 맡기 때문에 Svelte 쪽 상태 관리는 단순하게 유지됨
- Svelte의 템플릿 기능도 React보다 선호됨
- 삼항 연산자 대신
if/else를 사용할 수 있음 - 조건부 속성 설정을 사용할 수 있음
- 삼항 연산자 대신
- Svelte 5의 runes는 Svelte를 더 배우기 쉽고 추론하기 쉽게 만들 것으로 기대됨
- LiveView는 프런트엔드 컴포넌트를 렌더링하고, 증분 업데이트를 수행하며, 상태ful 백엔드 프로세스를 유지하고, 웹소켓 API를 제공하는 backend-for-frontend로 가장 빛남
댓글과 토론
Hacker News 의견들
-
멀티플레이어 게임에서 종종 쓰는 패턴은 기본적으로 클라이언트와 서버 양쪽에서 같은 코드를 실행하는 것임
클라이언트 코드는 서버 상태를 예측해서 먼저 실행하고, 서버 상태가 도착하면 클라이언트 상태를 덮어씀
게임에서는 클라이언트가 자기 입력 결과는 꽤 잘 추측할 수 있지만 다른 플레이어 입력은 모르니 “예측”이라는 말이 잘 맞음
하지만 이 방식은 드롭다운 활성화/비활성화나 로딩 스피너처럼 공식 서버 상태를 기다리는 동안 즉시 반응하는 데도 쓸 수 있음
파티클 시스템이나 래그돌처럼 서버에서 아예 돌릴 필요 없는 클라이언트 상태도 많고, 지속적인 서버 연결이 있다면 반응형 패러다임에서도 안 될 이유가 없어 보임- https://territoriez.io/에서 바로 이 방식을 썼음
https://generals.io/의 클론이고 LiveSvelte로 만들었음
사실상 보드게임이고 초당 2틱이라 예측 기능은 필요 없지만, 서버 업데이트 전 게임 상태를 먼저 보여주는 낙관적 업데이트는 사용함
서버와 싱크가 안 맞으면 서버가 게임 상태를 덮어씀
게임 로직은 전부 Elixir 안에 있고, 예측을 제대로 하려면 서버와 클라이언트가 로직을 공유해야 함
아니면 로직을 두 번 쓰게 되는데 그건 재앙으로 가는 길임
조사하진 않았지만 가능한 해법은 모든 게임 로직을 gleam(https://gleam.run/)으로 쓰는 것임
Gleam은 Elixir와 호환되고 JavaScript로도 컴파일 가능해서, 이론상 같은 코드를 서버와 클라이언트에서 실행할 수 있음
다만 “그냥 전부 js로 쓰면 되잖아”라고 해도 꽤 맞는 말이고 나도 아마 동의할 듯함
그래도 얻는 장점은 BEAM과 실시간 분산 시스템에 좋은 기능들을 쓸 수 있다는 것인데, 프런트엔드만 빼면 멀티플레이어 게임에 딱 맞음 - Overwatch처럼 래그돌 물리를 로컬에서 비결정적으로 시뮬레이션하는 게임을 플레이하다 보면, 그걸 제대로 했으면 싶음
누군가 죽어서 시체가 날아가거나 웃긴 자세로 끼어도 다른 사람은 아무도 못 보고, 리플레이에서도 각 클라이언트가 다르게 렌더링해서 다시 볼 수 없음
실시간 녹화로 잡지 못하면 영원히 사라짐
Overwatch처럼 가끔 우스꽝스러운 게임에서는 아무도 같이 못 본다는 게 아쉬움 - 격투 게임은 이런 저지연 충돌 해결 문제를 극단까지 밀어붙이고, 실시간 이벤트 스트림을 조정하는 “netcode” 기법만의 하위 분야가 있음
https://arstechnica.com/gaming/2019/10/explaining-how-fighti...가 훌륭한 설명이고, 관련 논의는 https://news.ycombinator.com/item?id=34399790와 https://news.ycombinator.com/item?id=26289933에 있음
MMO처럼 서버 영속성이 들어가면 입출력 병목도 함께 고려해야 함
https://prdeving.wordpress.com/2023/09/29/mmo-architecture-s...가 그쪽 관점에서 흥미로운 글이고, 논의는 https://news.ycombinator.com/item?id=37702632에 있음 - 그건 그냥 낙관적 업데이트 아닌가? 클라이언트 쪽 로직에서는 오래전부터 흔한 방식 아닌지
- 이건 조심해서 써야 함
예를 들어 라이브/인터랙티브 폼이 페이지의 파생 데이터를 업데이트하면서 잘 동작한 것처럼 보였는데, 나중에 서버 오류나 네트워크 문제로 전부 되돌려지면 곤란함
더 나쁘게는 사용자가 그걸 모른 채 페이지를 닫거나 이동할 수도 있음
- https://territoriez.io/에서 바로 이 방식을 썼음
-
LiveView + Svelte 조합을 좋아함
ElixirConf 2022에서 둘을 결합하는 방법을 발표했지만, live_svelte 기여자들이 실제로 쓸 수 있게 만든 것임
풍부한 사용자 경험을 가진 앱에는 항상 클라이언트 쪽 상태가 필요하다고 봄
특히 뉴욕에 살다 보면 이동 중 네트워크 연결이 당연하지 않음
글에서 다루지 않은 강력한 기능 하나는 Phoenix의 pubsub을 써서 다른 서버에서 발생한 서버 측 상태 변경도 모든 클라이언트에 반응형으로 푸시할 수 있다는 점임
중간 이상 트래픽을 처리하려면 웹 서버를 여러 대 두는 일이 꽤 흔함- 모바일 네트워크도 전반적으로 지연이 크고, 클라이언트에 로컬 우선 캐싱 시스템이 필요한 경우가 많음
LiveView 같은 사용자 경험을 원하면서도 불안정한 네트워크에 견디고 싶다면, Triplit[1]에 Svelte 5 바인딩을 추가했음
클라이언트 쪽에서 쿼리를 작성하고 웹소켓으로 서버와 동기화할 수 있음
Elixir 개발자에게는 그리 매력적이지 않을 수 있지만, Svelte가 중심이라면 Triplit은 좋은 선택임
[1] https://www.triplit.dev/docs/frameworks/svelte - 네트워크가 간헐적으로 끊기는 상황에서 LiveView 페이지는 오프라인일 때 얼마나 쓸 만한가?
- 모바일 네트워크도 전반적으로 지연이 크고, 클라이언트에 로컬 우선 캐싱 시스템이 필요한 경우가 많음
-
새 행이 들어올 때 그냥 테이블에 푸시하고 LiveView가 클라이언트를 업데이트하게 하는 방식은, 그 행들이 상호작용 가능한 업무용 앱에서는 피하는 게 좋음
인지 지연 때문에 사용자가 잘못된 항목을 클릭하거나, 잘못된 고객에게 이메일을 보내거나, 잘못된 거래를 환불하기 쉬움
선호하는 사용자 경험은 “데이터가 변경되었습니다. 여기를 눌러 새로고침하세요” 같은 고정 배너임
급하면 새 행을 추가 전용으로 렌더링하고 스크롤 위치가 바뀌지 않게 해야 함- 새 줄을 천천히 애니메이션으로 넣고, 새 데이터가 들어오는 동안 클릭을 비활성화하면 이 방식도 가능함
-
BeaconCMS에서 Svelte와 LiveView를 함께 쓰고 있음
클라이언트에서 UI를 더 세밀하게 제어해야 하는 좋은 사용처는 분명 있지만, 팀이 모든 것에 Svelte + LiveView를 전면 도입하는 건 조심해야 함
Phoenix에서도 LiveView가 항상 답은 아니고, 때로는 일반 렌더 페이지로 충분함
모든 걸 전부 아니면 전무로 고르면 안 됨
글에서 말하듯 “LiveView 방식”에서 벗어날 만한 좋은 사용처가 있음
왕복 시간이 1,000ms라면 다른 것도 고려해야겠지만, 비용 같은 이유로 지리적으로 가까운 서버를 둘 수 없는 팀도 있으니 클라이언트 쪽 상태 관리가 해법이 될 수 있음 -
클라이언트 상태를 관리하는 대신 클라이언트와 서버 양쪽 상태를 관리하겠다는 건가?
또 다른 API를 만들지 않아도 된다는 장점은 있어도, 개선처럼 보이진 않음- 웹 애플리케이션의 상태는 그렇게 단순하지 않음
항상 클라이언트가 추적해야 하는 상태와 서버가 추적해야 하는 상태가 있고, 여기에 메모리에만 있으면 되는 일시적 상태와 세션 사이에 유지되어야 하는 상태가 겹침
이걸 관리하는 방식은 거의 무한하고, JavaScript에는 “뭘 해도 정답이 없다” 보니 프레임워크가 정한 방식을 쓰거나 임시방편, 즉 혼돈으로 처리하게 됨
마지막으로 썼던 복잡한 단일 페이지 앱에서는 클라이언트가 페이지를 로드할 때마다 동기화를 수행하게 했음
모든 로컬 상태나 자산에는 날짜 기반 해시가 붙어 있었고, 서버의 값과 다르면 서버가 업데이트된 버전을 내려줬음
단순 변수든 거대한 JSON 객체든 이미지/오디오 블롭 전체든 마찬가지였음
클라이언트가 서버 상태 변경이 필요한 일을 하면 WebSocket으로 그 상태 업데이트를 보냈고, 앱의 99%가 WebSocket 기반이었음
특별한 프레임워크나 패턴은 쓰지 않았고, 코드를 쓰다가 “이건 서버가 추적해야 한다” 싶으면 새 상태를 서버에 메시지로 보내고, 페이지 로드 시 동기화할지는 서버가 결정하게 했음
이런 종류의 상태 관리에서는 이 정도가 거의 가장 단순한 메커니즘이라고 봄
WebSocket은 상태 관리에 정말 큰 도움이 됨 - 결국 이런 패러다임의 어떤 형태가 이길 것 같음
실제 애플리케이션은 클라이언트와 서버 양쪽에 상태가 필요함
서버는 클라이언트를 신뢰할 수 없으니 권위 있는 상태를 가져야 하지만, 클라이언트도 왕복 없이 사용자 상호작용에 반응해 다시 렌더링할 수 있어야 함 - 어떤 것들은 완전히 클라이언트 쪽에서 처리하는 편이 더 나은 경험이 됨
전부 그렇게 하지는 말고, 말이 될 때만 쓰면 됨
또 마음에 드는 점은 Heex 대신 Svelte를 템플릿 언어처럼 쓸 수 있다는 것임 - 새로운 세대가 ColdFusion, Web Forms, JSF, PHP, Spring, Rails를 다시 발견하는 것에 가까움
- 이건 다른 소프트웨어 생태계에서 하는 최적화와 다르지 않음
필요하지 않으면 할 이유가 없고, 필요해지는 이유는 성능과 사용자 경험 때문임
웹 애플리케이션에 사용자 상호작용이 많아 클라이언트 JavaScript가 필요하고, 실험이 많아 요청마다 번들이 달라진다면, 클라이언트로 보내는 JavaScript 양을 줄이기 위해 서버와 클라이언트에 로직을 나누고 싶어짐
그렇지 않으면 애플리케이션이 만날 수 있는 모든 경우의 수가 들어간 MB 단위 JavaScript 번들을 갖게 됨
Photoshop 웹 버전처럼 사용자가 초기 로딩 시간을 이해하는 앱이면 괜찮을 수 있지만, Stripe나 Gmail처럼 즉시 로드되길 기대하는 앱에서는 페이지의 초기 지연을 줄이고 싶음
전부 서버로 옮길 수도 있지만, GitHub가 겪었듯 즉시 반응해야 할 사용자 동작이 서버 왕복을 필요로 하면서 페이지 상호작용성이 나빠짐
그러면 빛의 속도와 서버 분산이 문제가 됨
async import 같은 것으로 번들을 지연 로드할 수도 있지만, 지연 로드된 JavaScript가 자기 데이터를 가져오려고 또 요청을 보내면서 폭포수 문제가 생김
이런 문제들을 모두 만나면 결국 클라이언트와 서버 사이의 로직/상태 공유라는 복잡한 문제를 쉽게 다루는 해법을 찾게 됨
- 웹 애플리케이션의 상태는 그렇게 단순하지 않음
-
이 접근에는 “서버가 사용자에게 가까울 수 있는 데는 한계가 있다”는 말 그대로 빛의 속도 제한이 있음
다음 단계는 서버를 WebAssembly로 컴파일해서 클라이언트에 보내는 것임
그러면 실제 서버 응답을 기다리는 동안 낙관적으로 응답을 렌더링할 수 있음
좀 미친 소리처럼 들리지만, 실제 프로젝트에서 해냈고 마법 같았음- 영속성은 어떻게 하나? 백엔드의 핵심 목적은 그거라고 봄
클라이언트에서 서버를 실행해도 그건 해결되지 않고, 결국 영속성의 네트워크 지연은 그대로 남음
최종적으로 이겨야 하는 서버 상태도 그 영속성임 - 과설계처럼 들림
별 이유 없이도 많은 과설계가 실제로 구현되곤 함 - 그냥 Service Worker를 쓰면 안 되는 이유가 있나?
- 자세한 내용을 더 공유할 수 있나?
- 현대 멀티플레이어 게임이 하는 클라이언트 측 예측과 많이 비슷하게 들림
- 영속성은 어떻게 하나? 백엔드의 핵심 목적은 그거라고 봄
-
LiveSvelte를 만들었으니 궁금한 게 있으면 알려주면 됨
- LiveSvelte를 쓴다면 그래도 JS Hooks를 써야 할 이유가 있을까?
-
전반적으로 항상 이런 모델로 앱을 만들고 싶었음
이벤트 지향, 서버와 양방향 실시간 업데이트, 순서 있는 이벤트, 로컬/원격 상태 같은 방식임
LiveView는 몰랐고 Erlang 계열 언어도 써본 적 없지만, 확실히 뭔가 잡고 있는 것 같음
전통적인 요청-응답 모델은 일관성과 오래된 데이터 문제를 미묘하게 자주 일으킴
지난 10년이 함수형 프로그래밍 개념을 주류 언어에 통합하는 시기였다면, 다음 10년은 상태 있는 메시지 지향 프로그래밍을 주류 풀스택에 통합하는 방향이면 좋겠음- JavaScript(http://liveviewjs.com), Go(https://github.com/canopyclimate/golive), Java(https://github.com/floodfx/undead), Python(https://github.com/ogrodnek/pyview)으로 작성된 LiveView 백엔드가 있음
참고로 앞의 세 개는 내가 만들었거나 만드는 데 도왔음 - asdf로 erlang/BEAM/OTP와 elixir를 설치할 수 있고, 도구 사용 경험이 조금 있다면 몇 분이면 됨
가이드를 따라 하면서 조금 만져보면, 대략 두 시간 안에 브라우저에서 돌아가는 기초적인 Phoenix 채팅 앱을 띄울 수 있을 것임 - “상태 있는 메시지 지향, 혹은 반응형 프로그래밍을 주류 풀스택에 통합”하는 건 Rails가 용어를 재정의하기 전의 MVC라고도 불렸음
- JavaScript(http://liveviewjs.com), Go(https://github.com/canopyclimate/golive), Java(https://github.com/floodfx/undead), Python(https://github.com/ogrodnek/pyview)으로 작성된 LiveView 백엔드가 있음
-
좋은 해법임
내 앱에서는 LiveView와 함께 재사용 가능한 Stimulus 컨트롤러를 쓰고 있고, 이것도 매끄럽게 동작함
전반적으로 LiveView로 개발하는 건 즐겁지만, 실제 시나리오에서 더 많이 쓸수록 Hotwire 같은 무상태 HTTP 프레임워크의 장점도 더 느끼게 됨
더 성능 좋고 재연결에 강한 느낌이며, 안정성을 위해 사용자 가까이에 서버를 더 배치할 필요도 줄어듦- Stimulus/Turbo가 처음 발표됐을 때, 글쓴이가 설명한 문제들을 해결하는 데 도움이 되길 꽤 기대했음
하지만 Stimulus는 클라이언트에 상태를 유지하는 우아한 방법을 제공하지 않음
“Stimulus 애플리케이션의 상태는 DOM의 속성으로 존재한다”는 식이라, 바닐라 JS나 jQuery보다 낫지 않음
실제 프로젝트에서 Stimulus를 써본 건 아니니, 값과 변경 콜백이 처음 생각보다 더 나은 경험일 가능성은 있음 - 나도 Stimulus와 LiveView를 함께 씀
이 조합은 복잡도 수준이 딱 맞고, Svelte는 LiveView와 함께 쓰면 애초에 문제가 아닌 것들까지 많이 다루는 느낌임
필요한 건 바닐라 JS나 그 위의 얇은 계층이지, 전체 프레임워크가 아님
어차피 JavaScript를 많이 쓰지 않아도 됨
운영 중인 앱의 JS는 200줄이 넘지 않고, Stimulus 컨트롤러 몇 개는 없앨 수도 있을 듯함
LiveView는 그만큼 좋음
Stimulus-LiveView 훅 어댑터도 아주 해키하게 만들어서, Stimulus 컨트롤러가 LiveView 프로세스에 직접 이벤트를 보낼 수 있게 했음
그리고 LiveView는 fly.io식 Kool-Aid를 너무 마신 게 아니라면, 지리적으로 분산된 서버를 꼭 요구하지 않음
연결 끊김도 훌륭하게 처리함
무중단 업데이트를 지원하려고 별도로 한 일도 없었음
클라이언트가 WebSocket 연결을 잃고 새 버전으로 재연결하고 상태를 복원하는 과정이 전부 기본으로 자동 처리됨
더 뭘 바라야 하나?
- Stimulus/Turbo가 처음 발표됐을 때, 글쓴이가 설명한 문제들을 해결하는 데 도움이 되길 꽤 기대했음
-
우리 회사는 LiveView와 Alpine을 함께 써서 큰 이점을 얻었고, 특히 클라이언트에서
eval을 허용할 수 없어 CSP 모드를 사용함
처음 LiveSvelte를 봤을 때는 매우 새롭고 검증이 덜 된 것처럼 보였고, 엄격한 CSP 환경에서 해결되지 않은 영향도 있었음
지금은 꽤 유용해진 것 같아 반가움
우리 패턴은 대략 LiveAlpine이라고 부를 수 있음
컴포넌트에 HTML이 필요하면 HTML Component를 쓰고, 서버 측 상태가 있으면 Live Component를 씀
클라이언트 측 동작이나 상태가 필요하면 Alpine 컴포넌트도 정의함
서버에서 클라이언트 이벤트를 받거나 HTTP 요청을 해야 하면 Phoenix Hook도 정의함