UI5

Component.js 없이 FLP 통합 큰일 — 3단계 해결 #shorts #SAP #UI5

▶ YouTube에서 보기

📖 개요와 목표 체크리스트

로컬 index.html에서는 멀쩡히 돌던 UI5 앱이 Fiori Launchpad(FLP)에 올리는 순간 빈 화면만 보여주는 경우가 있습니다. 원인의 대부분은 Component.js 부재 또는 잘못된 구성입니다. FLP는 앱을 "페이지"가 아니라 "컴포넌트"로 로드하기 때문에, Component 계층이 없으면 통합 자체가 성립하지 않습니다. 이 글에서는 판매오더(SalesOrder) 조회 앱을 예로 들어 FLP 통합을 3단계로 정리합니다.

  • Component.js가 FLP 통합에서 하는 역할과 동작 원리 이해
  • manifest.json의 crossNavigation/inbounds로 Intent 기반 내비게이션 구성
  • xs-app.json 라우팅과 SAP Build Work Zone 타일 등록까지 완결

📚 시작 전 갖춰야 할 배경

중급 난이도의 글입니다. UI5 MVC 구조(View/Controller), XML View 작성 경험, JSON 기반 설정 파일을 읽을 수 있는 수준을 전제로 합니다. OData 서비스 호출 경험이 있으면 2단계 이후 내용을 더 수월하게 따라갈 수 있습니다. Fiori Launchpad 사용 경험은 없어도 무방합니다.

🔧 환경 및 준비물

이 글의 예제는 다음 환경을 기준으로 작성했습니다.

  • SAPUI5 1.120 LTS (1.71 이상이면 대부분 동일하게 동작)
  • SAP BTP Cloud Foundry 환경 + HTML5 Application Repository
  • SAP Build Work Zone, standard edition (구 Launchpad Service) — 온프레미스 FLP(S/4HANA)도 개념은 동일
  • SAP Business Application Studio 또는 VS Code + Fiori tools

온프레미스 S/4HANA의 FLP를 쓰는 경우 타일 등록 위치만 Launchpad Designer/런치패드 콘텐츠 매니저로 바뀔 뿐, Component.js와 manifest.json 구성은 그대로 적용됩니다.

💡 핵심 개념

FLP를 쇼핑몰 건물에 비유하면 이해가 쉽습니다. 독립 실행 앱(index.html)은 자기 건물을 통째로 소유한 로드숍입니다. 전기, 출입문, 간판을 전부 스스로 관리합니다. 반면 FLP에 입점한 앱은 쇼핑몰 안의 매장입니다. 건물(부트스트랩, 셸 바, 테마, 세션)은 쇼핑몰이 제공하고, 매장은 표준 규격의 출입구만 제공하면 됩니다. 그 출입구가 바로 Component.js입니다.

동작 원리를 순서대로 보면 다음과 같습니다.

  1. 사용자가 타일을 클릭하면 FLP는 #SalesOrder-display 같은 Intent(SemanticObject-Action)를 해석합니다.
  2. 런치패드 콘텐츠에 등록된 타깃 매핑에서 해당 Intent에 연결된 앱의 URL과 컴포넌트 ID를 찾습니다.
  3. FLP 셸이 Component.js를 로드하고 UIComponent를 인스턴스화합니다. 이때 index.html전혀 사용되지 않습니다.
  4. Component가 manifest.json을 읽어 루트 뷰, 라우팅, 데이터소스(OData 모델)를 초기화합니다.

여기서 세 가지 사실이 도출됩니다. 첫째, Component.js가 없으면 FLP는 앱을 로드할 진입점 자체가 없습니다. 둘째, 부트스트랩·모델 생성 코드를 index.html에 넣어두면 FLP에서는 그 코드가 실행되지 않아 "로컬에선 되는데 FLP에선 안 되는" 문제가 생깁니다. 셋째, Intent가 manifest.json의 inbound 선언과 일치하지 않으면 타일 클릭 시 내비게이션 해석 오류가 발생합니다. 따라서 일반적으로 모든 초기화 로직은 Component/manifest로 이동시키는 것이 권장됩니다.

💻 실전 예제 3단계

1단계 — Component.js 기본 구성

