Booking Web with React.js

Coupang
Bali 팀

김태희(로토/roto)

  • booking-web service
  • Architecture
  • Front-end tools
  • Result
  • Tips
  • Q & A

table of contents

Booking Web

  • 외부 API를 통해 해외호텔에 대해 실시간으로 검색하고 예약할 수 있는 서비스

Booking Web이란?

 비동기 렌더링의 필요

  • 외부 연동을 통한 API 호출이 많아서 ajax를 통한 비동기 렌더링이 많음
  • 사용자 경험 측면에서도 바뀌는 부분만 렌더링 하는 게 좋지 않을까?

반복되는 화면 요소

Home

TDP

TLP

Hotel Not Found

  • 뭘 쓰던 좋으니까 빨리 만들어라

PO 님의 요구사항

팀 개발자들의 요구사항

  • 상식적이고 모던하게 개발하자
  • 요새 좋은 도구들 많은데 우리도 좀 써보자

Front End Tools

  • jsx transpiling 목적
  • es 6 사용
    • class
    • arrow function
    • block scope variable
    • string template
  • es6 import 키워드를 통해 js, css bundling + minify

webpack-dev-server 

  • 로컬에서 개발할 때만 사용
  • gulp watch를 대체
  • hot loader 지원
    • 파일 변경 감지해서 자동으로 현재 화면에 반영
    • 새로고침 주도 개발 일부 탈피

and dummy server

  • --dummy 옵션으로 webpack-dev-server 실행 시 같이 실행
  • 별도의 tomcat 서버 없이 webpack-dev-server만 띄워도 개발할 수 있게 만드는 게 목적
  • node.js의 express 를 이용해 url end point 및 api proxy, dummy json 처리
  • selenium driver 기반의 e2e test tool
  • build / deploy에 대한 제약이 아닌 최소한의 보호장치로 사용
  • bali team jenkins를 통해
    bali.booking-web.build -> bali.booking.web.deploy ->
    bali.booking-web.e2e.test 를 통해 테스트 실행

테스트가 깨지면....

React Developer Tools

  • Chrome, Firefox 지원
  • React Component 기준으로 DOM Tree를 볼 수 있음
  • 특정 Component의 현재 state 및 props를 볼 수 있고 state 직접 수정도 가능하다.

esdoc

Architecture

Rendering Flow

Project Structure - Server

  • booking-web-interfaces
    • 전형적인 coupang의 vitamin 기반 Spring MVC
    • server side rendering을 위한 hbs

Project Structure - Server

  • vitamin skeleton 중 interfaces만 사용
  • 비즈니스 로직은 Booking-API 에서 대부분 처리해서 내려주고, 뷰 로직은 모두 Client에서 구현이 되기 때문에 실질적으로 하는 일은 
    • end point mapping
    • server rendering
    • api gateway

Project Structure - Client

  • booking-web-client
    • front-end 자원들은 여기 모여있다.
    • JSX, JS, CSS, e2e test code
    • Server Side Code에 대한 의존성 Zero
      Java 외에 다른 Web Server 로도 화면 렌더링 가능

Project Structure

그외에..

Server Side

  • Spring MVC로 url end point 처리
  • 초기 rendering에 필요한 데이터 model에 적재
@RequestMapping({"/hotels/{productId}", "/overseas/hotels/{productId}"})
@CoupangWebLayoutEnable
public String hotelDetail(@PathVariable Long productId,
		Model model) {

    HotelDto hotelDto = overseasHotelService.findOverSeasHotel(productId);
    if (hotelDto == null) {
        return "pages/pc/hotel/overseas/hotelNotFound";
    }

    model.addAttribute("hotel", hotelDto);

    createMetaInfo(model, hotelDto);

    return "pages/pc/hotel/overseas/hotelDetail";
}

Server Side

