ABAP

VDM I_ C_ P_ 거꾸로 참조하면 큰일 — 계층 원칙 #shorts #SAP #ABAP

▶ YouTube에서 보기

1. VDM이란 무엇인가 — 탄생 배경과 설계 목적

VDM(Virtual Data Model)은 SAP S/4HANA에서 데이터베이스 테이블을 그대로 노출하지 않고, 의미 있는 비즈니스 개체(엔티티) 단위로 재조립해 제공하기 위한 CDS(Core Data Services) 기반의 논리적 데이터 모델입니다. ECC 시절에는 개발자가 BSEG, VBAK, MARA 같은 물리 테이블에 직접 조인 쿼리를 작성했지만, 이 방식은 필드 의미 파악이 어렵고 재사용성이 낮았습니다. VDM은 이러한 문제를 해결하기 위해 “비즈니스 시맨틱을 담은 뷰들의 계층 구조”를 제공하며, S/4HANA의 Analytics, Fiori, Embedded Analytics, RAP(RESTful Application Programming Model) 등 상위 스택 모두가 VDM 위에서 동작하도록 설계되었습니다.

이 글에서는 VDM의 뼈대를 이루는 세 가지 접두사 I_, C_, P_가 왜 존재하는지, 각각 언제 사용해야 하는지, 그리고 실제 SalesOrder 도메인에서 어떻게 조합되는지를 단계별로 살펴봅니다.

2. 이 글에서 다루는 개념 배경

ABAP CDS 뷰의 기본 문법(define view, @AbapCatalog, association)에 익숙하고, ADT(ABAP Development Tools in Eclipse)에서 DDL 소스를 활성화해 본 경험이 있으면 좋습니다. 또한 SAP S/4HANA의 표준 CDS 뷰(예: I_SalesOrder, C_SalesOrderTP)를 데이터 미리보기로 열어본 적이 있다면 개념 흡수가 훨씬 빠릅니다. 어노테이션 개념(@Analytics.dataCategory, @VDM.viewType)에 대한 기초 지식도 도움이 됩니다.

3. 세 접두사의 정체 — Interface, Consumption, Private

VDM 뷰는 크게 Basic / Composite / Consumption이라는 기능적 구분과 Interface / Consumption / Private이라는 노출 범위 구분을 함께 갖습니다. 실무에서 자주 마주치는 접두사는 이 노출 범위 관점에서 정의됩니다.

비유하자면, 회사의 부서 구조를 떠올리면 쉽습니다. P_는 "부서 내부 문서"(외부 유출 금지), I_는 "다른 부서와 공유하는 공식 데이터셋", C_는 "고객 앞에서 보여주는 완성된 보고서"에 비유할 수 있습니다.

  • P_ (Private View): 특정 애플리케이션 내부에서만 재사용되는 캡슐화된 뷰. 다른 애플리케이션이나 릴리즈 API로 노출되지 않으며, 저자가 언제든지 시그니처를 바꿀 수 있습니다.
  • I_ (Interface View): 조직 전체에서 재사용 가능한 안정적인 데이터 표준층. 릴리즈된 I_는 하위 호환성이 유지되도록 관리되며, 다른 애플리케이션·확장 개발자가 안심하고 참조할 수 있습니다.
  • C_ (Consumption View): 특정 UI 시나리오(Fiori Elements List Report, Analytical Query 등)를 위해 조립된 소비 계층. UI 어노테이션과 트랜잭션 처리 로직이 포함되며 일반적으로 재사용을 목적으로 하지 않습니다.

도식으로 표현하면 다음과 같은 흐름이 됩니다.

[DB Table]
   ↓ (기본 조합, 도메인 로직)
[P_XxxBasic]      ← Private, 내부 헬퍼
   ↓
[I_Xxx]           ← Interface, 재사용 표준
   ↓
[C_XxxTP / C_XxxQuery]  ← Consumption, UI/Analytics 종단점
   ↓
[Fiori / OData / RAP]

여기서 중요한 규칙은 화살표가 위에서 아래로만 흐른다는 점입니다. 즉, C_I_를 참조할 수 있지만, I_C_를 참조하는 일은 설계상 허용되지 않습니다.

4. Interface View 실전 — I_ 접두사로 표준 데이터 만들기

