이번 글에서 다뤄볼 주제는 PWA
입니다.
PWA
라는 용어는 Progressive Web App
의 약자입니다. 이 용어는 이제 많이 퍼져서 많은 사람들이 사용하고 있습니다. 하지만 PWA
라는 용어를 쓰는 사람마다 사용하는 기능이 무엇인지에 따라 그 의미가 종종 바뀌기도 합니다.
따라서 우리는 먼저 PWA라는 용어를 파헤쳐볼 필요가 있습니다.
먼저 Progressive Web App
는 앞으로 다가올 웹의 가능성의 상징이라고 표현할 수 있습니다. Progressive
은 점진적인, 진보적인
이라는 뜻을 가지고 있기 때문에 PWA
을 그대로 해석하면 점진적인 웹 앱
이고 이는 기본적인 웹에 점진적으로 여러가지 기능을 추가할 수 있음을 의미합니다.
그냥 좋은 비타민을 한껏 섭취한 웹 사이트다.(앨릭스 러셀)
PWA는 새롭게 자라는 기술에 이름을 붙인 것입니다. 마치 이단 마콧(Ethan Marcotte)이 반응형 웹 디자인에 이름을 붙인 것과 같은 이유이죠. 모두가 열광할 수 있는 무언가를 던져준 것입니다.
정의를 명확하게 하지 않는 것이 PWA에 관심을 끌어모으는데 도움이 될 수 있습니다. 하지만 PWA를 기획하고 개발하려면 PWA가 무엇인지 명확하게 파악해야합니다.
프로그레시브 웹 앱은 웹이 앱과 같은 사용자 경험을 제공할 수 있다는 믿음에 기반합니다. 그리고 이 믿음을 실현하기 위해 2015년에 이르러서는 크롬에 설치 가능한 웹 앱이라는 기능을 포함시켰고, 곧 다른 웹 브라우저도 이 기능을 넣기 시작했습니다. 기술이 먼저 나왔지만 이를 가리킬 용어가 없었습니다. 온라인 경험을 더 가치있게 만들어주는 일련의 복잡한 기법들을 간단히 부를 수 있는 방법이 필요했습니다. 그래서 이 웹 앱을 프로그레시브 웹 앱(Progressive Web App)이라고 부릅니다.
베리먼과 러셀은 PWA의 특징을 아홉가지로 문서화했습니다.
위 기능이 모두 포함되어야 PWA가 되는 것은 아닙니다. 단지 PWA는 점진적 향상(Progressive Enhancement)전략으로 제작됩니다. 점진적 향상이란 PWA로 작동하는 최소한의 서비스를 만들고 점차 다른 기능들을 추가하는 개발방법을 말합니다.
그렇다면 PWA로 작동하는 최소한의 서비스를 만들기위해 최소한으로 갖춰야하는 부분을 기술적 관점에서 알아보겠습니다.
먼저HTTPS
입니다. 프로그레시브 웹 앱은 HTTPS를 이용해 안전하게 서비스되어야 합니다. 프로그레시브 웹 앱의 대부분의 기능은 서비스 워커(Service Worker)
를 통해 제공되는데 서비스 워커
는 HTTPS에서만 사용할 수 있습니다.
서비스 워커
란 웹 브라우저의 네트워크 요청과 자원 관리자를 개발자가 중간에서 직접 제어하도록 해줍니다. 서비스 워커를 이용하면 오프라인에서도 작동하는 웹 페이지를 제작할 수 있습니다.
마지막으로 Web App Manifest
입니다. 프로그레시브 웹 앱이 검색될 수 있도록 해주는 간단한 파일입니다. 앱의 이름, 시작 URL, 아이콘등 웹사이트를 앱으로 인식할 수 있게 해주는 여러가지 정보를 입력합니다.
위 세가지 조건을 충족하면 PWA라고 할 수 있습니다. 다른 기능은 PWA의 정의처럼 점진적으로 붙여나갈 수 있습니다.
서비스 워커와 Web App Manifest에 대해서 더 자세히 알아보겠습니다.
서비스 워커는 자바스크립트 파일입니다.
PWA의 핵심은 서비스 워커에 있습니다. 서비스워커는 브라우저가 백그라운드에서 실행하는 스크립트로, 웹 페이지와는 별개로 작동하며 웹 페이지 또는 사용자의 인터랙션이 필요하지 않은 기능만 제공합니다.
서비스 워커는 브라우저와 네트워크 사이에서 프록시 서버 역할
을 합니다. 예를 들어 브라우저에서 fetch
요청을 했을 때 서비스 워커는 먼저 그 요청에 대한 캐시를 가지고 있는지 판단합니다.
캐시를 가지고 있다면 브라우저에 바로 캐시를 전달할 것이며, 캐시를 가지고 있지 않다면 서버에 그 요청을 보낼 것입니다. 캐싱기능을 활용함으로써 오프라인에서도 동작하는 웹을 만들 수 있습니다.
Fetch fetch란 web resource에 접근하기 위해 행해지는 모든 request action을 의미합니다. 서비스 워커는 fetch를 통해 발생하는 모든 http request를 중간에서 가로챌 수 있습니다.(Proxy)
서비스 워커에서 다양한 캐싱 전략을 활용할 수 있습니다.
manifest
는 json 파일입니다. 이 파일은 아래와 같이 웹 앱 설치 배너를 트리거하기위해 필요합니다. 적어주는 옵션에 따라 앱의 이름, 아이콘, 테마 색깔등을 정의할 수 있습니다.
흥미로운 부분은 manifest의 display 속성을 사용해 프로그레시브 웹 앱을 설치했을 때 브라우저에서 어떻게 보여질지 제어할 수 있다는 것입니다.
주소 막대, 뒤로가기 버튼과 같이 웹 페이지를 둘러싼 사용자 인터페이스 요소를 크롬(Chrome)
이라고 부릅니다.
구글의 웹 브라우저는 디자인에서 크롬 요소를 줄이는 것이 의식적인 목표였는데, 그래서 역설적으로
크롬
이라는 이름을 갖게 되었습니다.
display 속성을 이용해 설치된 PWA에 한하여 브라우저에서 크롬 영역을 없애거나 필요한 부분만 표시할 수 있습니다. 위와 같이 standalone이나 fullscreen 속성을 사용하면 웹 앱을 마치 네이티브 앱인 것처럼 보여지게 할 수 있습니다.
이렇게 PWA를 위해 필요한 Service Worker와 Web App Manifest에 대해 알아보았습니다. 여기에 덧붙여서 PWA의 Web Push(푸시 알림), 백그라운드 동기화와 같은 기능을 사용하면 웹에서 네이티브 앱의 기능을 사용할 수 있습니다.
마지막으로 소개드리고 싶은 것은 제가 개발하고 있는 MOZI라는 프로젝트에서 도입한 Offline First Architecture입니다.
사용자가 애플리케이션을 사용할 때 네트워크에 연결되어 있는지 판단하는 것은 매우 어렵습니다.(가령 와이파이에는 연결된 상태인데 실제로 인터넷 접속은 되지 않는 상황) 좋지 않은 네트워크 상황에서는 웹 브라우저에 애플리케이션을 그리는데 필요한 정적 파일을 받아오는 것 또한 매우 느리게 동작할 수 있습니다.
이런 상황에 대비하기 위해 Cache를 사용합니다. 정적 파일들을 캐시하면 네트워크가 안좋은 상황에서 Cache에서 응답을 하기 때문에 빠르게 웹 페이지를 그릴 수 있습니다.
정적 파일들을 캐시하면 오프라인 상태에서 화면을 그릴 수는 있지만 오프라인 상태에서의 사용자와 앱의 인터랙션을 서버에 전달할 수 없습니다.
Cache 인터페이스와 HTTP Cache는 관련이 없습니다.
오프라인 상태에서 사용자가 어떤 행동을 했는지 기록하기 위해 IndexedDB를 사용할 수 있습니다. 브라우저 기반 데이터베이스인 IndexedDB는 객제 저장소에 key-value 쌍으로된 레코드를 저장할 수 있습니다. 저장소에는 다수의 객체를 저장할 수 있는데 객체는 JavaScript 객체, 불린, 숫자, BLOB(Binary Large Object)및 JavaScript가 처리할 수 있는 대부분의 데이터 포맷 중 하나입니다.
IndexedDB는 사용자의 연결 상태와 관계없이 접근 및 조작 가능하므로 오프라인에서 사용자의 행위를 객체 형태로 저장할 수 있습니다.
IndexedDB는 동일 출처 정책(Same-Origin Policy)를 따릅니다. 사용자는 특정 사이트에서 작성된 데이터가 다른 사이트에 노출될 것을 걱정하지 않아도 다른 사이트에 방문할 수 있습니다. 다시 말하면 자신의 도메인 내에서는 데이터를 읽고 쓸 수 있지만, 다른 도메인의 IndexedDB에 기록된 데이터는 접근할 수 없습니다.
지금까지 내용을 시각화하면 위와 같습니다. 브라우저에서 발생하는 Interaction을 객체형태로 표현해서 IndexedDB에 저장(예: Todo를 만들면 그 Todo를 객체 형태로 IndexedDB에 저장)
IndexedDB에 데이터를 저장했으면 인터넷이 연결되면 그 데이터를 서버에 동기화 시켜줘야합니다. 이 때 PWA의 주요 기능인 백그라운드 동기화를 활용할 수 있습니다. 동기화 이벤트와 관련된 상호작용은 SyncManager
를 통해 이루어집니다. 백그라운드 동기화는 브라우저를 닫아도 진행되기 때문에 동기화 이벤트를 등록하면(브라우저, 서버스 워커 모두에서 동기화 이벤트를 등록할 수 있습니다.) 다음과 같은 경우에 sync 이벤트를 발생시킵니다.
- 동기화 이벤트 등록 직후
- 사용자 상태가 오프라인에서 온라인으로 변경될 때
- 성공적으로 완료되지 않은 동기화 이벤트가 있을 경우, 매 분마다
이러한 백그라운드 동기화의 특성에 기반하여 오프라인에서 진행된 작업들 중 서버에 동기화되지 못한 작업들에 대하여 sync 이벤트를 등록해 놓으면 네트워크가 온라인이 되거나 또는 주기적으로 서버에 동기화하지 못한 작업에 대하여 동기화를 수행할 수 있습니다.
결국 Offline First Architecture는 위와 같습니다. 사용자와의 인터렉션을 서버가 아닌 IndexedDB에 저장합니다. 이때 sync 이벤트를 등록해줍니다.
sync 이벤트가 등록되면 등록된 동기화 작업이 완료될 때까지 수행합니다.
Offline First Architecture에서 중요한 부분은 데이터를 불러올 때 입니다. 데이터를 불러오는 요청을 Server에게 한 후 그 데이터를 바로 쓰는 것이 아닌 IndexedDB에 저장합니다. 그리고 IndexedDB에서 데이터를 불러옵니다.
왜 그 데이터를 바로 활용하지 않고 IndexedDB에 저장하는 과정을 거친 후에 그 데이터를 활용하는 것일까요? 저는 그 이유를 서버 상태라는 개념을 통해 설명하고 싶습니다. 서버 상태는 사용자의 제어를 벗어난 위치에서 원격으로 유지되고 소유권을 공유하기 때문에 변경될 수 있습니다. 즉 오프라인 상태에서 사용자가 IndexedDB에 조작한 데이터들은 outdated된 상태일 수 있는 것이죠.
이러한 이유로 서버 상태를 받아오면(네트워크가 연결되면) 그 상태를 IndexedDB에 저장하여 최신 상태의 데이터를 로컬에 유지할 수 있고 네트워크가 끊어지더라도 로컬에 데이터를 가지고 있기 때문에 문제가 없습니다.
또 다른 장점으로는 데이터를 가져오는 로직을 일반화할 수 있다는 것입니다. 오프라인이든 온라인이든 즉 네트워크 요청이 실패했든 성공했든간에 상관없이 우리는 데이터를 무조건 IndexedDB에서만 가져오므로 fetch 요청이 성공했는지 실패했는지 케이스 구분을 하지 않습니다. 단지 IndexedDB에서 데이터를 불러오는 것뿐입니다.
위 생각의 흐름이 제가 프로젝트에서 도입해본 Offline First Architecture에 대한 설명이었습니다.
마지막으로 PWA에 대한 생각을 정리하며 마치겠습니다.
모든 웹 사이트는 PWA가 될 수 있습니다. PWA를 적용하면 웹사이트가 점진적으로 진화할 수 있는 가능성을 열어두고 서비스의 목적과 방향에 맞게 유동적으로 앱을 만들 수 있습니다.
Make Your Web Progressive.