UI5

Fiori Elements 플로어플랜 그만 헷갈려 — 4종 비교 #shorts #SAP #Fiori

📖 개요와 이 글의 목표

SAPUI5로 List Report 화면을 freestyle로 직접 그리다 보면 필터바, 테이블, 변형 관리(Variant Management), 초안(Draft) 처리까지 반복 구현하게 됩니다. Fiori Elements는 이 반복을 어노테이션 기반 템플릿으로 대체하는 접근이며, 핵심은 어떤 플로어플랜을 언제 선택하느냐입니다. 이 글에서는 List Report, Object Page, Worklist, Analytical List Page(ALP) 네 가지 플로어플랜의 동작 원리와 선택 기준을 실무 관점에서 정리합니다.

  • 플로어플랜 4종의 구조적 차이와 적합 시나리오 판단 기준 이해
  • manifest.json과 CDS 어노테이션으로 화면을 제어하는 실전 코드 작성
  • 필터바 유무, 집계 여부, 액션 중심 여부로 플로어플랜을 고르는 의사결정 체크리스트 확보
  • 프로덕션 관점의 성능·권한 설정 포인트 파악

📚 미리 알아두면 좋은 배경

OData 프로토콜(V2/V4)의 기본 개념, SAPUI5 프로젝트 구조(manifest.json, Component.js), 그리고 CAP 또는 RAP에서 CDS 어노테이션을 다뤄본 경험이 있으면 수월합니다. XML 뷰를 직접 작성해 본 freestyle SAPUI5 경험자라면 "뷰 코드가 없는데 화면이 나온다"는 Fiori Elements의 발상 전환을 더 빠르게 체감할 수 있습니다.

🔧 환경과 버전, 준비물

이 글의 예제는 다음 환경을 기준으로 합니다.

  • SAPUI5 1.120 이상 — OData V4 기반 템플릿(sap.fe.templates)을 사용합니다. V2 기반 구형 템플릿(sap.suite.ui.generic.template)과는 네임스페이스가 다릅니다.
  • SAP Business Application Studio 또는 VS Code + SAP Fiori tools 확장 — Application Generator로 플로어플랜을 선택해 스캐폴딩합니다.
  • 백엔드: CAP(Node.js) 기반 OData V4 서비스 기준으로 CDS 어노테이션을 작성합니다. RAP(S/4HANA 2022 이상 또는 BTP ABAP Environment)에서도 동일한 UI 어노테이션 체계가 적용됩니다.
  • ALP 예제는 집계($apply)를 지원하는 OData V4 서비스가 필요합니다. CAP에서는 @Aggregation.ApplySupported 계열 어노테이션으로 활성화합니다.

💡 핵심 개념 — 플로어플랜은 아파트 평면도다

플로어플랜(Floorplan)이라는 이름 그대로, 아파트 평면도에 비유하면 이해가 빠릅니다. 아파트를 지을 때 방 배치를 매번 새로 설계하지 않고 검증된 평면(59A, 84B)을 고르듯, Fiori Elements는 검증된 화면 구조를 템플릿으로 제공하고 개발자는 어노테이션이라는 옵션 시트만 채웁니다. 런타임에 템플릿 엔진이 OData 메타데이터와 어노테이션을 읽어 화면을 생성하므로, 뷰 XML을 직접 작성하지 않습니다.

네 가지 플로어플랜의 선택 기준을 표로 정리하면 다음과 같습니다.

플로어플랜구조 특징적합 시나리오피해야 할 경우
List Report필터바 + 테이블 + 변형 관리대량 데이터에서 조건 검색 후 개별 건 진입 (예: 서비스 티켓 조회)필터 없이 고정 목록만 볼 때
Object Page헤더 + 섹션(Facet) 기반 상세단일 객체의 조회·수정·Draft 편집목록 자체가 주인공일 때
Worklist필터바 없는(또는 축소된) 테이블 + 액션 중심"처리해야 할 항목"이 자동으로 정해진 승인·검토 업무사용자가 조건을 바꿔가며 탐색할 때
Analytical List PageKPI 헤더 + 차트/테이블 하이브리드 + 비주얼 필터집계로 이상치를 찾고 드릴다운으로 원인 건까지 내려가는 분석형 업무집계 지원이 없는 서비스, 단순 CRUD

