ABAP

I_Material vs I_Product 실수 3가지 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 이 글에서 다루는 것

SAP S/4HANA를 다루다 보면 I_MaterialI_Product라는 두 개의 CDS 뷰를 자주 마주치게 됩니다. 둘 다 자재 마스터의 헤더 정보를 노출하고, 내부적으로는 대부분 MARA 테이블에서 데이터를 가져옵니다. 그런데도 SAP는 왜 두 개의 뷰를 별도로 유지하는 것일까요? 이 글에서는 두 뷰의 구조적 차이, 각 Association 구성, 그리고 실무에서 어떤 상황에 어느 것을 선택해야 하는지를 실전 예제로 정리합니다.

  • I_Material과 I_Product의 존재 배경과 도메인 모델 차이 이해
  • 두 뷰의 필드 구성 및 Association 비교
  • SELECT / JOIN / FILTER 실전 CDS 예제 3단계
  • 잘못 선택했을 때의 트러블슈팅과 성능 이슈
  • 확장(Extension) 관점의 안전한 활용 방법

이 글을 읽기 전에 알아두면 좋은 것

ABAP CDS View의 기본 문법(define view, association, @ObjectModel 어노테이션)을 이해하고 있어야 합니다. 또한 자재 마스터(MARA, MAKT, MARC) 테이블 구조에 대한 기본 지식과, S/4HANA에서 Virtual Data Model(VDM) 계층(Basic Interface View / Composite / Consumption)의 개념을 알고 있으면 훨씬 수월합니다.

환경 및 버전 정보

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

  • SAP S/4HANA 2022 또는 2023 On-Premise / Cloud Private Edition
  • ABAP Development Tools (ADT) for Eclipse 2024-03 이상
  • ABAP RESTful Application Programming Model (RAP) 지원 시스템
  • 권한: S_DEVELOP(CDS 개발), S_RS_RSTT(테스트 조회 권한)

참고로 두 뷰 모두 Interface View 계층(I_ 접두어)에 속하며, 소비자 계층(C_)이나 확장 계층(E_)에서 재사용하도록 설계되어 있습니다. 클라우드 에디션에서는 릴리스 상태(Released Status)에 따라 접근 가능 여부가 달라질 수 있으니, ADT의 Repository Object Type 검색으로 릴리스 여부를 먼저 확인하는 것을 권장합니다.

핵심 개념 — 왜 두 개의 뷰가 존재하는가

SAP는 오랫동안 "Material"이라는 용어를 물류/재고 관점의 자재로 사용해왔습니다. 그런데 S/4HANA로 넘어오면서 One Domain Model(ODM) 및 SAP Cloud Platform과의 정합성을 위해 "Product"라는 상위 개념이 도입되었습니다. 쉽게 비유하자면 다음과 같습니다.

Material은 창고에 쌓여 있는 물리적 자재를 관리하는 물류 담당자의 시각이고, Product는 판매·마케팅·회계·엔지니어링이 공통으로 참조하는 비즈니스 엔티티입니다. MARA라는 같은 방(테이블)에 살고 있지만, 방문객이 보는 창문의 위치가 다른 셈입니다.

구조적으로 두 뷰의 핵심 차이는 다음과 같이 정리할 수 있습니다.

구분I_MaterialI_Product
주 키(Key)MaterialProduct
도메인 관점물류/재고 중심범용 비즈니스 엔티티
주요 Association_MaterialGroup, _BaseUnit, _ProductType_Product 하위 다수(_ProductDescription, _ProductSalesDelivery 등)
확장 방향MM/PP 관련 필드 확장에 유리SD/CO/BEV 등 전사 확장에 유리
RAP BO 연결일반적으로 사용 안 함Product 관련 RAP BO의 진입점

즉, I_Material은 "MM 모듈에서 자재번호로 조회하고 싶다"에 가깝고, I_Product는 "제품을 중심으로 판매·설명·규제·회계 데이터를 트리 형태로 확장하고 싶다"에 가깝습니다. 두 뷰의 키 필드는 각각 Material, Product로 이름이 다르지만, 실제 값은 동일한 MATNR을 담습니다. 이것이 초심자에게 가장 큰 혼란 지점입니다.

실전 예제 1단계 — 기본 조회

먼저 특정 자재군(Material Group)에 속한 자재의 기본 속성을 두 뷰로 각각 조회해봅니다. 목적은 동일하지만 필드명이 어떻게 다른지 눈으로 확인해보는 것입니다.

