Client-side UI Composition

Client-side UI Composition은 마이크로서비스 아키텍처(MSA)에서 클라이언트(주로 브라우저)가 여러 마이크로서비스로부터 데이터를 직접 가져와 사용자 인터페이스(UI)를 구성하는 패턴이다.
이 패턴은 각 서비스가 독립적으로 UI 컴포넌트를 제공하고, 클라이언트가 이를 조합하여 최종 화면을 렌더링하는 방식으로 동작한다.

이 패턴에서는 클라이언트(브라우저)가 여러 마이크로서비스로부터 데이터를 요청하고, 해당 데이터를 기반으로 UI를 렌더링한다. 각 마이크로서비스는 자신만의 UI 컴포넌트(HTML, CSS, JavaScript 등)를 제공하며, 클라이언트는 이러한 컴포넌트를 조합해 전체 화면을 구성한다.

예를 들어, 전자상거래 웹사이트의 상품 상세 페이지를 생각해보면:

  • 상품 정보(제목, 가격 등)는 상품 서비스에서 제공
  • 구매 이력은 사용자 서비스에서 제공
  • 고객 리뷰는 리뷰 서비스에서 제공
  • 관련 상품 추천은 추천 서비스에서 제공
    이 모든 데이터와 UI 컴포넌트는 클라이언트에서 조합되어 최종 페이지를 구성한다.

Client-side UI Composition은 동적이고 인터랙티브한 사용자 경험을 제공하며, 마이크로서비스 간 결합도를 낮추고 유연성을 높이는 데 적합한 패턴이다. 그러나 초기 로딩 속도나 SEO 문제 등 단점을 해결하기 위해 Lazy Loading, 코드 분할 등의 기술과 함께 사용해야 한다. 이 패턴은 특히 실시간 업데이트가 중요한 애플리케이션이나 동적인 인터페이스를 요구하는 시스템에 적합하다.

Client-side UI Composition의 동작 방식

  1. 클라이언트 요청:
    • 클라이언트는 필요한 데이터를 가져오기 위해 여러 마이크로서비스에 API 요청을 보낸다.
  2. 데이터 수집 및 조합:
    • 각 마이크로서비스는 자신의 데이터와 UI 컴포넌트를 반환한다.
    • 클라이언트는 응답받은 데이터를 조합하여 화면을 구성한다.
  3. UI 렌더링:
    • 클라이언트는 HTML, CSS, JavaScript를 사용해 최종 UI를 렌더링한다.

장점

  1. 서비스 간 결합도 감소:

    • 각 서비스가 독립적으로 개발되며, 클라이언트가 이를 조합하기 때문에 서비스 간 결합도가 낮아진다.
  2. 유연한 UI 구성:

    • 클라이언트가 UI를 자유롭게 조합할 수 있어 더 동적이고 인터랙티브한 사용자 경험을 제공한다.
  3. 서버 부하 감소:

    • 데이터 조합 및 렌더링 작업이 클라이언트에서 이루어지므로 서버의 처리 부담이 줄어든다.
  4. 독립적인 배포 가능:

    • 각 마이크로서비스의 UI 컴포넌트를 독립적으로 배포할 수 있어 빠른 업데이트와 유지보수가 가능하다.

단점

  1. 초기 로딩 속도 저하:
    • 클라이언트가 여러 API 호출을 통해 데이터를 가져오고 이를 조합해야 하므로 초기 로딩 시간이 길어질 수 있다.
  2. SEO 문제:
    • 브라우저에서 동적으로 렌더링되기 때문에 검색 엔진 크롤러가 콘텐츠를 제대로 인식하지 못할 수 있다.
  3. 복잡성 증가:
    • 클라이언트에서 데이터를 조합하고 렌더링해야 하므로 프론트엔드 코드의 복잡성이 증가한다.
  4. 네트워크 요청 증가:
    • 여러 마이크로서비스에 대한 API 호출이 많아져 네트워크 트래픽이 증가할 수 있다.

구현 방법

  1. 프레임워크 활용:

    • React, Angular, Vue.js와 같은 프론트엔드 프레임워크를 사용하여 개별 서비스를 기반으로 UI를 구성한다.
    • 예: React에서는 각 마이크로서비스의 UI 컴포넌트를 독립적인 React 컴포넌트로 구현하고 이를 조합한다.
  2. Web Components 사용:

    • Web Components 표준을 사용하여 각 서비스를 독립적인 HTML 요소(Custom Elements)로 구현하고, 이를 페이지에 삽입하여 조합한다.
    1
    2
    3
    4
    5
    6
    
    class MyFragment extends HTMLElement {
        connectedCallback() {
            this.innerHTML = `<h1>Hello World</h1>`;
        }
    }
    customElements.define('my-fragment', MyFragment);
    
  3. API 호출 및 데이터 바인딩:

    • 클라이언트는 여러 API를 호출하여 데이터를 가져오고, 이를 적절히 바인딩하여 화면에 표시한다.
    1
    2
    3
    4
    5
    
    fetch('/api/product')
        .then(response => response.json())
        .then(data => {
            document.getElementById('product-title').innerText = data.title;
        });
    
  4. Lazy Loading 및 코드 분할:

    • 초기 로딩 속도를 개선하기 위해 필요한 데이터와 컴포넌트를 동적으로 로드(Lazy Load)하거나 코드 분할(Code Splitting)을 활용한다.

구현 전략

  1. 컴포넌트 로딩 전략:

    • 동적 임포트 사용
    • 레이지 로딩 구현
    • 프리로딩 최적화
  2. 통신 관리:

    • 이벤트 기반 통신
    • 상태 공유 메커니즘
    • 데이터 동기화
  3. 오류 처리:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    // 오류 처리 시스템
    class ErrorBoundary {
        constructor() {
            this.errorHandlers = new Map();
        }
    
        handleError(error, componentId) {
            const handler = this.errorHandlers.get(componentId);
            if (handler) {
                handler(error);
            } else {
                this.defaultErrorHandler(error);
            }
        }
    
        registerErrorHandler(componentId, handler) {
            this.errorHandlers.set(componentId, handler);
        }
    }
    

사용 사례

  1. 전자상거래 플랫폼:

    • 상품 정보, 리뷰, 추천 상품 등 다양한 데이터를 여러 서비스에서 가져와 하나의 페이지에 표시하는 경우.
  2. 대시보드 애플리케이션:

    • 여러 서비스에서 데이터를 가져와 위젯 형태로 보여주는 대시보드 구현.
  3. 뉴스 포털 사이트:

    • 다양한 뉴스 섹션(정치, 경제, 스포츠 등)을 각각 다른 서비스에서 가져와 하나의 페이지에 표시.

참고 및 출처