ABAP

CDS 어노테이션 30초 만에 파악 — UI·OData 제어 핵심 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요와 이 글에서 얻어갈 것

CDS(Core Data Services)에서 @Annotation은 단순한 주석이 아닙니다. Fiori Elements가 자동으로 렌더링하는 리스트 컬럼, OData $metadata에 노출되는 필드의 의미론적 타입, 검색 우선순위, VDM(Virtual Data Model) 역할까지 모두 어노테이션 한 줄로 결정됩니다. 이 글에서는 실무에서 반드시 알아야 할 5가지 핵심 어노테이션 카테고리를 PurchaseOrder 시나리오로 재구성하여 살펴봅니다.

  • OData 노출을 제어하는 @OData.publish의 동작 원리 이해
  • Fiori Elements 리스트/오브젝트 페이지를 만드는 @UI 어노테이션 5종 실전 적용
  • 통화·수량·이메일 등을 표현하는 @Semantics로 값의 의미 명확화
  • Enterprise Search 결과 랭킹을 조정하는 @Search 활용
  • VDM 계층 역할을 부여하는 @ObjectModel과 Metadata Extension 분리 패턴

알고 있어야 할 배경

이 글은 ABAP CDS View 문법(define view entity, association, select from)을 한 번이라도 작성해 본 개발자를 대상으로 합니다. OData v2/v4의 서비스 엔드포인트 개념, Fiori Elements List Report / Object Page 템플릿의 기본 구조, 그리고 SAP GUI 또는 ADT에서 CDS 오브젝트를 활성화해 본 경험이 있으면 충분합니다.

환경 및 도구 준비

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

  • ABAP Platform: SAP S/4HANA 2022 On-Premise 또는 ABAP Environment (Steampunk) 최신 릴리스 권장
  • IDE: ADT (Eclipse 2024-03 + ABAP Development Tools 3.40 이상)
  • 미리보기: Fiori Elements Preview 또는 SAP Gateway Client
  • OData 확인: 브라우저에서 /sap/opu/odata/sap/서비스명/$metadata로 XML 확인

핵심 개념: 어노테이션이 만들어내는 두 가지 산출물

CDS 어노테이션은 크게 두 가지 결과물을 생성합니다. 하나는 OData 서비스의 $metadata에 반영되는 기술적 메타데이터이고, 다른 하나는 Fiori Elements가 UI를 자동 렌더링할 때 참조하는 UI 힌트입니다.

CDS View가 "요리 재료 목록"이라면 어노테이션은 "이 재료는 소스로, 저 재료는 장식용"이라고 알려주는 라벨입니다. 같은 데이터라도 라벨에 따라 완전히 다른 화면이 만들어집니다.

어노테이션 카테고리는 접두어(namespace)로 구분됩니다.

Namespace제어 대상대표 어노테이션
@OData서비스 노출@OData.publish
@UIFiori Elements 화면 레이아웃@UI.lineItem, @UI.facet, @UI.headerInfo
@Semantics필드 의미(통화/수량/이메일 등)@Semantics.amount.currencyCode
@SearchEnterprise Search 활성화@Search.searchable, @Search.defaultSearchElement
@ObjectModelVDM 역할 정의@ObjectModel.usageType

어노테이션은 계층적 우선순위를 가집니다. Metadata Extension에서 선언한 값이 가장 높은 우선순위를 가지며, 기본 CDS View 내부 선언이 가장 낮습니다.

실전 예제 1단계: OData 서비스 노출과 Semantics 적용

구매오더 헤더를 조회하는 기본 CDS View에 @OData.publish: true와 Semantics 어노테이션을 적용합니다.

@AbapCatalog.sqlViewName: 'ZVPORDHDR'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Purchase Order Header - Basic'
@OData.publish: true
define view Z_C_PurchaseOrderHdr
  as select from ztab_pord_hdr
{
  key po_number       as PurchaseOrderNo,
      supplier_id     as SupplierID,
      order_date      as OrderDate,

      @Semantics.amount.currencyCode: 'CurrencyCode'
      total_amount    as TotalAmount,

      @Semantics.currencyCode: true
      currency_code   as CurrencyCode,

      @Semantics.user.createdBy: true
      created_by      as CreatedBy
}

@Semantics.amount.currencyCode는 금액 필드와 통화 필드를 연결합니다. OData $metadatasap:unit 속성이 자동으로 생성되어 Fiori Elements가 통화 심볼을 표시합니다. 금액 필드에만 붙이는 것으로는 부족하고 통화 필드에도 반드시 @Semantics.currencyCode: true를 붙여야 합니다.

활성화 후 SAP Gateway에서 서비스를 등록하면 OData 엔드포인트가 생성됩니다. 이 시점에서는 UI 힌트가 없어 Fiori Elements 미리보기 시 컬럼 순서가 무작위로 나열됩니다.

실전 예제 2단계: UI와 Search 어노테이션 실무 적용

Fiori Elements List Report 컬럼 순서, Object Page 헤더, 검색 활성화를 어노테이션으로 제어합니다.

