글 작성자: 택시 운전사
반응형

해당 글은 제가 DEVIEW 2020의 발표 영상을 보면서 문서화한 글입니다. 발표 영상을 보시려면 아래 링크를 확인해주세요.

묻고 한 벌로 가

한 벌의 코드로 모바일 웹/모바일 앱/PC 웹 서비스 확장 개발기

- 여러 환경으로 서비스를 확장하면서 겪었던 시행착오를 공유

- 네이버 플레이스 서비스의 기술 스택: JavaScript 웹 fullstack

1. 왜 코드 한 벌을 여러 서비스에 넣었을까?

1.1 플레이스와 지도

  • 플레이스지도는 원래 다른 서비스
  • "검색에서 업종별 특화 화면을 보여주면 어떨까?"라는 생각에서 플레이스 서비스 시작
    • 식당, 숙박, 미용실, 관광 명소 등

1.2 플레이스/지도 통합의 배경

  • 플레이스 서비스의 성장 업종별로 특화 및 세분화
    • 해수욕장, 은행, 병/의원, 주유소, 약국(공적마스크), 미용실(스타일 검색)
  • 문제점: NAVER 검색 결과와 지도 검색 결과가 다르게 됨

1.3 어떻게 통합할까?

가장 빠르고 효율적인 방법 찾기

  • 중복 개발 피하기 -> 플레이스 서비스를 지도 앱과 PC 지도에 넣자
  • 코드 재사용할 수 있도록 기존 화면을 그.대.로. 집어 넣으면 어떨까?
  • 지도앱에 Webview 형태로 플레이스 서비스를 넣으면 되지 않을까?

2. 지도앱(iOS/Android) 장소결과를 Native에서 Webview로 변경하기

2.1 어느 영역을 Webview로 전환하지?

설계 조건

  • 사용자가 어색함을 느끼지 않아야 한다.
  • 플레이스는 가급적 독립적인 영역으로 고립시켜야 한다.
  • Native와 Webview의 장점을 잘 살릴 수 있어야 한다.

Native인 지도 판과 장소 검색 결과를 담당하는 플레이스를 Webview로 보여주자!

2.2 Webview 설계

장소 검색 결과는 플레이스 Webview가 담당하고 그 외는 지도앱에서 담당한다.

  • 플레이스 목록 페이지 & 업체 페이지에서 가져오자!
  • 지도앱 특화 기능들은 분기 처리로 대응한다.

앱으로 들어가면서 바뀐 점들

2.3 지도앱 + 플레이스 검색 Flow

지도앱 + 플레이스 검색 Flow

2.4 Webview -> Native 통신

목록 결과를 지도에 마커로 그려줘야 함

  • Webview <-> Native 어떻게 통신할 것인가?
  1. (aOS) App scheme 사용 (inapp://)
  2. (iOS) webkit.messageHandler 사용

Native에서 Webview 호출 방식

  • 기본: url 호출
    • 문제: '현 지도에서 검색'터치 시 깜빡임
    • 원인: Native -> Webview - url 변경 호출
      • 서버로 요청이 넘어가서 SSR된 결과를 그린다.
      • SPA인데?
      • 보는 화면에서 목록만 업데이트 시켜주기
    • 해결: Webview에서 Global 함수 제공
      • SSR이 아닌 CSR로 업데이트

Webview 밑에 깔려 있는 지도판을 터치 할 수 있어야 한다.

  • Webview 위에 touch 이벤트를 받을 수 있는 가상의 layer를 하나 더 두자
  • Layer 사이즈는 Webview에서 전달하는 이벤트 기반으로 동작
  • Webview가 스크롤 될 때마다 높이값을 Native에 전달

Q. 왜 Webview 영역 전체를 스크롤하지 않았나?
A. Webview 영역을 위로 올려 최상단이 되었을 때 내부 system scroll로 전환되는 사용성이 좋지 않음

Native 카드와 Webview를 묶어서 자연스럽게 보이도록 하자!

3. PC지도 서비스(Angular)에 플레이스 서비스(React) iframe 으로 집어넣기

3.1 어떻게 통합할 것인가?

PC지도 서비스에 플레이스 서비스를 넣는 초기 기획안

  • 1안) 플레이스는 API만 제공 & 화면은 지도웹에서
  • 2안) 플레이스 화면을 재사용 & 지도웹은 마커
    • 2-1안) Angular에 React Component 포함
    • 2-2안) HTML import(link rel) -> deprecated
    • 2-3안) iframe 으로 포함

3.2 iframe 괜찮을까?!

단점)

  • url 문제
  • debugging 어려움
  • 보안 조심

장점)

  • 두 영역을 분리하여 고립시키기에는 오히려 좋다.
  • cross domain components 조합

3.3 Angular + React

지도 서비스(Angular)와 플레이스 서비스(React)의 공존

  • iframe 사용으로 서비스 간 고립
  • library/framework가 달라서 발생하는 문제는 없음
  • 서비스 간에 통신을 어떻게 할 지가 더 중요

3.4 PC지도 + 플레이스 검색 flow

PC지도 + 플레이스 검색 flow

3.5 서비스간의 통신