가장 먼저 다뤄야 할 계층은 I_입니다. 판매 주문 헤더를 표현하는 커스텀 인터페이스 뷰를 만들어 보겠습니다. 실제 시나리오는 사내 물류팀이 표준 I_SalesOrder에 없는 커스텀 필드(예: 우선순위 코드 ZZ_PriorityCode)를 표준처럼 재사용하기 위한 뷰라고 가정합니다.

@AbapCatalog.sqlViewName: 'ZISOHDR'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Header - Interface View'
@VDM.viewType: #BASIC
@Analytics.dataCategory: #DIMENSION

define view Z_I_SalesOrderHeader
  as select from vbak as SalesOrderHeader
  association [0..*] to Z_I_SalesOrderItem as _Item
    on $projection.SalesOrder = _Item.SalesOrder
{
  key SalesOrderHeader.vbeln           as SalesOrder,
      SalesOrderHeader.auart           as SalesOrderType,
      SalesOrderHeader.vkorg           as SalesOrganization,
      SalesOrderHeader.kunnr           as SoldToParty,
      SalesOrderHeader.erdat           as CreationDate,
      SalesOrderHeader.netwr           as NetAmount,
      SalesOrderHeader.waerk           as TransactionCurrency,
      cast( SalesOrderHeader.zz_priority as abap.char( 2 ) ) as PriorityCode,
      _Item
}

이 뷰의 특징은 첫째, UI 어노테이션이 하나도 없다는 점입니다. 인터페이스 뷰의 임무는 "정확하고 재사용 가능한 데이터"를 제공하는 것이지, "특정 화면에 어떻게 표시할지"가 아니기 때문입니다. 둘째, @VDM.viewType: #BASIC으로 명시해 하위 뷰가 없는 기본 계층임을 선언합니다. 셋째, 필드명은 CamelCase의 비즈니스 시맨틱 이름을 사용합니다.

5. Consumption View 설계 — C_ 접두사로 UI 종단점 조립

이제 위에서 만든 Z_I_SalesOrderHeader를 Fiori Elements List Report에서 소비할 수 있는 형태로 감싸는 C_ 뷰를 작성합니다. 여기서는 화면 표시 로직, 검색 필드, 값 도움말 등이 어노테이션으로 추가됩니다.

@AbapCatalog.sqlViewName: 'ZCSOHDR'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order - Consumption for List Report'
@VDM.viewType: #CONSUMPTION
@OData.publish: true
@UI: {
  headerInfo: { typeName: 'Sales Order',
                typeNamePlural: 'Sales Orders',
                title: { value: 'SalesOrder' } }
}

define view Z_C_SalesOrderListReport
  as select from Z_I_SalesOrderHeader
{
  @UI.lineItem: [{ position: 10, label: 'Order No.' }]
  @UI.selectionField: [{ position: 10 }]
  key SalesOrder,

  @UI.lineItem: [{ position: 20, label: 'Order Type' }]
      SalesOrderType,

  @UI.lineItem: [{ position: 30, label: 'Sales Org.' }]
  @UI.selectionField: [{ position: 20 }]
      SalesOrganization,

  @UI.lineItem: [{ position: 40, label: 'Customer' }]
      SoldToParty,

  @UI.lineItem: [{ position: 50, label: 'Net Value' }]
  @Semantics.amount.currencyCode: 'TransactionCurrency'
      NetAmount,

      TransactionCurrency,

  @UI.lineItem: [{ position: 60, label: 'Priority' }]
      PriorityCode
}

이 뷰는 @OData.publish: true를 통해 곧바로 OData 서비스로 노출되며, Fiori Elements가 어노테이션을 읽어 리스트 컬럼과 검색 필드를 자동 렌더링합니다. C_ 뷰는 재사용을 위한 것이 아니라 하나의 화면을 위한 조립품이라는 점이 핵심입니다. 다른 화면이 같은 데이터를 필요로 한다면 별도의 C_ 뷰를 만들어야 합니다.

6. Private View의 캡슐화 — P_ 접두사 활용 패턴

P_ 뷰는 특정 애플리케이션이 내부적으로만 사용하는 헬퍼입니다. 예를 들어 판매 주문 상태 코드 gbstk를 "Open/InProgress/Closed"라는 세 가지 카테고리로 매핑하는 로직이 여러 뷰에서 반복된다면, 이를 P_로 캡슐화할 수 있습니다.

