📖 개요 및 이 글에서 얻어갈 것
SAP CAP(Cloud Application Programming Model)으로 백엔드를 만들고 나서, UI를 위해 SAPUI5 화면을 처음부터 코딩하고 있다면 이미 먼 길을 돌아가고 있는 것입니다. CAP은 CDS 모델에 annotation 몇 줄만 추가하면 Fiori Elements가 리스트 리포트, 오브젝트 페이지를 자동으로 렌더링해 줍니다. 이 글은 CAP Node.js 기준으로 annotations만으로 UI를 만드는 과정을 3단계로 나누어 다룹니다.
- Fiori Elements가 CDS annotation을 읽어 화면을 그리는 원리 이해
@UI.LineItem,@UI.SelectionFields,@UI.Facets핵심 어노테이션 실습- Draft 편집, Value Help, 권한 제어까지 포함한 프로덕션 수준 구성
- annotation이 화면에 반영되지 않을 때의 트러블슈팅 포인트
📚 미리 알아두면 좋은 것
CDS로 entity와 service를 정의해 본 경험이 있다면 충분합니다. cds init, cds watch 같은 기본 CLI 사용법과 OData가 무엇인지 정도의 감만 있으면 됩니다. JavaScript 핸들러 작성 경험은 3단계에서 도움이 되지만 필수는 아닙니다. Fiori Elements나 SAPUI5 사전 지식은 전혀 필요 없습니다 — 그게 이 접근 방식의 핵심이기 때문입니다.
🔧 환경 / 버전 / 준비물
이 글의 예제는 다음 환경을 기준으로 작성했습니다.
- Node.js 20 LTS 이상 (18도 동작하지만 20 권장)
- @sap/cds 8.x (CAP Node.js 런타임) —
npm i -g @sap/cds-dk로 CLI 설치 - 로컬 개발 DB: SQLite (
@cap-js/sqlite), 배포 시 SAP HANA Cloud - 선택: VS Code + SAP Fiori tools 확장 (annotation 자동완성 지원)
프로젝트 생성은 cds init sales-portal 한 줄이면 됩니다. cds watch를 실행하면 CAP이 제공하는 index 페이지에서 각 엔티티별 Fiori preview 링크가 자동으로 노출되는데, 별도의 UI 프로젝트 없이도 annotation 결과를 즉시 확인할 수 있어 개발 루프가 매우 짧아집니다.
💡 핵심 개념 — 화면을 '그리는' 게 아니라 '선언하는' 방식
Fiori Elements는 메타데이터 기반(metadata-driven) UI 프레임워크입니다. 일반적인 프론트엔드 개발이 "테이블 컴포넌트를 만들고, 컬럼을 바인딩하고, 필터 바를 배치하는" 절차적 작업이라면, Fiori Elements는 "이 엔티티의 리스트에는 이 필드들을 보여줘"라고 선언만 합니다. 실제 렌더링은 SAPUI5의 표준 템플릿(List Report, Object Page 등)이 담당합니다.
비유하자면 인테리어 업자에게 벽지·가구를 직접 시공하는 대신, 표준 시공 패키지에 체크리스트만 전달하는 것과 같습니다. "거실엔 테이블, 창가엔 필터형 블라인드"라고 적으면 숙련된 시공팀(UI5 템플릿)이 SAP 디자인 가이드에 맞게 완성해 줍니다.
동작 흐름은 다음과 같습니다.
- CDS 파일에
@UI.*annotation을 작성 (OData UI Vocabulary 기반) - CAP 서버가 이를 컴파일해 OData V4 서비스의
$metadata에 포함시켜 노출 - Fiori Elements 앱이 기동 시
$metadata를 읽고 화면 구조를 결정해 렌더링
즉 UI 정의가 백엔드 모델 옆에 함께 살기 때문에, 필드가 추가되면 annotation 한 줄로 화면까지 반영됩니다. 자주 쓰는 어노테이션은 크게 네 그룹입니다.
| 어노테이션 | 역할 |
|---|---|
@UI.LineItem | 리스트 리포트의 테이블 컬럼 정의 |
@UI.SelectionFields | 상단 필터 바에 노출할 필드 |
@UI.HeaderInfo | 오브젝트 페이지 제목/부제목 |
@UI.Facets + @UI.FieldGroup | 오브젝트 페이지의 섹션 구성 |
💻 실전 코드 3단계
1단계 — 기본 예제: 리스트가 뜨는 최소 구성
영업 주문 관리 시나리오로 시작합니다. 먼저 도메인 모델과 서비스를 정의합니다.
// db/schema.cds
namespace sales.portal;
using { cuid, managed } from '@sap/cds/common';
entity SalesOrders : cuid, managed {
orderNo : String(12);
customer : Association to Customers;
totalAmount : Decimal(15,2);
currency : String(3);
status : String(1); // N=신규, P=처리중, D=완료
}
entity Customers : cuid {
name : String(80);
country : String(2);
orders : Association to many SalesOrders on orders.customer = $self;
}
// srv/order-service.cds
using { sales.portal as db } from '../db/schema';
service OrderService {
entity SalesOrders as projection on db.SalesOrders;
entity Customers as projection on db.Customers;
}
이제 UI annotation을 추가합니다. 핵심은 서비스 프로젝션 엔티티에 붙인다는 점입니다.
// app/annotations.cds
using OrderService from '../srv/order-service';
annotate OrderService.SalesOrders with @(
UI.LineItem: [
{ Value: orderNo, Label: '주문번호' },
{ Value: customer.name, Label: '고객명' },
{ Value: totalAmount, Label: '주문금액' },
{ Value: status, Label: '상태' }
]
);
cds watch 실행 후 index 페이지에서 SalesOrders의 Fiori preview를 열면 필터 바와 테이블을 갖춘 List Report가 즉시 나타납니다. UI 코드는 단 한 줄도 작성하지 않았습니다.
2단계 — 실무 시나리오: 필터, 상세 페이지, 상태 시각화
실무에서는 검색 필터, 상세 화면, 상태별 색상 표시가 기본 요구사항입니다. 상태 코드에 criticality를 매핑해 신호등 색상까지 넣어 보겠습니다.
annotate OrderService.SalesOrders with @(
UI.SelectionFields: [ orderNo, status, customer_ID ],
UI.HeaderInfo: {
TypeName: '영업 주문', TypeNamePlural: '영업 주문 목록',
Title: { Value: orderNo },
Description: { Value: customer.name }
},
UI.LineItem: [
{ Value: orderNo, Label: '주문번호' },
{ Value: customer.name, Label: '고객명' },
{ Value: totalAmount, Label: '주문금액' },
{ Value: status, Label: '상태',
Criticality: statusCriticality }
],
UI.Facets: [{
$Type : 'UI.ReferenceFacet',
Label : '주문 정보',
Target: '@UI.FieldGroup#Main'
}],
UI.FieldGroup #Main: {
Data: [
{ Value: orderNo },
{ Value: totalAmount },
{ Value: currency },
{ Value: status }
]
}
);
statusCriticality는 가상 필드로 두고 Node.js 핸들러에서 계산합니다. 이때 로깅과 방어 코드를 함께 넣는 것이 일반적입니다.
// srv/order-service.js
const cds = require('@sap/cds');
const LOG = cds.log('order-service');
module.exports = class OrderService extends cds.ApplicationService {
init() {
const map = { N: 2, P: 1, D: 3 }; // 2=노랑, 1=빨강, 3=초록
this.after('READ', 'SalesOrders', rows => {
for (const row of Array.isArray(rows) ? rows : [rows]) {
if (!row) continue;
row.statusCriticality = map[row.status] ?? 0;
if (!(row.status in map))
LOG.warn('알 수 없는 상태 코드', { id: row.ID, status: row.status });
}
});
return super.init();
}
};
CDS 쪽에는 virtual statusCriticality : Integer;를 프로젝션에 추가하면 됩니다. 이 패턴만으로 필터 바 + 색상 표시 리스트 + 상세 페이지가 완성됩니다.
3단계 — 프로덕션: Draft 편집, 보안, 성능, 테스트
화면에서 직접 생성·수정하려면 Draft를 켭니다. Fiori Elements는 Draft가 활성화된 엔티티에 대해 편집 UI를 자동 제공합니다.
annotate OrderService.SalesOrders with @odata.draft.enabled;
// 보안: 역할 기반 접근 제어
annotate OrderService with @(requires: 'authenticated-user');
annotate OrderService.SalesOrders with @(restrict: [
{ grant: 'READ', to: 'Viewer' },
{ grant: ['CREATE','UPDATE','DELETE'], to: 'SalesManager' }
]);
// 성능: 페이지당 최대 반환 건수 제한
annotate OrderService.SalesOrders with @cds.query.limit: { default: 20, max: 100 };
고객 선택 필드에는 Value Help를 붙여 드롭다운 검색을 제공합니다.
annotate OrderService.SalesOrders:customer with @(
Common.ValueList: {
CollectionPath: 'Customers',
Parameters: [
{ $Type: 'Common.ValueListParameterInOut',
LocalDataProperty: customer_ID, ValueListProperty: 'ID' },
{ $Type: 'Common.ValueListParameterDisplayOnly',
ValueListProperty: 'name' }
]
}
);
마지막으로 cds.test로 서비스 레벨 회귀 테스트를 작성해 두면 annotation 리팩터링 시 안심할 수 있습니다.
// test/order-service.test.js
const cds = require('@sap/cds');
const { GET, expect } = cds.test(__dirname + '/..');
it('LineItem이 metadata에 노출된다', async () => {
const { data } = await GET('/odata/v4/order/$metadata');
expect(data).to.include('UI.LineItem');
});
⚠️ 흔한 실수 / 트러블슈팅
Q1. annotation을 작성했는데 화면에 아무 변화가 없어요.
가장 흔한 원인은 두 가지입니다. 첫째, db 레벨 엔티티에 annotate했는지 확인하세요 — UI annotation은 서비스 프로젝션 엔티티에 적용하는 것이 일반적입니다. 둘째, annotations.cds 파일이 모델에 포함되는지 확인하세요.
cds compile srv -s OrderService --to edmx출력에UI.LineItem이 보이지 않으면 파일이 로드되지 않은 것입니다.
Q2. Fiori preview에서 수정 버튼(Edit)이 보이지 않아요.
Fiori Elements V4 템플릿은 Draft 기반 편집을 전제로 합니다.
@odata.draft.enabled가 없으면 읽기 전용으로 렌더링됩니다. Draft를 켠 뒤에는 key 관리가cuid기반인지,@readonly가 실수로 걸려 있지 않은지 확인하세요.
Q3. Criticality 색상이 안 나오고 숫자만 보입니다.
Criticality에는 상태 텍스트 필드가 아니라 0~3 정수를 담은 별도 필드를 지정해야 합니다(0=중립, 1=빨강, 2=노랑, 3=초록). 또한 virtual 필드 선언 후 after-READ 핸들러가 배열/단건 케이스를 모두 처리하는지 점검하세요.
Q4. 필터 바에 연관 필드가 안 뜹니다.
SelectionFields에는 경로 표현이 제한적입니다. 연관관계는customer_ID처럼 외래키 필드를 지정하고 Value Help를 붙이는 방식이 일반적으로 안정적입니다.
🚀 이어서 다뤄볼 주제
annotations로 기본 화면을 손에 넣었다면, 다음 주제로 확장해 보세요.
- SAP Fiori tools의 Page Editor — annotation을 GUI로 편집하고 XML fragment로 커스텀 컬럼 확장
- Analytical List Page / Chart annotation —
@UI.Chart,@Analytics로 대시보드형 화면 구성 - Side Effects와 Actions —
@Common.SideEffects, bound action으로 버튼 기반 업무 프로세스 구현 - i18n 다국어 — Label을
'{i18n>orderNo}'로 치환해 번역 가능한 UI 만들기
📚 더 깊이 볼 자료
- CAP 공식 문서 — Serving Fiori UIs (capire)
- help.sap.com — SAP Fiori Elements 제품 문서
- help.sap.com — SAP Fiori tools (Page Editor, annotation 지원)
- help.sap.com — SAP BTP에서의 CAP 개발 가이드
- SAPUI5 Documentation — Developing Apps with Fiori Elements
- SAP-samples — Fiori Elements Feature Showcase (annotation 예제 모음)
- OData UI Vocabulary 레퍼런스
댓글 0
아직 댓글이 없습니다.