" I_Material 기반 조회
SELECT Material,
       MaterialGroup,
       MaterialType,
       BaseUnit,
       CreationDate
  FROM I_Material
  WHERE MaterialGroup = '01'
  INTO TABLE @DATA(lt_material_view)
  UP TO 50 ROWS.

" I_Product 기반 동일 조회
SELECT Product,
       ProductGroup,
       ProductType,
       BaseUnit,
       CreationDate
  FROM I_Product
  WHERE ProductGroup = '01'
  INTO TABLE @DATA(lt_product_view)
  UP TO 50 ROWS.

두 결과는 값 자체는 동일하지만, 필드명이 MaterialProduct, MaterialGroupProductGroup으로 다릅니다. 이후 프론트엔드나 OData 서비스로 노출할 때, 어떤 도메인 언어를 쓸지에 따라 뷰 선택이 달라진다는 점을 기억해야 합니다.

실전 예제 2단계 — Association을 활용한 실무 시나리오

실무에서는 자재 정보만 보는 경우가 거의 없습니다. 자재명(MAKT), 플랜트별 데이터(MARC), 판매 조직 데이터(MVKE)를 함께 조회하는 경우가 대부분입니다. 이때 I_Product는 이미 잘 정돈된 Association 체계를 갖추고 있어 매우 편리합니다.

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Product with Description and Plant'
define view entity ZI_ProductOverview
  as select from I_Product as Prod

  association [0..*] to I_ProductDescription as _Desc
    on _Desc.Product = Prod.Product

  association [0..*] to I_ProductPlant as _Plant
    on _Plant.Product = Prod.Product
{
  key Prod.Product,
      Prod.ProductType,
      Prod.ProductGroup,
      Prod.BaseUnit,
      Prod.CreationDate,

      _Desc,
      _Plant
}

이 뷰를 ABAP에서 소비할 때는 아래와 같이 Path Expression으로 자연스럽게 하위 데이터를 끌어올 수 있습니다.

SELECT Product,
       ProductType,
       \_Desc[ 1: Language = 'K' ]-ProductDescription AS KoreanName,
       \_Plant[ 1: Plant    = '1000' ]-MRPType         AS MrpTypeAtPlant
  FROM ZI_ProductOverview
  INTO TABLE @DATA(lt_result)
  UP TO 100 ROWS.

IF sy-subrc <> 0.
  MESSAGE |No products retrieved for the given criteria| TYPE 'I'.
  RETURN.
ENDIF.

" 애플리케이션 로그 남기기 (Application Log 프레임워크)
DATA(lo_log) = cl_bali_log=>create_with_header(
  header = cl_bali_header_setter=>create(
             object      = 'ZPROD_LOG'
             subobject   = 'READ'
             external_id = |PROD_READ_{ sy-datum }| ) ).

lo_log->add_item( cl_bali_free_text_setter=>create(
                   severity = if_bali_constants=>c_severity_status
                   text     = |{ lines( lt_result ) } products loaded| ) ).

동일한 시나리오를 I_Material로 구현하려면 MAKT, MARC를 직접 JOIN하거나 별도 뷰를 추가로 정의해야 하는 경우가 많습니다. Association 재사용성 측면에서 I_Product가 유리한 이유가 여기에 있습니다.

실전 예제 3단계 — 프로덕션 관점(성능·보안·테스트)

대량 조회 상황에서는 뷰 선택이 성능에 직접적인 영향을 줍니다. I_Product는 다수의 Association을 정의하고 있어, 필요한 필드만 조회하지 않으면 불필요한 JOIN이 발생할 수 있습니다. 필드 선택을 명시적으로 제한하고, 페이징을 적용하는 것이 안전합니다.

CLASS zcl_product_reader DEFINITION
  PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_product_slim,
             product       TYPE i_product-product,
             product_group TYPE i_product-productgroup,
             base_unit     TYPE i_product-baseunit,
           END OF ty_product_slim,
           tt_product_slim TYPE STANDARD TABLE OF ty_product_slim
                                WITH EMPTY KEY.

    METHODS read_by_group
      IMPORTING iv_group        TYPE i_product-productgroup
                iv_page_size    TYPE i DEFAULT 500
                iv_page_index   TYPE i DEFAULT 0
      RETURNING VALUE(rt_data)  TYPE tt_product_slim
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_product_reader IMPLEMENTATION.
  METHOD read_by_group.
    DATA(lv_offset) = iv_page_index * iv_page_size.

    SELECT Product,
           ProductGroup,
           BaseUnit
      FROM I_Product
      WHERE ProductGroup = @iv_group
      ORDER BY Product
      INTO TABLE @rt_data
      OFFSET @lv_offset
      UP TO @iv_page_size ROWS.
  ENDMETHOD.