@AbapCatalog.sqlViewName: 'ZPSOSTAT'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Order Status - Private Helper'
@VDM.viewType: #COMPOSITE
@VDM.private: true

define view Z_P_SalesOrderStatusCategory
  as select from vbuk
{
  key vbeln as SalesOrder,
      case gbstk
        when 'A' then 'OPEN'
        when 'B' then 'INPROGRESS'
        when 'C' then 'CLOSED'
        else 'UNKNOWN'
      end as StatusCategory
}

@VDM.private: true가 핵심 선언입니다. 이 뷰는 오직 동일 애플리케이션 내부의 뷰에서만 참조해야 하며, 다른 팀이 이 뷰를 참조하면 향후 필드 변경 시 예고 없이 깨질 수 있습니다.

단계별로 정리하면 다음과 같습니다.

  1. 도메인 로직 중 반복되는 계산·매핑을 식별합니다.
  2. 내부 전용이라는 판단이 서면 P_ 접두사와 @VDM.private: true를 선언합니다.
  3. 이 뷰를 I_ 계층에서만 참조하고, 외부 애플리케이션 문서에는 노출하지 않습니다.
  4. 필드 변경이 필요할 때 참조 뷰 하나만 함께 조정합니다.

7. 실전 예제 — SalesOrder 도메인으로 VDM 3계층 구성하기

지금까지 배운 내용을 SalesOrder 도메인에서 통합해 봅니다. 3계층을 순서대로 쌓으면 다음과 같은 활성화 순서가 됩니다.

  1. P_ 먼저: Z_P_SalesOrderStatusCategory 활성화 (다른 뷰가 참조하기 전에 존재해야 함)
  2. I_ 다음: Z_I_SalesOrderHeader 활성화 — P_ 뷰를 association 또는 join으로 참조
  3. C_ 마지막: Z_C_SalesOrderListReport 활성화 — I_ 뷰를 소비

실무에서 흔한 실수는 활성화 순서를 무시하고 C_부터 만들려는 것입니다. CDS 뷰는 참조된 뷰가 먼저 활성화되어야 하므로, 반드시 하위 계층부터 순서대로 진행해야 합니다.

또 다른 실수는 C_ 뷰에서 P_ 뷰를 직접 참조하는 것입니다. P_는 내부 전용이므로 C_ → I_ → P_ 체인을 통해서만 간접적으로 데이터를 가져와야 합니다.

8. 자주 나오는 오해와 올바른 설계 감각

실무에서 반복적으로 마주치는 함정과 해결 방향을 FAQ 형태로 정리합니다.

Q1. C_ 뷰를 다른 C_ 뷰가 재사용해도 되나요?
권장되지 않습니다. C_는 특정 UI 시나리오에 종속된 어노테이션(UI 라벨, selectionField, OData publish 등)을 포함하므로, 다른 화면이 이를 참조하면 UI 요구가 바뀔 때 연쇄 영향이 발생합니다. 공통 데이터가 필요하면 I_로 끌어올려 재사용하는 것이 올바른 방향입니다.

Q2. I_ 뷰에 UI 어노테이션을 넣으면 안 되나요?
일반적으로 지양됩니다. 인터페이스 뷰의 임무는 "데이터의 시맨틱 정의"이지 "표현 방식 정의"가 아닙니다. UI 어노테이션이 섞이면 분석용, 트랜잭션용, 리스트 리포트용 등 서로 다른 소비자에게 불필요한 결합이 생깁니다.

Q3. 표준 SAP I_ 뷰를 그대로 수정해도 되나요?
안 됩니다. 표준 릴리즈 뷰는 SAP의 업그레이드 대상이므로 수정하면 다음 업그레이드에서 덮어써지거나 충돌합니다. 커스텀 필드 추가는 CDS View Extension(extend view)이나 별도의 Z_I_ 래퍼 뷰를 만들어 처리하는 것이 권장됩니다.

이 외에 흔한 실수로는, 활성화 순서가 P_ → I_ → C_가 되어야 하는데 이를 잊고 상위 뷰부터 만들어 참조 오류가 발생하는 경우, 그리고 @VDM.viewType 어노테이션을 빼먹어 정적 검사에서 경고가 뜨는 경우가 자주 보고됩니다.

댓글 0

아직 댓글이 없습니다.