개요 및 이 글에서 얻어갈 것
Fiori Launchpad에 앱을 배포했는데 사용자들이 "타일만 봐서는 지금 처리할 일이 있는지 없는지 모르겠다"고 불평한 경험이 있다면, 이 글이 정확히 그 문제를 해결합니다. Static Tile(정적 타일)만 나열된 홈 화면은 사용자에게 매번 앱을 열어 확인해야 하는 부담을 강요합니다. 반면 Dynamic Tile(동적 타일)은 OData 서비스로부터 실시간 KPI(예: 미결재 구매 주문 47건)를 가져와 타일 표면에 숫자로 렌더링합니다. 이 글에서는 실무 시나리오로 자주 등장하는 "미결재 구매 주문 건수 실시간 표시"를 3단계로 구현합니다.
- 동적 타일이 왜 UX 성패를 가르는지 이해
- ABAP OData 서비스에서 카운터 endpoint 노출 방법 습득
- Fiori Launchpad Designer에서 Dynamic Tile 카탈로그 등록 절차 숙지
- 타일 refresh 주기, 캐싱, 권한 검증 등 프로덕션 이슈 대응
- 실제 발생하는 500 에러, CSRF 토큰 이슈 트러블슈팅
이 글을 시작하기 전에 알아두면 좋은 것
이 글은 intermediate 난이도로 작성되었습니다. ABAP 기본 문법과 SEGW(SAP Gateway Service Builder) 또는 RAP(RESTful Application Programming) 모델 중 하나를 사용해본 경험이 있어야 무리 없이 따라올 수 있습니다. Fiori Launchpad Designer(FLPD) 트랜잭션 접근 권한과 카탈로그·그룹 개념도 미리 익혀두면 좋습니다. HTTP 상태 코드, JSON 구조에 대한 이해도 필요합니다. S/4HANA 온프레미스 또는 BTP ABAP Environment 모두 유사한 개념이 적용됩니다.
환경 및 버전 준비
이 글의 코드는 다음 환경 기준으로 작성되었습니다. 버전별 UI 차이가 있을 수 있으니 본인 시스템과 대조하며 진행하시기 바랍니다.
- 백엔드: S/4HANA 2022 FPS02 또는 BTP ABAP Environment(Steampunk) 2024년 릴리스
- SAP Gateway: SAP_GWFND 750 SP20 이상 권장
- Fiori Front-End Server: SAP Fiori for SAP S/4HANA 2020 FPS03 이상, UI5 1.108 LTS 기준
- 개발 도구: Eclipse ADT(ABAP Development Tools) 최신 버전, 또는 Business Application Studio
- 권한 오브젝트: S_RFC, S_SERVICE, /UI2/CHIP 등 카탈로그 관리 권한
- 테스트 계정: SU01에서 PFCG 롤로 카탈로그·그룹이 할당된 사용자
동적 타일은 백엔드 OData 서비스가 정상적으로 카운터 값을 반환해야만 동작하므로, 서비스 활성화(트랜잭션 /IWFND/MAINT_SERVICE)와 SICF 노드 활성화가 선행되어야 합니다.
핵심 개념 정리 — Static vs Dynamic Tile
Fiori Launchpad의 타일은 사용자가 앱으로 진입하는 관문입니다. 이를 잘못 설계하면 사용자는 매번 앱을 클릭해서 열어봐야 하고, 처리할 업무가 없는 앱까지 확인해야 하므로 피로도가 급증합니다. 실제로 많은 프로젝트에서 "타일만 나열된 홈 화면"이 사용자 불만 1순위로 꼽힙니다.
Static Tile은 아이콘, 제목, 부제목만 표시하는 간판입니다. 반면 Dynamic Tile은 백엔드 OData 서비스를 주기적으로 호출해 number, numberUnit, info, infoState 같은 필드를 받아 타일 표면에 실시간으로 표시합니다. 예를 들어 "미결재 구매 주문" 타일에 "47"이라는 숫자가 표시되고 상태에 따라 초록/노랑/빨강 색상이 바뀌면, 사용자는 클릭 없이도 우선순위를 판단할 수 있습니다.
비유하자면 Static Tile은 "은행 앞 간판", Dynamic Tile은 "은행 창구별 현재 대기번호 전광판"입니다. 후자는 방문객이 굳이 창구 앞까지 가지 않아도 얼마나 붐비는지 즉시 파악할 수 있게 해줍니다.
동적 타일이 요구하는 OData 응답 스키마는 아래와 같이 규격화되어 있습니다. 백엔드에서 이 구조를 정확히 맞추지 않으면 타일이 아무 값도 표시하지 못하고 회색으로 남습니다.
{
"d": {
"results": {
"__metadata": { "type": "TileCounterService.Counter" },
"number": 47,
"numberUnit": "건",
"numberState": "Negative",
"numberDigits": 0,
"info": "긴급 처리 필요",
"infoState": "Critical",
"title": "미결재 구매 주문",
"subtitle": "지난 24시간",
"icon": "sap-icon://sales-order"
}
}
}
여기서 numberState는 Positive/Negative/Critical/Neutral 4가지 값을 가지며, 이에 따라 숫자 색상이 자동 변경됩니다. 이 스키마는 SAP UI5의 sap.suite.ui.commons.GenericTile 컨트롤이 파싱하는 규격과 일치합니다.
실전 예제 1단계 — 기본 카운터 OData 서비스 만들기
먼저 SEGW로 최소 형태의 카운터 서비스를 만듭니다. 실무에서는 미결재 구매 주문(PurchaseOrder) 건수를 반환하도록 하겠습니다. Entity Type은 위 JSON 스키마의 필드를 그대로 매핑합니다.
CLASS zcl_po_counter_dpc_ext DEFINITION
PUBLIC
INHERITING FROM zcl_po_counter_dpc
CREATE PUBLIC.
PUBLIC SECTION.
METHODS counterset_get_entityset REDEFINITION.
ENDCLASS.
CLASS zcl_po_counter_dpc_ext IMPLEMENTATION.
METHOD counterset_get_entityset.
DATA: lv_count TYPE i,
ls_row TYPE zcl_po_counter_mpc=>ts_counter.
" 미결재 구매 주문 카운트 조회
SELECT COUNT( * )
FROM ekko
INTO @lv_count
WHERE frgke = @space " 미결재 상태
AND bstyp = 'F' " 구매 주문
AND aedat GE @( sy-datum - 30 ).
ls_row-id = 'PO_OPEN'.
ls_row-number = lv_count.
ls_row-number_unit = '건'.
ls_row-title = '미결재 구매 주문'.
ls_row-subtitle = '최근 30일'.
ls_row-icon = 'sap-icon://sales-order'.
APPEND ls_row TO et_entityset.
ENDMETHOD.
ENDCLASS.
서비스를 등록(/IWFND/MAINT_SERVICE)한 뒤 브라우저에서 다음 URL을 호출해 응답을 확인합니다.
GET /sap/opu/odata/sap/ZPO_COUNTER_SRV/CounterSet?$format=json
이 시점에서 JSON이 정상적으로 반환되어야 다음 단계로 넘어갈 수 있습니다. 만약 401/403이 뜨면 사용자 롤에 서비스 권한이 누락된 것이고, 500이 뜨면 DPC_EXT 클래스의 SELECT 로직에 문제가 있을 가능성이 높습니다.
실전 예제 2단계 — Launchpad 카탈로그에 Dynamic Tile 등록 및 에러 처리
이제 만든 OData 서비스를 Fiori Launchpad Designer에서 Dynamic Tile로 연결합니다. 트랜잭션 /UI2/FLPD_CUST 또는 /UI2/FLPD_CONF에 진입해 카탈로그를 하나 생성한 뒤 타일을 추가합니다.
Tile Type을 Dynamic App Launcher로 선택하면 아래와 같은 파라미터를 입력하는 폼이 나옵니다. 각 필드의 의미를 정확히 이해해야 합니다.
Title: 미결재 구매 주문
Subtitle: 최근 30일 기준
Icon: sap-icon://sales-order
Information: 지연 시 알림
Number Unit: 건
# Dynamic Data 영역
Service URL: /sap/opu/odata/sap/ZPO_COUNTER_SRV/CounterSet('PO_OPEN')
Refresh Interval: 300 # 초 단위
Number Factor: 1
# Navigation 영역
Semantic Object: PurchaseOrder
Action: manage
여기서 가장 흔한 실수는 Service URL 뒤에 ?$format=json을 붙이는 것입니다. Launchpad는 내부적으로 XML을 요청하므로 강제로 JSON을 지정하면 오히려 파싱이 실패합니다. URL은 순수 endpoint만 지정하세요.
DPC_EXT에서 예외 상황을 명시적으로 처리해두면 프로덕션에서 훨씬 안정적입니다. 카운터가 조회 불가할 때 회색 화면 대신 "확인 필요" 문구를 표시하도록 개선합니다.
METHOD counterset_get_entity.
DATA: lv_count TYPE i,
ls_row TYPE zcl_po_counter_mpc=>ts_counter.
TRY.
SELECT COUNT( * )
FROM ekko
INTO @lv_count
WHERE frgke = @space
AND bstyp = 'F'.
ls_row-number = lv_count.
ls_row-number_state = COND #(
WHEN lv_count = 0 THEN 'Positive'
WHEN lv_count < 20 THEN 'Neutral'
WHEN lv_count < 50 THEN 'Critical'
ELSE 'Negative' ).
ls_row-info = COND #(
WHEN lv_count >= 50 THEN '긴급 처리 필요'
ELSE '정상' ).
ls_row-info_state = ls_row-number_state.
ls_row-title = '미결재 구매 주문'.
ls_row-subtitle = '최근 30일'.
er_entity = ls_row.
" 로그 남기기
cl_bali_log_db_writer=>write(
io_log = build_log( lv_count )
i_object = 'ZPO_TILE'
i_subobject = 'COUNTER'
).
CATCH cx_sy_open_sql_db INTO DATA(lx_db).
RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
EXPORTING
message = |Counter query failed: { lx_db->get_text( ) }|.
ENDTRY.
ENDMETHOD.
이렇게 하면 count 값에 따라 색상이 자동으로 바뀝니다. 사용자는 타일만 봐도 "지금 개입할 필요가 있는지" 즉시 판단할 수 있습니다.
실전 예제 3단계 — 프로덕션 성능·보안·CDS annotation 통합
실서비스에서는 카운터 SELECT가 매 사용자마다 300초마다 호출되므로, 사용자 1000명이면 초당 다수의 쿼리가 지속적으로 발생합니다. 이대로 두면 EKKO 테이블에 부하가 걸립니다. 프로덕션에서는 CDS 뷰와 RAP 방식으로 재작성하는 것을 권장합니다.
@AbapCatalog.sqlViewName: 'ZVPOCOUNT'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Open Purchase Order Counter'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_PO_COUNTER_QUERY'
@Analytics.dataCategory: #CUBE
define view Z_PO_Open_Counter
as select from ekko
{
key 'PO_OPEN' as CounterId,
count( distinct ebeln ) as OpenCount,
max( aedat ) as LastChanged
}
where
frgke = ' '
and bstyp = 'F'
group by
'PO_OPEN'
이 CDS 뷰를 RAP Service Binding으로 노출하면 OData V4 endpoint가 자동 생성됩니다. Launchpad Dynamic Tile은 V2를 기본 기대하지만, V4도 $count와 응답 매핑을 조정하면 사용할 수 있습니다.
더 나아가 CDS annotation으로 UI 힌트를 심으면, 나중에 Fiori Elements 앱에서 재사용할 때 편리합니다.
@UI.headerInfo: {
typeName: 'Purchase Order Counter',
title: { value: 'OpenCount' }
}
@UI.identification: [
{ position: 10, value: 'OpenCount', label: '미결재 건수' },
{ position: 20, value: 'LastChanged', label: '최종 변경' }
]
annotate view Z_PO_Open_Counter with
{
@UI.dataPoint: {
title: '미결재 구매 주문',
criticality: #CRITICAL,
trend: 'OpenCount'
}
OpenCount;
}
보안 측면에서는 카운터 서비스에도 반드시 인가 체크가 들어가야 합니다. 사용자가 볼 권한이 없는 조직단위의 PO까지 카운트되면 정보 유출이 됩니다. @AccessControl.authorizationCheck: #CHECK와 DCL(Data Control Language) 조합으로 이를 처리합니다.
@MappingRole: true
define role Z_PO_Counter_Role {
grant select on Z_PO_Open_Counter
where ( CounterId ) = aspect pfcg_auth (
M_BEST_EKO, EKORG, ACTVT = '03'
);
}
마지막으로 Application Log(SLG1)에 카운터 조회 이력을 남기고, ST05 SQL Trace로 실행 계획을 확인하는 습관을 들이세요. 300초 캐싱만 걸어도 DB 부하가 극적으로 감소합니다.
흔한 실수와 트러블슈팅 FAQ
실무에서 마주치는 대표적인 문제들을 정리했습니다.
Q1. 타일이 계속 회색으로 나오고 숫자가 안 뜹니다.
브라우저 개발자 도구 Network 탭에서 Service URL 호출 결과를 먼저 확인하세요. 401이면 사용자 롤에 /IWFND/* 서비스 권한이 빠졌고, 200인데 표시가 안 되면 응답 JSON의 number 필드명이 정확한지 확인해야 합니다. 필드명은 대소문자를 엄격히 구분합니다. Entity Property 이름이 Number인지 number인지가 문제인 경우가 많습니다.
Q2. Chrome에서는 되는데 Edge 또는 모바일 Fiori Client에서 CSRF 에러가 납니다.
Dynamic Tile은 GET 요청이라 원칙적으로 CSRF 토큰이 불필요하지만, 일부 프록시 환경에서 사전 fetch 단계에서 실패합니다. SICF에서 서비스 노드의 Logon Data와 Session Handling 설정을 확인하고, sap-client 파라미터를 URL에 명시(?sap-client=100)해보세요.
Q3. 숫자는 나오는데 색상(Critical/Negative)이 반영되지 않습니다.
numberState와 infoState는 반드시 정확히 Positive, Negative, Critical, Neutral 중 하나여야 합니다. 소문자로 보내거나 "Warning" 같은 존재하지 않는 값을 보내면 무시됩니다. DPC_EXT에서 문자열 하드코딩할 때 오타를 체크하세요.
Q4. Refresh Interval을 10초로 설정했는데 계속 300초마다 갱신됩니다.
Launchpad는 성능 보호를 위해 내부적으로 최소 갱신 주기를 강제합니다. 10초 같은 값은 무시되고 기본값으로 되돌아가는 경우가 일반적입니다. 실시간성이 정말 필요하면 Push 메커니즘(SAP Event Mesh, WebSocket) 도입을 검토해야 합니다.
Q5. 개발기에선 되는데 운영기 전송 후 타일이 안 뜹니다.
카탈로그·그룹·타겟매핑은 각각 별도의 Workbench Request로 관리됩니다. FLPD에서 3가지 오브젝트가 모두 이관되었는지, 그리고 운영기의 OData 서비스가 /IWFND/MAINT_SERVICE에서 정상 활성화되었는지 확인하세요. 이관 누락 1순위가 "Target Mapping"입니다.
이어서 학습하면 좋은 관련 주제
Dynamic Tile을 마스터했다면, 다음으로 Interactive Tile(하나의 타일에 여러 KPI를 슬라이딩으로 보여주는 방식), News Tile(RSS 피드 기반), KPI Modeler(SAP Smart Business로 복잡한 지표 시각화) 등을 학습하시길 권합니다. 또한 카운터를 실시간으로 밀어주고 싶다면 SAP Event Mesh 또는 ABAP Push Channel(APC)를 조합해 WebSocket 기반 갱신을 구현할 수 있습니다. Fiori Elements의 Overview Page(OVP) 패턴은 여러 Dynamic Tile을 하나의 화면에 집약해 보여주는 발전형이니 함께 살펴보면 시야가 넓어집니다.
더 깊이 파고들 수 있는 자료들
- help.sap.com — Configuring Dynamic App Launcher Tiles
- help.sap.com — Fiori Launchpad Designer 사용 가이드
- help.sap.com — SAP Gateway OData Service 개발 가이드
- SAPUI5 API — GenericTile 컨트롤 레퍼런스
- SAP Community 블로그 — Dynamic Tile 실전 사례 모음
- help.sap.com — RAP(RESTful ABAP Programming Model) 개요
- SAP Blogs — Fiori Launchpad 태그 아카이브
댓글 0
아직 댓글이 없습니다.