ENDCLASS.

보안 측면에서는 두 뷰 모두 @AccessControl.authorizationCheck: #CHECK가 기본이지만, 커스텀 뷰를 만들 때는 반드시 DCL(Data Control Language)을 정의해 S_MATNR 권한을 반영해야 합니다. 특히 I_Product는 판매·회계 등 여러 도메인에서 소비되므로 권한 릭이 발생하면 파급이 큽니다.

테스트는 ABAP Unit + CDS Test Double Framework(cl_cds_test_environment)를 사용해 실제 DB를 건드리지 않고 시나리오를 재현하는 방식을 일반적으로 권장합니다.

CLASS ltc_product_reader DEFINITION FINAL FOR TESTING
  DURATION SHORT RISK LEVEL HARMLESS.
  PRIVATE SECTION.
    CLASS-DATA go_env TYPE REF TO if_cds_test_environment.
    CLASS-METHODS class_setup.
    METHODS read_returns_expected FOR TESTING.
ENDCLASS.

CLASS ltc_product_reader IMPLEMENTATION.
  METHOD class_setup.
    go_env = cl_cds_test_environment=>create(
               i_for_entity = 'I_PRODUCT' ).
  ENDMETHOD.

  METHOD read_returns_expected.
    go_env->insert_test_data( VALUE i_product_tab(
      ( product = 'TEST-01' productgroup = 'ZZZ' baseunit = 'EA' )
    ) ).
    DATA(lt) = NEW zcl_product_reader( )->read_by_group( 'ZZZ' ).
    cl_abap_unit_assert=>assert_equals( act = lines( lt ) exp = 1 ).
  ENDMETHOD.
ENDCLASS.

흔한 실수와 트러블슈팅

FAQ 1. "필드가 없다"는 오류가 납니다.
I_Product에서 MaterialGroup을 찾으면 존재하지 않습니다. 필드명이 ProductGroup으로 리네이밍되어 있기 때문입니다. 반대로 I_Material에서 Product 필드를 참조하려 해도 실패합니다. ADT의 Element Info(F2)로 원본 필드 매핑을 먼저 확인하는 습관을 들이는 것이 좋습니다.

FAQ 2. 두 뷰를 UNION 하면 안 되나요?
기술적으로는 가능하지만 권장되지 않습니다. 필드명, Association 대상, 어노테이션이 달라 소비 계층에서 예측 불가능한 동작이 발생할 수 있습니다. 한쪽 도메인을 정하고 필요한 필드만 캐스팅해서 가져오는 것이 일반적으로 안전합니다.

FAQ 3. 성능이 갑자기 느려졌습니다.
I_Product의 Association을 SELECT 리스트에 직접 넣으면 HANA 옵티마이저가 필요 이상으로 JOIN을 확장할 수 있습니다. SQL Trace(ST05)로 실제 실행 계획을 확인하고, 필요 없는 Association은 명시적으로 제외하거나 @AbapCatalog.viewEnhancementCategory: [#NONE]으로 확장을 차단하는 것이 도움이 됩니다.

추가 팁: 확장 필드가 필요한 경우 I_Product 계열에는 이미 표준 확장 포인트(_ProductExtension류)가 다수 존재합니다. 무작정 커스텀 뷰를 만들기 전에 표준 확장 뷰가 있는지 먼저 검색해보는 것이 좋습니다.

더 깊이 파고들기 위한 방향

이 글의 내용을 익혔다면 다음 주제로 확장해볼 수 있습니다.

  • I_ProductText, I_ProductDescription 등 다국어 처리 뷰의 활용
  • Product 도메인 기반 RAP Business Object 설계
  • Behavior Definition(define behavior for I_Product) 및 액션 정의
  • Custom Analytical Query에서 I_Product를 마스터 데이터로 활용하기
  • Cloud Edition에서 Released API로 노출된 Product 관련 뷰 정리

더 읽어볼 만한 곳

댓글 0

아직 댓글이 없습니다.