판매오더 조회 앱의 네임스페이스를 acme.sales.orders로 잡고, 최소 구성의 Component를 만듭니다.

// webapp/Component.js
sap.ui.define([
    "sap/ui/core/UIComponent",
    "sap/ui/Device"
], function (UIComponent, Device) {
    "use strict";

    return UIComponent.extend("acme.sales.orders.Component", {

        metadata: {
            manifest: "json",
            interfaces: ["sap.ui.core.IAsyncContentCreation"]
        },

        init: function () {
            UIComponent.prototype.init.apply(this, arguments);

            this.setModel(
                new sap.ui.model.json.JSONModel(Device),
                "device"
            );

            this.getRouter().initialize();
        }
    });
});

핵심 포인트는 두 가지입니다. manifest: "json" 선언으로 설정을 전부 manifest에 위임하고, init에서 부모 호출을 빠뜨리지 않는 것입니다. 부모 init을 생략하면 OData 모델과 라우터가 생성되지 않아 FLP에서 조용히 빈 화면이 됩니다. IAsyncContentCreation 인터페이스는 루트 뷰를 비동기로 생성해 FLP 로딩 성능을 개선하며 1.89 이상에서 일반적으로 권장됩니다.

2단계 — manifest.json의 FLP 필수 설정과 Intent 선언

FLP 통합의 실질적 계약서는 manifest.json입니다. 특히 crossNavigation/inbounds가 빠지면 Work Zone이 이 앱을 어떤 Intent로 열어야 할지 알 수 없습니다.

{
  "_version": "1.59.0",
  "sap.app": {
    "id": "acme.sales.orders",
    "type": "application",
    "title": "{{appTitle}}",
    "applicationVersion": { "version": "1.0.0" },
    "dataSources": {
      "salesOrderService": {
        "uri": "/sap/opu/odata/sap/ZSD_SALESORDER_SRV/",
        "type": "OData",
        "settings": { "odataVersion": "2.0" }
      }
    },
    "crossNavigation": {
      "inbounds": {
        "SalesOrder-display": {
          "semanticObject": "SalesOrder",
          "action": "display",
          "title": "{{flpTileTitle}}",
          "signature": {
            "parameters": {},
            "additionalParameters": "allowed"
          }
        }
      }
    }
  },
  "sap.ui5": {
    "rootView": {
      "viewName": "acme.sales.orders.view.App",
      "type": "XML",
      "async": true,
      "id": "appRoot"
    },
    "models": {
      "": {
        "dataSource": "salesOrderService",
        "preload": true,
        "settings": { "defaultBindingMode": "TwoWay" }
      }
    },
    "routing": {
      "config": {
        "routerClass": "sap.m.routing.Router",
        "path": "acme.sales.orders.view",
        "async": true,
        "controlId": "appRoot",
        "controlAggregation": "pages"
      },
      "routes": [
        { "name": "orderList", "pattern": "", "target": "orderList" }
      ],
      "targets": {
        "orderList": { "name": "OrderList", "type": "View" }
      }
    }
  }
}

실무에서 자주 놓치는 부분은 세 가지입니다. 첫째, sap.app/id는 Component.js의 네임스페이스와 정확히 일치해야 합니다. 둘째, 모델은 컨트롤러 코드가 아닌 models 섹션에서 선언해야 FLP 환경에서도 동일하게 생성됩니다. 셋째, 에러 추적을 위해 컨트롤러에서 모델 이벤트에 로깅을 붙여두면 FLP에서의 장애 분석이 쉬워집니다.

// 예: OrderList.controller.js — OData 오류 로깅
onInit: function () {
    var oModel = this.getOwnerComponent().getModel();
    oModel.attachRequestFailed(function (oEvent) {
        var oResp = oEvent.getParameter("response");
        sap.base.Log.error("SalesOrder 서비스 호출 실패",
            oResp ? oResp.responseText : "no response",
            "acme.sales.orders");
        sap.m.MessageBox.error("판매오더 데이터를 불러오지 못했습니다.");
    });
}

3단계 — xs-app.json 라우팅과 Work Zone 타일 등록 (프로덕션)

