1. 개요 및 무엇을 얻어갈 것인가
SAP S/4HANA 환경에서 CDS(Core Data Services) 뷰를 작성하다 보면 I_SalesOrder, C_SalesOrderTP, P_SalesOrderCube 같은 접두사가 붙은 이름들을 마주치게 됩니다. 이 접두사들은 단순한 명명 규칙이 아니라, SAP의 Virtual Data Model(VDM) 아키텍처의 계층 구조를 그대로 드러내는 설계 언어입니다. 이 글에서는 VDM 3계층 모델의 배경, 각 접두사(I_, C_, P_)의 역할, 그리고 실제 개발 현장에서 어떤 순서로 뷰를 조합해 나가는지를 실전 예제와 함께 정리합니다.
- VDM 계층 모델(Basic, Composite, Consumption)의 존재 이유 이해
- I_, C_, P_ 접두사의 의미와 각 계층의 책임 구분
- 계층별로 CDS 뷰를 작성할 때 유의할 어노테이션과 참조 방향
- 실제 판매 주문(SalesOrder) 시나리오로 3계층을 직접 조립하는 감각 익히기
- 흔한 명명 실수와 잘못된 계층 참조를 진단하는 방법
2. 이 글을 편하게 읽기 위한 배경
이 글은 ABAP CDS 뷰의 기본 문법(define view, association, @Analytics 어노테이션)을 한 번이라도 다뤄 본 개발자를 대상으로 합니다. ADT(ABAP Development Tools)에서 DDLS 오브젝트를 생성하고 액티베이션까지 해 본 경험, 그리고 S/4HANA 데이터베이스 테이블(예: VBAK, KNA1)의 존재를 알고 있으면 충분합니다. OData 서비스 노출이나 RAP(RESTful ABAP Programming Model) 지식은 필수는 아니지만, C_ 계층 이해에 도움이 됩니다.
3. 실습 환경과 준비물
이 글의 예제는 다음 환경을 기준으로 작성되었습니다. 온프레미스와 클라우드 모두 명명 규칙은 동일하게 적용되지만, 사용 가능한 어노테이션과 릴리스 수준이 조금씩 다를 수 있습니다.
- SAP S/4HANA: 2022 FPS02 이상 (온프레미스) 또는 SAP S/4HANA Cloud Public Edition 2402 이상
- ABAP Platform: 2022 이상 (ADT 3.36 이상 권장)
- 개발 도구: Eclipse + ABAP Development Tools 최신 버전
- 권한:
S_DEVELOP(DDLS 오브젝트 생성), 로컬 패키지 또는 개발용 트랜스포트 - 참고 트랜잭션/툴:
SEPM_SALES등 EPM 샘플 데이터, View Browser(F2 Data Preview)
실습 데이터는 표준 예시(Enterprise Procurement Model, EPM)를 사용해도 되고, 회사의 VBAK/VBAP 테이블을 직접 조회해도 무방합니다. 다만 프로덕션 데이터를 개발 환경에 노출할 때는 마스킹 정책을 반드시 확인해야 합니다.
4. VDM이라는 지도를 읽는 방법
VDM은 SAP S/4HANA의 데이터를 "누가 봐도 이해할 수 있는 비즈니스 언어"로 재정의한 뷰의 집합입니다. 원천 테이블은 수십 년 누적된 R/3 시절의 이름(VBAK, MARA 등)을 그대로 갖고 있어 신규 개발자에게 진입 장벽이 높습니다. VDM은 이 원천을 감싸서 SalesOrder, Product, Customer 같은 의미 있는 이름과 연관 관계로 다시 그린 지도입니다.
이 지도는 세 개의 층으로 나뉩니다. 도시의 지하 배관을 상상해 보면 좋습니다.
- I_ (Interface View, Basic Interface View) — 지하 배관. 원천 테이블에 가장 가까운 계층으로, 필드 이름을 표준화하고 기본 어소시에이션을 정의합니다. 재사용이 가장 활발한 층입니다.
- C_ (Consumption View) — 최상층 수도꼭지. UI(Fiori), OData, 분석 툴 같은 최종 소비자를 위한 층입니다. 특정 시나리오(트랜잭션, 분석, 검색)에 맞춰 필터링·라벨·UI 어노테이션이 붙습니다.
- P_ (Private View) — 배관 내부의 조립 파트. 특정 개발자 그룹 내부에서만 재사용되며, 외부 노출을 목적으로 하지 않습니다. 큐브·집계·중간 계산에 자주 쓰입니다.
참조 방향은 단방향이 원칙입니다. C_는 I_/P_를 참조할 수 있지만, I_가 C_를 참조하면 안 됩니다. 이 방향성이 무너지면 재사용 계층인 I_가 특정 UI 시나리오에 종속되어 지도의 의미가 사라집니다.
기억 팁: Interface = 인프라, Consumption = 소비자, Private = 내부용. 아래에서 위로 흐른다.
추가로 알아두면 좋은 접두사도 있습니다. R_(Restricted, RAP BO의 루트 뷰), E_(Extension), A_(Application) 등이 릴리스에 따라 등장하지만, 이 글에서는 가장 널리 쓰이는 세 가지에 집중합니다.
5. 판매 주문 시나리오로 3계층 직접 조립하기
5-1. 1단계 — I_ 계층: 원천을 비즈니스 언어로 감싸기
먼저 판매 주문 헤더 테이블 VBAK를 감싸는 인터페이스 뷰를 만듭니다. 필드명을 CamelCase 비즈니스 용어로 바꾸고, 고객 마스터로의 어소시에이션을 정의하는 것이 핵심입니다.
@AbapCatalog.sqlViewName: 'ZISLSORDHDR'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Order Header - Interface'
@VDM.viewType: #BASIC
@ObjectModel.dataCategory: #FACT
@ObjectModel.representativeKey: 'SalesOrder'
define view ZI_SalesOrderHeader
as select from vbak
association [0..1] to ZI_Customer as _Customer
on $projection.SoldToParty = _Customer.CustomerId
{
key vbeln as SalesOrder,
erdat as CreationDate,
auart as SalesOrderType,
vkorg as SalesOrganization,
kunnr as SoldToParty,
netwr as NetAmount,
waerk as TransactionCurrency,
_Customer
}
포인트를 짚어 봅니다. @VDM.viewType: #BASIC이 명시적으로 "이 뷰는 I_ 계층"이라고 선언합니다. @AccessControl.authorizationCheck: #NOT_REQUIRED는 인터페이스 뷰에서는 권한 체크를 상위 계층에 위임한다는 뜻입니다. 필드명은 VBELN 같은 4자리 약어 대신 SalesOrder라는 자기설명적 이름으로 바꿨습니다.
5-2. 2단계 — P_ 계층: 내부 재사용을 위한 집계
실무에서는 "월별 판매 조직별 주문 합계" 같은 중간 집계가 자주 필요합니다. 이 집계를 여러 소비자 뷰(대시보드용, 리포트용)에서 공유하고 싶지만 외부 애플리케이션에는 노출하고 싶지 않을 때 P_ 계층이 등장합니다.
@AbapCatalog.sqlViewName: 'ZPSLSMONTHAGG'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Monthly Aggregation - Private'
@VDM.viewType: #COMPOSITE
@VDM.private: true
@Analytics.dataCategory: #CUBE
define view ZP_SalesOrderMonthlyAgg
as select from ZI_SalesOrderHeader
{
key SalesOrganization,
key cast( concat(
substring( cast( CreationDate as abap.char( 8 ) ), 1, 4 ),
substring( cast( CreationDate as abap.char( 8 ) ), 5, 2 )
) as abap.char( 6 ) ) as YearMonth,
TransactionCurrency,
@Semantics.amount.currencyCode: 'TransactionCurrency'
sum( NetAmount ) as TotalNetAmount,
count( distinct SalesOrder ) as OrderCount
}
group by
SalesOrganization,
CreationDate,
TransactionCurrency
여기서 두 가지가 새롭습니다. 첫째, @VDM.private: true가 붙어 있어 이 뷰가 팀 외부에서 직접 소비되면 안 된다는 의도를 표현합니다. 둘째, 뷰가 I_ 계층을 참조합니다 — 이 방향은 허용되지만, 반대로 I_가 P_를 참조하는 것은 금지입니다. @Semantics.amount.currencyCode는 금액 필드에 반드시 붙여야 할 시맨틱 어노테이션으로, 통화 인식 집계를 위해 필요합니다.
5-3. 3단계 — C_ 계층: Fiori/OData 소비자를 위한 완성품
이제 최종 사용자가 Fiori 앱에서 보는 트랜잭션 뷰를 만듭니다. UI 라벨, 필터 기본값, 헤더 정보 어노테이션이 이 계층에서 폭발적으로 늘어납니다.
@AbapCatalog.sqlViewName: 'ZCSLSORDDASH'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Dashboard - Consumption'
@VDM.viewType: #CONSUMPTION
@OData.publish: true
@UI.headerInfo: { typeName: 'Sales Order',
typeNamePlural: 'Sales Orders' }
define view ZC_SalesOrderDashboard
as select from ZI_SalesOrderHeader as Header
association [1..1] to ZP_SalesOrderMonthlyAgg as _MonthlyAgg
on $projection.SalesOrganization = _MonthlyAgg.SalesOrganization
{
@UI.lineItem: [{ position: 10, label: 'Order' }]
@UI.identification: [{ position: 10 }]
key Header.SalesOrder,
@UI.lineItem: [{ position: 20, label: 'Created On' }]
Header.CreationDate,
@UI.lineItem: [{ position: 30, label: 'Sales Org' }]
Header.SalesOrganization,
@UI.lineItem: [{ position: 40, label: 'Customer' }]
Header._Customer.CustomerName,
@Semantics.amount.currencyCode: 'TransactionCurrency'
@UI.lineItem: [{ position: 50, label: 'Net Amount' }]
Header.NetAmount,
Header.TransactionCurrency,
_MonthlyAgg
}
@AccessControl.authorizationCheck: #CHECK가 I_ 계층과 대비되는 점에 주목합니다. 실제 권한 필터링은 최종 소비 계층에서 걸립니다. @OData.publish: true로 OData 서비스가 자동 생성되며, Fiori Elements 앱은 @UI.* 어노테이션만 보고도 리스트/디테일 페이지를 렌더링합니다. 이 뷰는 I_와 P_를 모두 참조하지만 그 반대는 없습니다 — 계층의 방향성이 명확히 지켜집니다.
6. 자주 밟는 지뢰와 해결법
계층 규칙을 처음 다룰 때 반복해서 나오는 실수와 진단법을 정리합니다.
- Q1. I_ 뷰에
@UI.lineItem을 붙였는데 왜 문제인가요?
I_는 재사용을 목적으로 하는 계층이라 특정 UI 시나리오에 종속되면 안 됩니다. 열 순서·라벨·필터 기본값은 소비자마다 다르므로 C_에서 정의하는 것이 일반적으로 권장됩니다. UI 어노테이션이 I_에 스며들면 다른 앱이 그 뷰를 재사용할 때 원치 않는 어노테이션까지 상속받습니다. - Q2. C_ 뷰끼리 서로 참조해도 되나요?
가능은 하지만 권장되지 않습니다. C_는 종점(sink) 계층이라는 것이 VDM의 전제입니다. 공통 로직이 생겼다면 그 부분을 I_ 또는 P_로 승격시키는 리팩터링이 더 건강한 방향입니다. - Q3. P_와 I_ 중 어떤 것을 만들어야 할지 판단 기준이 뭔가요?
"이 뷰를 우리 팀 밖의 개발자도 참조할 가능성이 있는가?"를 스스로에게 물어봅니다. Yes면 I_, No면 P_입니다. 또한 집계/CUBE 성격이면 대개 P_ 또는#COMPOSITE가 자연스럽습니다. - Q4. SQL view 이름(
ZISLSORDHDR)이 왜 별도로 필요한가요?
DB 레벨에서 실제 생성되는 뷰 이름이 16자 제한을 받기 때문입니다. ADT가@AbapCatalog.sqlViewName을 요구하는 이유이며, 프로젝트 명명 규칙(예:Z+ 계층문자 + 도메인 축약)을 정해 두면 SE11에서도 찾기 쉬워집니다. - Q5.
@VDM.viewType을 안 붙이면 어떻게 되나요?
문법 오류는 아니지만 코드 리뷰·툴링에서 계층 검증이 어려워집니다. ATC(ABAP Test Cockpit) 룰셋에 VDM 검사가 포함되어 있으면 경고가 발생할 수 있으므로 붙이는 것이 안전합니다.
디버깅 팁: 잘못된 계층 참조가 의심되면 ADT의 "Where-Used List"로 뷰가 어느 방향으로 참조되는지 시각화하고, View Browser에서 뷰 타입 필터를 걸어 목록을 정리하는 것이 빠릅니다.
7. 여기서 더 확장하고 싶다면
VDM 명명 규칙을 익혔다면 자연스럽게 이어지는 주제들이 있습니다. 먼저 RAP(RESTful ABAP Programming Model)의 R_ 뷰와 Behavior Definition을 학습하면, C_ 계층이 어떻게 트랜잭션 앱으로 확장되는지 이해할 수 있습니다. 다음으로 Analytical Queries(@Analytics.query: true)를 다뤄 P_ CUBE 위에 쿼리 뷰를 얹는 방법을 익히면 임베디드 분석까지 시야가 넓어집니다. 마지막으로 CDS Access Control(DCL)과 Metadata Extension을 학습하면 C_ 계층의 UI/권한 코드를 시나리오별로 분리하는 실전 감각이 완성됩니다.
8. 더 깊이 파고들 링크
- SAP Help — Virtual Data Model (VDM) 개요
- SAP Help — CDS View Types and VDM View Types
- SAP Help — S/4HANA VDM Naming Conventions
- SAP Help — ABAP CDS Annotations (@VDM, @Analytics)
- SAP Community Blog — Virtual Data Model in S/4HANA (개념 정리)
- SAP Developers — ABAP Development Tools 설치 이글
- SAP Help — RAP Business Object and CDS View Roles
댓글 0
아직 댓글이 없습니다.