{{#partial "head"}}
  {{#unless hotLoaderEnable}}
    {{assetStyle "bundles/pc.overseas.tdp.HotelDetailController.bundle.css"}}
  {{/unless}}
{{/partial}}
 
{{!-- 메뉴별 바디 영역 --}}
{{#partial "body"}}
  {{!-- 해당 영역 아래에 react를 통한 client side rendering이 이루어진다. --}}
  <div id="booking-contents" class="booking-main-wrapper"></div>
{{/partial}}
 
{{#partial "script-page"}}
  {{!-- 서버에서 spring의 model에 넣어준 값들을 application/json 형식으로 미리 렌더링하여 가져다 쓰는 구조 --}}
  <script id="hotel" type="application/json">{{json hotel}}</script>
  <script id="user" type="application/json">{{json user}}</script>
  {{#if hotLoaderEnable}}
    {{assetScript "devModeChecker.js"}}
    <script src="http://{{serverName}}:3001/bundles/pc.overseas.tdp.HotelDetailController.bundle.js"
            type="application/javascript"
            onerror="handleDevModeScriptLoadingFail()"></script>
  {{else}}
    {{assetScript "bundles/pc.overseas.tdp.HotelDetailController.bundle.js"}}
  {{/if}}
{{/partial}}
  • server side handlebars 로 초기 rendering
  • GNB 적용을 위해 coupangWebLayout 적용
  • SEO와 OG를 위한 meta tag 처리
  • Spring model에 넣은 값 JSON rendering

~Controller.jsx

  • 특정 화면을 구성하는 Root Component
  • 기존 coupang-web의 Controller.js와 용도가 유사
  • 렌더링에 필요한 데이터 로딩
  • ReactDOM을 통해 렌더링하는 코드 포함

~Controller.jsx

import React from 'react';
import ReactDOM from 'react-dom';

import JSONLoader from 'utils/JSONLoader';
  
export default class HotelDetailController extends React.Component {
  ... controller react component 구현
  
  state = {    
    hotel: JSONLoader.load('hotel') 
  };
  
  componentDidMount () {
    // 컴포넌트가 렌더링된 이후에 1회 실행
    // ajax를 통해 비동기로 데이터를 가져오는 코드도 보통 이곳에 들어옴
  }

  render () {
    // rendering code
    return (
      <div>hello bali!</div>
    );
  }

  // 이벤트 핸들러들
  handleXXX() = (e) => {
  };
}
  
// 실질적인 렌더링은 여기서 일어남
try {
    ReactDOM.render(<HomeController />, document.getElementById('booking-contents'));  
} catch (e) {
    console.error('[Home rendering error]', e);
}

Server Side

  • SEO, OG를 위한 meta tag 데이터 생성에 필요한 것들
  • end point  진입시 데이터 정합성 체크를 위해 무조건 불러와야 하는 데이터

Client Side

  • 로딩이 오래 걸리거나 UI 인터랙션에 의해 자주 렌더링 되어 비동기 처리 해야하는 데이터들

Data Loading

Ajax Pattern

  • Root Component 에서 ajax 처리를 모두 담당하고, 실제 데이터가 필요한 하위 Component까진 props로 넘기는 구조
  • props 로 넘기는 노가다가 있지만, 나중에 데이터 흐름 파악하기가 쉽다.
  • http://andrewhfarmer.com/react-ajax-best-practices/

Result

Client와 Server의 완전한 분리

  • 화면 렌더링에 관해 Java에 의존적이지 않으며
  • 작업한 UI Component를 다른 프로젝트에 이식하기도 수월
    • 추후 private npm repository를 통해서 팀간에 만든 Component가 손쉽게 공유가 됐으면 하는 바람(아발론 파이팅!)
  • 추후 배포도 분리해서 할 수 있었으면 좋겠다.
    • Client 수정만 있는 경우 WAS 재기동 없이 bundle 배포만 가능했으면.. 

Component Tree

  • 각 요소별로 쪼개져있기 때문에 고쳐야 하는 코드가 명확
  • 쪼개진 Component 별로 재사용이 수월

대표적으로 재사용된 컴포넌트

같은 컴포넌트에 CSS만 다르게 입힌 것

<PCQuickSearch />

재사용할 수 있는 컴포넌트

<StaticGoogleMap />

<HotelDescriptionText />

<HotelStars />

<FAQ />

<Breadcrumb />

<PriceText />

TODO

  • Booking Mobile Web에서도 PC Web에서 사용된 컴포넌트를 재사용하여 작업 중
  • redux

팀원들의 후기 - 장점편

  • state와 props를 통해서만 컴포넌트끼리 데이터가 흐르므로 데이터 흐름을 파악하는데 큰 도움
  • propTypes를 통한 type  checking 이 가능해서 이 역시 데이터 흐름 파악에 큰 도움
  • 일관된 this context
  • state update 시 알아서 화면이 바뀌므로 rendering에 신경쓸 포인트가 줄어듦
  • 자연스럽게 팀 내 코딩 컨벤션이 생김
  • 팀 내 다른 개발자가 짠 react 컴포넌트 갖다 쓰기가 매우 쉬웠음

팀원들의 후기 - 단점편

  • 익혀야 하는 기술이 많고 개념 전환이 어려웠다
  • 간단한 DOM 조작으로 해결될 문제도 복잡하게 처리해야 하는 경우가 생김
  • input field 처리의 비효율성
  • 아직은 익숙하지 않아서 디버깅이 어렵다는 의견
  • 0.14 기준 react가 자동으로 만들어주는 span 문제
    => span 에 걸려있는 css가 있다면 자연스럽게 side effect 생김
  • build에 쓰인 일부 node modules 의 완성도가 떨어져 삽질을 했던 경우도.
    • 어느날 갑자기 빌드가 안 되는 경우도 있다.

Tips

  • windows 에선 node_modules 의존성의 긴 경로명이 문제를 일으키기 때문에 반드시 npm 3.x.x를 써야함
  • 그럼에도 불구하고 일부 npm module은 windows 7에서 설치에 문제를 일으킨다. windows 10 에선 정상작동 확인
    • scss 라던가..
    • eslint import plugin 이라던가..
    • and more...
  • 사내에 지급된 windows 7 pc에선
    watch 성능이 매우 떨어지는 경우도..

windows 7 개발환경 문제

  • Client side rendering에 걸리는 시간이 길어질 경우 사용자가 인지할 정도의 깜빡임이 생김
  • server side hbs에서 wire frame이라도 잡아놓자

Dummy Markup 처리

<div id="booking-contents">
  <div class="live-search-section clearFix">
    <div class="dummy-block" style="margin-bottom:5px;height:48px"></div>
    <div style="background-color:#eaf6ff;height:59px;margin-bottom:10px;"></div>
    <div class="filter">
      <div class="dummy-block" style="background-color:#fff6d2;width:240px;height:340px;"></div>
      <div class="dummy-block" style="width:240px;margin-top:10px;height:358px;"></div>
    </div>
    <div class="booking-hotel-list">
      <div class="live-list">
      <div class="dummy-block" style="background-color:#fff;margin-top:35px;height:30px;"></div>

      <div class="dummy-block" style="background-color:#fff;height:202px;margin-top:8px;">
        <div style="float:left;width:198px;height:200px;border-right:1px solid #ccc;"></div>
      </div>
      <div class="dummy-block" style="background-color:#fff;height:202px;margin-top:8px;">
        <div style="float:left;width:198px;height:200px;border-right:1px solid #ccc;"></div>
      </div>
      <div class="dummy-block" style="background-color:#fff;height:202px;margin-top:8px;">
        <div style="float:left;width:198px;height:200px;border-right:1px solid #ccc;"></div>
      </div>
      <div class="dummy-block" style="background-color:#fff;height:202px;margin-top:8px;">
        <div style="float:left;width:198px;height:200px;border-right:1px solid #ccc;"></div>
      </div>
      <div class="dummy-block" style="background-color:#fff;height:202px;margin-top:8px;">
        <div style="float:left;width:198px;height:200px;border-right:1px solid #ccc;"></div>
      </div>
      </div>
    </div>
  </div>
</div>

Dummy Markup 처리

  • 서비스마다 다르지만 booking-web 같은 경우 ajax 요청이 빈번하게 일어남
  • chrome 기준 http 동시에 맺을 수 있는 갯수 6개





     
  • 직접 Queue 처리 하거나 bluebird.js 로 처리
    • Promise.map과 concurrency 옵션 이용

Ajax Control

  • 현재 node.js 버전이 4.x 이상인 jenkins server 는 8번 서버
  • 그외 빌드 서버는 버전이 낮으므로 webpack build 가 안 될 수 있음
  • jenkins와 deploy server 모두 빌드할 때마다 프로젝트를 날리고 git clone 새로 받으므로 npm install에 시간이 많이 걸린다. node_modules 폴더 캐싱해서 쓸 것.

build / deploy

  • 0.0.4 버전의 경우 스크립트 실패 시 exit 1이 반환되어야 하지만
  • 무조껀 exit 0이 반환되어 빌드가 실패했는데에도 jenkins build가 안 깨지는 문제가 있었음
  • 덕분에 webpack build 실패 후에도 배포가 진행되어 대참사
    (다행히 개발서버에서만)
  • 0.0.8로 업그레이드 후 해결

better-npm-run

  • bail 옵션 없어 실행하면 process exit 코드 무조건 0으로 반환하는 문제
  • 최신 버전에선 고쳐졌다고 함

심지어 webpack에서도

IE 문제

  • react.js 0.14 버전은 IE 8부터 지원
  • 차기 버전은 15 버전에선 IE 9부터 지원

Q & A

많이 써주세요...

Thank you!

Booking Web with React.js

By 김태희(로토/Roto) [Travel Systems CX] ­