왜 Semantics 어노테이션이 필수인가
ABAP CDS View를 처음 설계할 때 개발자들이 가장 많이 놓치는 부분이 바로 의미 정보(semantics)입니다. DB 컬럼 관점에서 보면 NET_AMOUNT는 그저 DEC(15,2) 타입의 숫자일 뿐이고, QUANTITY도 DEC(13,3) 숫자에 불과합니다. 하지만 비즈니스 관점에서는 "1,250,000"이 원화인지 달러인지, "500"이 개수인지 킬로그램인지에 따라 완전히 다른 값이 됩니다.
이 글에서 다룰 학습 체크리스트는 다음과 같습니다.
@Semantics.amount.currencyCode와@Semantics.currencyCode페어링 원리 이해@Semantics.quantity.unitOfMeasure와@Semantics.unitOfMeasure페어링 방식 습득- Fiori Elements / Smart Table이 이 정보를 어떻게 렌더링에 활용하는지 파악
- 실무 PurchaseOrder CDS View에서 통화·수량 필드를 정확히 연결하는 패턴 학습
- 어노테이션 누락 시 발생하는 SQL 계산 오류·표시 오류 방지
SAP S/4HANA 2022 이상 및 ABAP Platform 2023 기준으로 설명하며, RAP(RESTful ABAP Programming Model) 기반 앱 개발에도 동일하게 적용됩니다.
사전에 알아두면 좋은 배경 지식
이 글은 ABAP CDS View의 기본 문법(DEFINE VIEW, SELECT FROM, 필드 리스트, CAST)을 이미 알고 있다고 가정합니다. 또한 currency conversion, unit conversion 함수를 접해봤거나, Fiori Elements List Report / Object Page의 자동 렌더링 구조에 대한 감각이 있으면 이해가 훨씬 빠릅니다. ABAP Development Tools(ADT)에서 CDS View를 활성화하는 절차와 @EndUserText.label 같은 프레임워크 어노테이션의 존재를 알고 있다면 충분합니다.
실습 환경과 준비물 정리
실무에서 동일한 결과를 재현하려면 다음 환경이 권장됩니다.
- 백엔드: SAP S/4HANA 2022 FPS02 이상 또는 ABAP Cloud (Steampunk 2308+)
- 개발 도구: Eclipse 2024-03 + ABAP Development Tools 3.38 이상
- 테이블 준비: 커스텀 구매발주 헤더/아이템 테이블 두 개 (
ZTPO_HEADER,ZTPO_ITEM) - 참조 테이블:
TCURC(통화 코드),T006(측정 단위) — 표준 제공 - Fiori 확인용: SAP Fiori Elements Preview 또는 SAP Business Application Studio
커스텀 테이블에는 CURRENCY_CODE(CUKY), NET_AMOUNT(CURR), UOM(UNIT), ORDER_QTY(QUAN) 컬럼이 각각 존재해야 합니다. DB 레벨의 참조 필드(REFTAB/REFFIELD)는 어노테이션 동작과 별개로 반드시 설정되어 있어야 CDS 컴파일이 통과합니다.
Semantics 어노테이션의 동작 원리와 페어링 개념
@Semantics의 핵심은 "두 필드를 한 쌍으로 묶는다"는 아이디어입니다. 은행 창구에서 금액 전표를 처리하는 상황을 생각해봅시다. 창구 직원 앞에 "10,000"이라고만 적힌 종이가 놓여 있으면, 이게 원인지 엔인지 몰라 처리할 수 없습니다. 반드시 "10,000 KRW"처럼 수치 + 단위가 함께 있어야 의미가 완성됩니다.
CDS의 @Semantics는 이 짝을 프레임워크에 알려주는 라벨링 시스템입니다. 크게 네 가지 형태를 기억하면 됩니다.
@Semantics.amount.currencyCode: '필드명'— "이 금액 필드의 통화는 저 필드를 참조하라"@Semantics.currencyCode: true— "나는 통화 코드 필드다"@Semantics.quantity.unitOfMeasure: '필드명'— "이 수량 필드의 단위는 저 필드를 참조하라"@Semantics.unitOfMeasure: true— "나는 측정 단위 필드다"
도식으로 보면 다음과 같이 화살표가 연결됩니다.
NET_AMOUNT ──amount.currencyCode──▶ CURRENCY_CODE (currencyCode: true)
ORDER_QTY ──quantity.unitOfMeasure─▶ UOM (unitOfMeasure: true)
이 화살표가 연결되어야 Fiori Elements의 Smart Field가 자동으로 "1,250,000.00 KRW"처럼 포매팅된 문자열을 만들어냅니다. 화살표가 없으면 1250000이라는 raw number만 노출되어, 소수점 자릿수도 통화별로 다르게 처리되지 않고 통화 기호 표시도 사라집니다.
추가로 알아둘 관련 어노테이션이 있습니다. @Semantics.text: true는 코드 필드에 대한 텍스트 설명 필드임을 표시하며(예: 자재번호에 대한 자재 설명), @Semantics.name.fullName: true는 성명 표시용 필드를 알려줍니다. 이들은 통화/수량과 조합되어 Analytical Query나 Value Help 표시에도 활용됩니다.
1단계 실전 예제: 최소한의 금액-통화 페어링
가장 기초적인 형태부터 시작합니다. 구매발주 헤더의 총액을 표시하는 간단한 View입니다.
@AbapCatalog.sqlViewName: 'ZVPOHEADBASIC'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Purchase Order Header - Basic'
define view ZI_PurchaseOrderHeader_Basic
as select from ztpo_header as poh
{
key poh.po_number as PurchaseOrderNumber,
poh.supplier_id as SupplierId,
@Semantics.amount.currencyCode: 'CurrencyCode'
poh.net_amount as NetAmount,
@Semantics.currencyCode: true
poh.currency_code as CurrencyCode,
poh.created_at as CreatedAt
}
여기서 주의할 지점은 'CurrencyCode'가 alias 기준의 필드명이라는 사실입니다. 원본 컬럼 currency_code가 아니라 SELECT 절에서 부여한 CurrencyCode를 참조해야 합니다. 이걸 헷갈리면 "referenced field not found" 컴파일 에러가 발생합니다.
2단계 실전 예제: 아이템 레벨에 수량-단위와 로깅 어노테이션 추가
실무에서는 헤더-아이템 구조에서 아이템 단에 수량 필드가 존재합니다. 여기에 단위 페어링과 함께, 오류 상황을 대비한 @ObjectModel.readOnly, @EndUserText.label 같은 부가 어노테이션을 함께 살펴봅니다.
@AbapCatalog.sqlViewName: 'ZVPOITEMLOG'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Purchase Order Item with Semantics'
@Metadata.allowExtensions: true
define view ZI_PurchaseOrderItem_Log
as select from ztpo_item as itm
association [1..1] to ZI_PurchaseOrderHeader_Basic as _Header
on $projection.PurchaseOrderNumber = _Header.PurchaseOrderNumber
{
key itm.po_number as PurchaseOrderNumber,
key itm.po_item as PurchaseOrderItem,
itm.material_id as MaterialId,
@EndUserText.label: 'Order Quantity'
@Semantics.quantity.unitOfMeasure: 'OrderUnit'
itm.order_qty as OrderQuantity,
@EndUserText.label: 'Order Unit'
@Semantics.unitOfMeasure: true
itm.uom as OrderUnit,
@EndUserText.label: 'Item Net Amount'
@Semantics.amount.currencyCode: 'ItemCurrency'
itm.item_net_amount as ItemNetAmount,
@Semantics.currencyCode: true
itm.item_currency as ItemCurrency,
@ObjectModel.readOnly: true
cast( itm.order_qty * itm.unit_price as abap.dec(23,2) )
as CalculatedGross,
_Header
}
이 예제에서 눈여겨볼 부분은 CalculatedGross처럼 계산된 필드에도 통화 어노테이션을 붙일 수 있다는 점입니다. 다만 CAST의 결과 타입이 CURR이 아닌 DEC이면, 소비 측에서 통화 포매팅이 제대로 되지 않을 수 있어 실제 운영에서는 currency_conversion() 결과를 저장하는 필드로 다시 감싸는 편이 안전합니다.
3단계 프로덕션 예제: 다국 통화 변환과 성능·보안 고려
실운영 환경에서는 여러 통화가 섞이고, 리포팅용으로 회사통화(예: KRW)로 환산한 값도 함께 필요합니다. 이 경우 currency_conversion 함수와 @Semantics를 결합합니다.
@AbapCatalog.sqlViewName: 'ZVPOREPORT'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Purchase Order Reporting View'
@VDM.viewType: #CONSUMPTION
@Analytics.dataCategory: #CUBE
define view entity ZC_PurchaseOrderReport
as select from ZI_PurchaseOrderItem_Log as itm
{
key itm.PurchaseOrderNumber,
key itm.PurchaseOrderItem,
itm.MaterialId,
@Semantics.quantity.unitOfMeasure: 'OrderUnit'
itm.OrderQuantity,
@Semantics.unitOfMeasure: true
itm.OrderUnit,
@Semantics.amount.currencyCode: 'ItemCurrency'
itm.ItemNetAmount,
@Semantics.currencyCode: true
itm.ItemCurrency,
// 회사통화(KRW) 환산 금액
@Semantics.amount.currencyCode: 'CompanyCurrency'
@EndUserText.label: 'Amount in Company Currency'
currency_conversion(
amount => itm.ItemNetAmount,
source_currency => itm.ItemCurrency,
target_currency => cast( 'KRW' as abap.cuky ),
exchange_rate_date => cast( '20260701' as abap.dats ),
error_handling => 'SET_TO_NULL'
) as AmountInCompanyCurrency,
@Semantics.currencyCode: true
cast( 'KRW' as abap.cuky ) as CompanyCurrency
}
보안 관점에서는 @AccessControl.authorizationCheck: #CHECK를 반드시 지정하고, 별도의 DCL(Data Control Language) 파일로 회사코드/구매조직 기반 권한을 걸어야 합니다. 성능 측면에서는 currency_conversion이 HANA calculation view 레벨에서 실행되므로 대량 데이터 조회 시 WHERE 절 인덱스(구매발주번호, 생성일자)로 결과 집합을 먼저 축소하는 편이 좋습니다.
Fiori Smart Table에서의 렌더링 차이 관찰
Semantics 어노테이션이 있고 없고의 차이는 Fiori Elements List Report에서 극명하게 드러납니다.
| 구분 | 어노테이션 없음 | 어노테이션 있음 |
|---|---|---|
| 금액 표시 | 1250000 | 1,250,000.00 KRW |
| 소수 자릿수 | 고정 | 통화별 자동 (JPY 0자리, KRW 0자리, USD 2자리) |
| 정렬 | 왼쪽 정렬 | 오른쪽 정렬 (금액 관례) |
| 합계 집계 | 통화 무시하고 합산 (오류) | 통화별 그룹 합계 |
| Export to Excel | 숫자 셀만 | 통화 서식이 적용된 셀 |
특히 합계 집계가 중요합니다. Analytical List Page에서 자동 합계를 쓸 때 어노테이션이 빠져 있으면 USD와 KRW를 그대로 더하는 심각한 데이터 왜곡이 발생합니다.
자주 만나는 실수 패턴과 해결 방법
Q1. 컴파일은 통과했는데 Fiori에서 여전히 통화가 안 보입니다.
A. 십중팔구 소비 뷰(Consumption View)에서 어노테이션이 전파되지 않은 경우입니다. Interface View에서 붙였다고 자동 상속되지 않으며, 소비 뷰 필드 리스트에도 명시적으로 다시 붙여야 합니다.
Q2. "Semantic reference field 'XYZ' not found" 에러가 뜹니다.
A. 참조는 반드시 같은 뷰의 SELECT 리스트 alias여야 합니다. Association을 통해 넘어온 필드를 참조하려면 _Header.CurrencyCode가 아니라, 먼저 _Header.CurrencyCode as HeaderCurrency로 노출한 뒤 'HeaderCurrency'를 지정해야 합니다.
Q3. currency_conversion 결과에 어노테이션을 붙였는데 값이 null로 나옵니다.
A. error_handling 옵션 때문입니다. SET_TO_NULL은 환율이 없으면 조용히 null을 반환합니다. 초기 개발 시에는 FAIL_ON_ERROR로 두어 원인을 파악한 뒤, 운영 시 SET_TO_NULL로 전환하는 편이 안전합니다.
Q4. DB 컬럼에 이미 REFTAB/REFFIELD가 있는데 굳이 CDS 어노테이션이 또 필요한가요?
A. 네. DB 레벨 참조는 DDIC 무결성용이고, CDS 어노테이션은 상위 소비 레이어(OData, Fiori, Analytics)에 정보를 노출합니다. 둘은 목적이 다르므로 모두 필요합니다.
Q5. UNION이나 JOIN으로 뷰를 합쳤을 때 어노테이션이 사라집니다.
A. UNION의 경우 결과 뷰에서 다시 명시해야 합니다. 특히 다른 통화 필드를 UNION으로 합칠 때는 통화 필드 자체도 함께 SELECT 리스트에 포함시키고 페어링을 새로 걸어야 합니다.
확장 학습으로 이어질 수 있는 관련 주제들
@Semantics 어노테이션에 익숙해졌다면, 다음 주제로 확장하는 것을 권장합니다. 첫째, UI 어노테이션 그룹인 @UI.lineItem, @UI.selectionField와 조합하여 List Report의 컬럼 순서/필터를 제어하는 방법. 둘째, @ObjectModel 계열로 넘어가 Value Help(@ObjectModel.foreignKey.association)와 텍스트 결합(@ObjectModel.text.element)을 배우는 것. 셋째, RAP에서 Behavior Definition 작성 시 통화/수량 필드의 determination과 validation을 어떻게 걸지 학습하기. 넷째, unit_conversion() 함수로 KG↔TON, EA↔BOX 변환을 뷰 내부에서 처리하는 패턴 익히기.
더 파고들 때 도움이 되는 자료 링크
- SAP Help Portal - ABAP CDS Annotations Overview
- SAP Help Portal - Semantics Annotations for CDS Views
- SAP Help Portal - Virtual Data Model (VDM) for S/4HANA
- SAP Community - CDS Semantics Annotations 심화
- SAP Developers - ABAP CDS View 개발 예제
- SAP Help Portal - currency_conversion 및 unit_conversion 함수
실무에서는 어노테이션 하나 빠뜨렸다고 컴파일 에러가 나지는 않지만, 운영 리포트에서 통화 합계가 어긋나는 순간 프로젝트 신뢰도가 크게 흔들립니다. 처음 뷰를 설계할 때부터 수치 필드 = 반드시 페어 필드 필요라는 원칙을 습관으로 만들어 두는 것이 장기적으로 안정적인 CDS 자산을 쌓는 지름길입니다.
댓글 0
아직 댓글이 없습니다.