실무 판단은 세 가지 질문으로 압축됩니다. 첫째, 사용자가 필터 조건을 스스로 바꾸는가? 그렇다면 List Report, 아니라면 Worklist입니다. 둘째, 숫자 집계가 의사결정의 시작점인가? 그렇다면 ALP입니다. 셋째, 한 건을 열어 편집하는가? 그 상세 화면이 Object Page이며, 일반적으로 List Report·Worklist·ALP 뒤에 내비게이션 대상으로 붙습니다. 즉 Object Page는 단독보다 조합으로 쓰이는 "안방" 역할입니다.

💻 실전 코드 3단계

1단계 — List Report + Object Page 기본 구성

서비스 티켓(ServiceTicket) 관리 앱을 가정합니다. manifest.json의 라우팅에 두 플로어플랜을 연결하는 것이 뼈대입니다.

{
  "sap.ui5": {
    "routing": {
      "routes": [
        { "name": "TicketList", "pattern": ":?query:",
          "target": "TicketList" },
        { "name": "TicketDetail", "pattern": "ServiceTicket({key}):?query:",
          "target": "TicketDetail" }
      ],
      "targets": {
        "TicketList": {
          "type": "Component", "id": "TicketList",
          "name": "sap.fe.templates.ListReport",
          "options": { "settings": {
            "contextPath": "/ServiceTicket",
            "initialLoad": "Enabled",
            "navigation": {
              "ServiceTicket": { "detail": { "route": "TicketDetail" } }
            }
          } }
        },
        "TicketDetail": {
          "type": "Component", "id": "TicketDetail",
          "name": "sap.fe.templates.ObjectPage",
          "options": { "settings": { "contextPath": "/ServiceTicket" } }
        }
      }
    }
  }
}

화면에 무엇이 보일지는 CDS 어노테이션이 결정합니다.

annotate SupportService.ServiceTicket with @(
  UI.SelectionFields : [ status_code, priority_code, assignedTeam_ID ],
  UI.LineItem : [
    { Value : ticketNo,      Label : '티켓번호' },
    { Value : subject,       Label : '제목' },
    { Value : status.name,   Label : '상태',
      Criticality : statusCriticality },
    { Value : slaDeadline,   Label : 'SLA 기한' }
  ],
  UI.HeaderInfo : {
    TypeName : '서비스 티켓', TypeNamePlural : '서비스 티켓',
    Title : { Value : subject }
  }
);

Criticality에 백엔드 계산 필드(1=빨강, 2=노랑, 3=초록)를 연결하면 상태 색상이 자동 표현됩니다.

2단계 — Worklist 전환과 에러 처리·로깅

같은 데이터라도 "내 팀에 배정된 미처리 티켓을 승인/반려"하는 화면이라면 Worklist가 맞습니다. OData V4 템플릿에서는 별도 컴포넌트가 아니라 List Report 설정을 Worklist 성격으로 조정하는 방식이 일반적입니다. 필터바를 숨기고, 서버 측 프리필터(SelectionVariant)로 대상 건을 고정합니다.

"TicketWorklist": {
  "name": "sap.fe.templates.ListReport",
  "options": { "settings": {
    "contextPath": "/ServiceTicket",
    "hideFilterBar": true,
    "initialLoad": "Enabled",
    "views": { "paths": [
      { "key": "openOnly",
        "annotationPath": "com.sap.vocabularies.UI.v1.SelectionPresentationVariant#OpenTickets" }
    ] }
  } }
}

승인 액션은 커스텀 액션으로 등록하고, 실패 시 사용자 메시지와 개발자 로그를 분리합니다.