BTP Cloud Foundry에 배포할 때는 xs-app.json이 요청 라우팅과 인증을 담당합니다. OData 경로가 여기서 목적지(destination)로 매핑되지 않으면 FLP에서 404가 발생합니다.

{
  "welcomeFile": "/index.html",
  "authenticationMethod": "route",
  "routes": [
    {
      "source": "^/sap/opu/odata/(.*)$",
      "target": "/sap/opu/odata/$1",
      "destination": "s4hana-onprem",
      "authenticationType": "xsuaa",
      "csrfProtection": false
    },
    {
      "source": "^(.*)$",
      "target": "$1",
      "service": "html5-apps-repo-rt",
      "authenticationType": "xsuaa"
    }
  ]
}

배포는 MTA로 묶고, Work Zone 사이트에서는 채널 매니저 → HTML5 Apps 콘텐츠 공급자 동기화 → 콘텐츠 익스플로러에서 앱 선택 → 역할(Role)과 그룹/페이지에 할당 순서로 타일을 노출합니다. manifest의 inbound(SalesOrder-display)가 그대로 타깃 매핑으로 인식되므로 별도 수동 입력이 필요 없습니다. 보안 측면에서는 authenticationType: "xsuaa"로 모든 라우트를 보호하고, 역할 컬렉션을 통해서만 타일이 보이도록 구성하는 것이 일반적으로 권장됩니다. 성능 측면에서는 Component-preload.zip이 생성되도록 ui5 build --all 파이프라인을 CI에 포함시키고, 배포 전 ui5 serve + 로컬 FLP 샌드박스(test/flpSandbox.html)로 Intent 내비게이션을 테스트하는 습관을 들이면 좋습니다.

⚠️ 흔한 실수와 트러블슈팅

Q1. 로컬 index.html에서는 잘 되는데 FLP 타일을 누르면 빈 화면입니다.

모델 생성이나 초기화 코드가 index.html에 있는 전형적 사례입니다. FLP는 index.html을 로드하지 않으므로 해당 코드가 실행되지 않습니다. 모든 초기화를 Component.js와 manifest.json으로 옮기세요. 또한 sap.app/id와 Component 네임스페이스 불일치도 같은 증상을 냅니다.

Q2. 타일 클릭 시 "Could not resolve navigation target" 오류가 납니다.

Intent(SalesOrder-display)에 해당하는 inbound가 manifest에 없거나, Work Zone 콘텐츠 동기화가 안 된 경우입니다. 채널 매니저에서 콘텐츠 공급자를 다시 동기화하고, 사용자에게 역할 컬렉션이 할당되어 있는지 확인하세요.

Q3. FLP에서 OData 호출만 404/401이 발생합니다.

xs-app.json의 라우트 source 정규식이 manifest의 dataSource URI와 맞지 않거나, destination 이름 오타가 원인인 경우가 많습니다. 브라우저 네트워크 탭에서 실패한 경로를 그대로 정규식에 대입해 검증하세요. 상대 경로(./odata/...) 사용 시 FLP 셸 URL 기준으로 해석되어 깨질 수 있으니 절대 경로를 권장합니다.

Q4. Duplicate Component ID 오류가 납니다.

FLP는 앱 전환 시 컴포넌트를 재생성할 수 있습니다. View ID를 하드코딩으로 전역 등록했거나 exit에서 리소스를 해제하지 않으면 재진입 시 충돌합니다. ID는 항상 뷰 로컬로 두고 this.byId()로 접근하세요.

🚀 더 나아가기

Component.js와 inbound 구성이 끝났다면 다음 주제로 확장할 수 있습니다. Cross-App Navigation API(sap.ushell.Container의 Navigation 서비스)로 판매오더 앱에서 청구서 앱으로 파라미터를 넘기는 앱 간 이동, Fiori Elements로 동일한 시나리오를 어노테이션 기반으로 재구현하는 비교, 그리고 SAP Build Work Zone의 Spaces/Pages 모델을 활용한 역할별 홈 화면 설계가 자연스러운 후속 학습 경로입니다. 온프레미스 환경이라면 런치패드 콘텐츠 매니저와 타깃 매핑 개념을 추가로 정리해 두면 좋습니다.

📚 함께 보면 좋은 문서

댓글 0

아직 댓글이 없습니다.