@Search.searchable: true
@ObjectModel.usageType: {
  serviceQuality: #D,
  sizeCategory:   #L,
  dataClass:      #TRANSACTIONAL
}
define view Z_C_PurchaseOrderHdr2
  as select from ztab_pord_hdr
{
      @UI.headerInfo: {
        typeName:       'Purchase Order',
        typeNamePlural: 'Purchase Orders',
        title: { value: 'PurchaseOrderNo' }
      }
      @UI.facet: [{
        id:       'General',
        purpose:  #STANDARD,
        type:     #IDENTIFICATION_REFERENCE,
        label:    'General Information',
        position: 10
      }]

  key @UI.lineItem:       [{ position: 10, label: 'PO No.' }]
      @UI.identification: [{ position: 10 }]
      @Search.defaultSearchElement: true
      po_number       as PurchaseOrderNo,

      @UI.lineItem: [{ position: 20, label: 'Supplier' }]
      @Search.defaultSearchElement: true
      @Search.fuzzinessThreshold: 0.8
      supplier_id     as SupplierID,

      @UI.lineItem: [{ position: 30, label: 'Order Date' }]
      order_date      as OrderDate,

      @UI.lineItem: [{ position: 40, label: 'Amount', importance: #HIGH }]
      @Semantics.amount.currencyCode: 'CurrencyCode'
      total_amount    as TotalAmount,

      @Semantics.currencyCode: true
      currency_code   as CurrencyCode
}

각 어노테이션이 담당하는 역할입니다.

  • @UI.headerInfo: Object Page 상단 타이틀/서브타이틀 정의
  • @UI.facet: Object Page 섹션(탭/블록) 구성. type: #IDENTIFICATION_REFERENCE@UI.identification 어노테이션이 붙은 필드들을 해당 섹션에 표시
  • @UI.lineItem: List Report 테이블 컬럼과 순서. position은 10단위 간격 권장
  • @Search.defaultSearchElement: true: Enterprise Search와 검색창의 기본 검색 대상으로 지정
  • @Search.fuzzinessThreshold: HANA fuzzy search 유사도 임계치(0.0~1.0)
  • @ObjectModel.usageType: VDM에서 이 View의 역할과 데이터 성격을 정의

실전 예제 3단계: Metadata Extension으로 관심사 분리

프로덕션에서는 비즈니스 정의(View)와 UI 정의(Extension)를 분리하는 것이 권장됩니다.

" 기본 View — UI 어노테이션 제거, 데이터 정의에 집중
@Metadata.allowExtensions: true
@OData.publish: true
define view Z_C_PurchaseOrderHdr3
  as select from ztab_pord_hdr
{
  key po_number     as PurchaseOrderNo,
      supplier_id   as SupplierID,
      order_date    as OrderDate,

      @Semantics.amount.currencyCode: 'CurrencyCode'
      total_amount  as TotalAmount,

      @Semantics.currencyCode: true
      currency_code as CurrencyCode
}

" Metadata Extension — UI 어노테이션만 담당
@Metadata.layer: #CUSTOMER
annotate view Z_C_PurchaseOrderHdr3 with
{
  @UI.headerInfo: {
    typeName: 'Purchase Order',
    title:    { value: 'PurchaseOrderNo' }
  }
  @UI.facet: [{
    id: 'Main', purpose: #STANDARD,
    type: #IDENTIFICATION_REFERENCE, position: 10
  }]

  @UI.lineItem:       [{ position: 10, label: 'PO No.' }]
  @UI.identification: [{ position: 10 }]
  PurchaseOrderNo;

  @UI.lineItem: [{ position: 20, label: 'Supplier' }]
  SupplierID;

  @UI.lineItem: [{ position: 30, label: 'Order Date' }]
  OrderDate;

  @UI.lineItem: [{ position: 40, label: 'Amount', importance: #HIGH }]
  TotalAmount;
}

@Metadata.allowExtensions: true가 기본 View에 있어야 Extension이 동작합니다. @Metadata.layer는 CORE → LOCALIZATION → INDUSTRY → PARTNER → CUSTOMER 순으로 우선순위가 높아집니다.

UI가 변경되어도 기본 View를 수정하지 않으므로 Transport 리스크가 감소합니다. 여러 프로젝션이 동일 기본 View를 공유하면서 각기 다른 Extension을 붙일 수 있어 재사용성도 향상됩니다.

자주 겪는 실수와 트러블슈팅

Q1. Metadata Extension을 만들었는데 어노테이션이 반영되지 않습니다.
기본 View에 @Metadata.allowExtensions: true가 선언되어 있는지 확인하세요. 이 플래그가 없으면 Extension은 활성화되어도 무시됩니다. @Metadata.layer가 낮은 레이어라면 상위 레이어에서 덮어썼을 수 있습니다.

Q2. @Semantics.amount.currencyCode를 붙였는데 통화 심볼이 안 나옵니다.
참조되는 통화 필드에도 @Semantics.currencyCode: true를 반드시 붙여야 합니다. 수량 필드도 마찬가지로 @Semantics.quantity.unitOfMeasure@Semantics.unitOfMeasure: true가 짝으로 존재해야 합니다.

Q3. Fiori Elements Preview에 컬럼이 뒤죽박죽입니다.
@UI.lineItemposition을 10, 20, 30처럼 간격을 두어 지정하세요. 중복되거나 누락되면 렌더링 순서가 예측 불가능해집니다. List Report는 @UI.lineItem을, Object Page는 @UI.identification@UI.facet을 사용합니다.

Q4. 어노테이션 변경이 프로덕션에 반영되지 않습니다.
OData 메타데이터 캐시를 /IWFND/CACHE_CLEANUP/IWBEP/CACHE_CLEANUP으로 삭제하세요. RAP 기반이라면 Service Binding 재활성화가 필요할 수 있습니다.

더 깊이 들어갈 주제

이 글에서 다룬 5가지 어노테이션 카테고리는 시작점입니다. 실무에서는 @UI.selectionField와 Value Help 어노테이션으로 필터 UX를 개선하고, @Consumption.valueHelpDefinition으로 CDS 기반 검색 도움말을 구현합니다. RAP Behavior와의 결합(@ObjectModel.readOnly, @ObjectModel.mandatory), Draft 시나리오 어노테이션, Analytical CDS의 KPI 어노테이션으로 범위를 넓혀가면 Fiori Elements 화면 제어를 완전히 자동화할 수 있습니다.

댓글 0

아직 댓글이 없습니다.