// ext/controller/TicketActions.js
sap.ui.define(["sap/base/Log", "sap/m/MessageToast"],
function (Log, MessageToast) {
  "use strict";
  return {
    approveTickets: async function (oBindingContext, aSelectedContexts) {
      const oModel = this.getModel();
      try {
        for (const oCtx of aSelectedContexts) {
          const oAction = oModel.bindContext(
            "SupportService.approve(...)", oCtx);
          await oAction.invoke();
        }
        MessageToast.show(\`\${aSelectedContexts.length}건 승인 완료\`);
        this.refresh();
      } catch (oError) {
        Log.error("Ticket approve failed",
          oError.message, "btp.support.TicketActions");
        MessageToast.show("승인 처리 중 오류가 발생했습니다.");
      }
    }
  };
});

Worklist의 본질은 "처리하면 목록에서 사라진다"입니다. 액션 후 refresh()를 빠뜨리면 이미 처리한 건이 남아 이중 승인 사고로 이어질 수 있으니 주의합니다.

3단계 — Analytical List Page와 프로덕션 고려사항

"어느 팀의 SLA 위반이 급증했는가"를 찾는 화면은 ALP입니다. 먼저 서비스에 집계 능력을 부여합니다.

annotate SupportService.ServiceTicket with @(
  Aggregation.ApplySupported : {
    GroupableProperties    : [ assignedTeam_ID, status_code ],
    AggregatableProperties : [ { Property : processingHours } ]
  },
  Analytics.AggregatedProperty #avgHours : {
    Name : 'avgProcessingHours',
    AggregationMethod : 'average',
    AggregatableProperty : processingHours,
    ![@Common.Label] : '평균 처리시간'
  },
  UI.Chart : {
    ChartType : #Column,
    Dimensions : [ assignedTeam_ID ],
    DynamicMeasures : [ '@Analytics.AggregatedProperty#avgHours' ]
  }
);

manifest에서 ALP 템플릿을 지정합니다.

"TicketAnalytics": {
  "name": "sap.fe.templates.AnalyticalListPage",
  "options": { "settings": {
    "contextPath": "/ServiceTicket",
    "views": { "paths": [
      { "primary": [ { "annotationPath":
          "com.sap.vocabularies.UI.v1.Chart" } ],
        "secondary": [ { "annotationPath":
          "com.sap.vocabularies.UI.v1.LineItem" } ],
        "defaultPath": "both" } ] }
  } }
}

프로덕션 관점에서는 세 가지를 점검합니다. 성능 — 차트가 $apply=groupby(...)로 서버 집계를 수행하므로 그룹 기준 컬럼에 DB 인덱스(HANA라면 적절한 뷰 설계)를 확보하고, 테이블 영역은 growing 페이징으로 초기 로드를 제한합니다. 테스트 — Fiori Elements 앱은 뷰 코드가 없으므로 OPA5 대신 어노테이션 산출 화면을 검증하는 통합 테스트(예: wdi5)와 백엔드 단위 테스트에 무게를 둡니다. 보안 — 화면에서 필터를 숨겨도 OData 요청은 조작 가능하므로, CAP @restrict 또는 RAP 권한 체크로 서버 측 인가를 반드시 두는 것이 권장됩니다.

annotate SupportService.ServiceTicket with @(restrict : [
  { grant : ['READ'], to : 'TicketViewer',
    where : 'assignedTeam_ID = $user.team' },
  { grant : ['*'],    to : 'TicketManager' }
]);

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

Q1. List Report 첫 진입 시 테이블이 비어 있고 "필터를 조정하십시오"만 나옵니다.
기본 동작상 초기 자동 조회가 꺼져 있는 구성이 많습니다. manifest의 initialLoad: "Enabled"를 확인하세요. 반대로 수백만 건 엔터티라면 의도적으로 끄고 SelectionFields로 필수 조건을 유도하는 편이 안전합니다.

Q2. ALP 차트가 "지표를 표시할 수 없음"으로 뜹니다.
대부분 집계 어노테이션 누락입니다. Aggregation.ApplySupported에 차트의 Dimension이 GroupableProperties로, Measure가 AggregatableProperties 또는 DynamicMeasures로 선언됐는지 대조하세요. OData V2 서비스라면 V4 기반 ALP 템플릿과 호환되지 않는 조합일 수 있습니다.

Q3. Worklist처럼 쓰려고 필터바만 숨겼더니 사용자가 URL 파라미터로 전체 데이터를 봅니다.
UI 은닉은 보안이 아닙니다. 2·3단계처럼 SelectionVariant 프리필터와 서버 측 where 인가를 함께 적용해야 합니다.

Q4. 어노테이션을 고쳤는데 화면에 반영이 안 됩니다.
메타데이터가 캐시된 경우가 많습니다. 브라우저 하드 리로드와 함께, 온프레미스 게이트웨이 환경이라면 메타데이터 캐시 초기화가 필요할 수 있습니다. 로컬 CAP 개발에서는 cds watch 재기동으로 해결되는지 먼저 확인하세요.

🚀 확장 주제와 학습 경로

플로어플랜 선택이 익숙해졌다면, 다음 주제로 확장하는 흐름이 일반적입니다. Flexible Programming Model(FPM)로 표준 템플릿 안에 커스텀 섹션·페이지를 삽입하는 기법, Overview Page(OVP) 카드 기반 대시보드와 ALP의 역할 분담, RAP 기반 Draft 처리와 Object Page 편집 플로우, 그리고 Fiori tools의 Page Map/Guided Development를 활용한 어노테이션 생산성 향상이 대표적입니다. freestyle과 Fiori Elements를 한 앱에서 혼합하는 하이브리드 구성도 실무에서 자주 요구됩니다.

📚 참고 링크 모음

댓글 0

아직 댓글이 없습니다.