단순한 통신 흐름 지향

  • 지도 -> 플레이스
    • iframe URL 변경
  • 플레이스 -> 지도
    • postMessage() 사용

3.6 PC서비스에서 모바일과 달라지는 점

1) 가로 스크롤

  • 가로 system scroll

  • PC에서 가로 스크롤은 익숙하지 않음

  • IE11 스크롤 커스텀 노출이 어려움

    • @egjs/flicking + 좌우 버튼 추가

2) 무한 스크롤 관련 처리

  • 모바일: 무한 스크롤
  • PC: 페이징
  • wrapper component로 대응

3) PC만 지원하는 이벤트처리

  • mouseenter 이벤트
  • mouseleave 이벤트

4) 사용성 때문에 변경되는 부분

  • 동작 환경과 사용성에 맞춰 변경
    • 지도 탭 삭제, 뒤로가기 버튼 삭제, MY 탭 삭제 등
  • 넓은 화면을 활용
    • 포토뷰어 / 거리뷰
  • 외부 페이지 이동
    • iframe 내가 아닌 새 창에서 외부 페이지가 열림
  • 로그인이 필요한 경우
    • iframe 본 창에 로그인 처리 요청
    • 본 창 & iframe 새로고침(로그인 쿠키 반영)

3.7 Internet Explorer 11 대응

Build target 변경

  • 모바일

4. 한 벌의 코드로 여러 서비스 제공은 어떻게 할까?

4.1 플레이스 서비스 개발 스택

JavaScript Web Fullstack

  • TypeScript
  • Node.js with Koa
  • React with hooks
  • GraphQL with Apollo
  • kubernetes with NCC

4.2 어떻게 분기처리 할 것인가?

지속 가능한 코드 = 유지보수하기 쉬운 코드

  • 서비스 환경을 체크하는 if/else 분기는 되도록 적게
  • component 단위를 작은 기능에 집중해서 작게 만든다.

서비스 운영이 쉬운 환경 구성

  • 코드는 재사용
  • domain & server & build는 분리

4.3 배포 환경 분리

독립된 별도 서버군으로 별도 도메인으로 대응

  • 코드는 재사용하지만 서비스간의 독립성은 보장

    • webpack build를 별도로 한다.
    • 환경별로 entry 포인트를 따로 가져간다.
  • API 레벨의 분기

    • GraphQL의 장점을 살리자
      • GraphQL은 versionless 추구
        • 하위 호환성을 지키기 위해 field type 변경 X
        • 변경이 필요한 field는 신규로 추가하여 대응
      • 화면별로 필요한 field를 정의하고 호출하는 것으로 정함
      • GraphQL resolver는 client가 어떤 환경인지 몰라도 되도록 관리
      • 분기의 권한은 client에게 있다.
      • GraphQL query 레벨에서 device 별 분기 처리 진행
        • @skip, @include 사용

5. 성능 개선

5.1 이미지 사이즈를 줄여야 한다.

사진이 많은 건 좋은데 많이 느려졌다.

  • 빠르고 가벼운 화면 VS. 사용자에게 좋은 정보
  • 이미지 최적화

5.2. 첫 로딩 사이즈를 줄여야 한다.

이미지 lazy loading & Component lazy loading

  • 화면에 보이는 부분만 loading
  • DOM 최적화 + 이미지 load 최적화
  • react-lazyload 사용

Dynamic Loading

  • code splitting - asset size 최적화
  • @loadable/component
  • react-router와 조합 -> path 별 resource 분리

SSR인데 왜 화면이 늦게 보일까?

  • 플레이스 페이지 노출여부 제어는 지도에서 한다.
  • 플레이스 페이지 로드 완료는 message로 플레이스에서 지도로 전달
  • 원인: message 전달 타이밍 이슈
    • CSR 시점에 message 전달 -> SSR의 이점 못살림
    • SSR load 후 바로 로드 완료 message 전달로 수정
  • 단점
    • 화면은 미리 보이지만 interaction은 CSR 후 가능
    • "SSR과 CSR 사이의 시간 < 사용자가 화면을 인식하는 시간"이기 때문에 괜찮다고 판단

5.3 Rendering 최적화

스크롤 이벤트로 인한 React Component re-render 최적화

  • 상위 Component에 걸려있는 scroll 이벤트로 인한 불필요한 re-render
  • component 별로 스크롤 이벤트가 필요한 부분만 처리하는 hook 생성

5.4 스크롤 최적화

IntersectionObserver를 이용한 무한 스크롤 최적화

  • 무한 스크롤 시 언제 추가 contents 로딩 할건지 확인
  • 기존: scroll event + getBoundingClientRect() 사용
  • 개선: IntersectionObserver() 사용 - async non-blocking API

장점

  • frame 안정적, 저사양 폰에서 효과가 좋음
  • 코드 개선 효과

6. 앞으로 해야할 일

  • 개발 후 확인해야 할 환경이 많다 -> 테스트 자동화
  • polyfill 관리 불편 -> 환경별 자동화
  • 업종 추가/개선
  • 끊임 없는 Refactoring/성능 개선/사용성 개선
반응형