ABAP

LFA1→CDS 30초 전환 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 핵심 포인트

구매·조달 모듈에서 공급사(Vendor/Supplier) 정보를 가져올 때 여전히 SELECT * FROM lfa1 코드를 그대로 유지하고 있다면, S/4HANA 전환 시점에 큰 리팩터링 부담을 떠안게 됩니다. SAP는 S/4HANA 이후 Virtual Data Model(VDM)을 표준으로 제시하고 있으며, 공급사 마스터의 대표 뷰가 바로 I_Supplier CDS View입니다. 이 글에서는 LFA1 직접 조회의 위험을 정리하고, I_Supplier로 안전하게 마이그레이션하는 실전 코드 3단계를 다룹니다.

  • LFA1 직접 SELECT가 왜 위험한지 이해하기
  • I_Supplier CDS View의 구조와 필드 매핑 파악하기
  • ABAP 코드에서 I_Supplier를 활용해 공급사 정보 조회하기
  • 구매오더(EKKO)와 JOIN하여 실무 시나리오 구현하기
  • 성능·에러 처리·인증 정보를 반영한 프로덕션 코드 작성하기

사전 이해가 필요한 개념

이 글은 ABAP OpenSQL의 SELECT ... FROM ... INTO TABLE 문법과 CDS View의 개념(DEFINE VIEW ENTITY, Annotation, Association)을 최소한으로 알고 있다고 가정합니다. 또한 SAP ERP의 공급사 마스터 테이블인 LFA1, LFB1, LFM1의 존재를 알고 있으면 매핑 이해가 훨씬 쉽습니다. ADT(Eclipse ABAP Development Tools) 사용 경험이 있으면 CDS View 정의 확인이 편리합니다.

환경 및 준비물

본 예제는 다음 환경에서 검증되었습니다. 릴리스별로 뷰 필드가 조금씩 다를 수 있으므로, ADT의 Data Preview로 실제 필드를 확인해 주세요.

  • SAP S/4HANA 2022 FPS02 이상 (On-Premise) 또는 SAP S/4HANA Cloud Public Edition 2402 이상
  • ABAP Platform 7.58 이상 (Cloud Development 모델은 ABAP Cloud 권장)
  • ADT(Eclipse ABAP Development Tools) 3.36 이상
  • 권한 오브젝트 S_DEVELOP, 공급사 데이터 조회용 M_LFM1_EKO
  • 참고 패키지: APPL_MM_PUR_VDM_BE, APPL_MDG_BS_BP_VDM
ABAP Cloud(RAP, Steampunk) 환경에서는 LFA1 직접 접근이 아예 차단되어 있으므로, I_Supplier 등 릴리스된 CDS View만이 유일한 진입점입니다.

핵심 개념: LFA1 직접 조회의 문제와 VDM

고전적인 ABAP 코드는 공급사 이름·주소를 조회하기 위해 LFA1을 직접 SELECT합니다. 언뜻 간단해 보이지만, 다음과 같은 구조적 문제가 있습니다.

  • 비즈니스 시맨틱 부재: LFA1은 물리 테이블일 뿐, 어떤 필드가 활성 공급사인지, Central Block 여부는 어디를 봐야 하는지 코드에 하드코딩됩니다.
  • Business Partner(BP) 통합 이후 데이터 정합성 이슈: S/4HANA는 공급사를 Business Partner로 통합했습니다. LFA1은 여전히 존재하지만, 최신 필드(예: 국제 주소)는 BP 테이블에 있습니다.
  • Deprecation 위험: SAP는 릴리스별 노트에서 "핵심 마스터는 VDM을 사용하라"고 권장합니다. LFA1 구조는 유지되지만, 신규 필드는 CDS View로만 제공되는 경우가 늘고 있습니다.
  • 테스트·재사용성: CDS View는 SADL/OData/RAP과 자연스럽게 연동되지만, 원시 테이블은 그렇지 않습니다.

VDM은 3계층 구조로 구성됩니다. 도식으로 표현하면 다음과 같습니다.

[Interface Layer]   I_Supplier         <- 재사용 가능한 표준 뷰 (개발자가 SELECT)
       |
[Basic Layer]       I_SupplierBasic    <- 순수 데이터 조합
       |
[Physical Table]    LFA1 / BUT000 ...  <- 물리 테이블 (직접 접근 금지)

개발자는 최상위 Consumption 뷰이거나 Interface 뷰인 I_Supplier를 사용하면, 하부 물리 구조가 바뀌어도 코드가 깨지지 않습니다. 이는 마치 REST API와 DB 스키마의 관계와 유사합니다. DB 스키마를 직접 건드리지 말고, API 계층을 통해 접근하라는 원칙과 같습니다.

I_Supplier의 주요 필드는 Supplier(공급사 코드), SupplierName, SupplierAccountGroup, Country, PostalCode, CityName, TaxNumber1, IsBlockedForPosting, IsMarkedForDeletion 등이며, LFA1과의 매핑은 아래 표를 참고하세요.

LFA1 컬럼I_Supplier 필드비고
LIFNRSupplier공급사 코드 (Key)
NAME1SupplierNameBusiness Partner 이름과 연동
LAND1CountryISO 국가코드
ORT01CityName주소 뷰 통합 반영
PSTLZPostalCode-
STCD1TaxNumber1-
SPERRIsBlockedForPostingBoolean 형태로 변환
LOEVMIsMarkedForDeletionBoolean 형태로 변환
KTOKKSupplierAccountGroup-

실전 코드 1단계: 기본 조회

가장 단순한 시나리오부터 시작합니다. 특정 공급사 코드로 이름과 국가를 조회합니다. 아래는 리팩터링 이전 코드입니다.

" 안티 패턴: LFA1 직접 접근
DATA: lv_name  TYPE lfa1-name1,
      lv_land  TYPE lfa1-land1.

SELECT SINGLE name1 land1
  FROM lfa1
  INTO ( lv_name, lv_land )
 WHERE lifnr = '0000100234'.

이 코드를 I_Supplier 기반으로 바꾸면 다음과 같습니다.

" 권장 패턴: I_Supplier CDS View 사용
DATA ls_supplier TYPE i_supplier.

SELECT SINGLE
       Supplier,
       SupplierName,
       Country,
       CityName,
       IsBlockedForPosting
  FROM I_Supplier
  WHERE Supplier = @( CONV bu_partner( '0000100234' ) )
  INTO @ls_supplier.

IF sy-subrc = 0.
  WRITE: / ls_supplier-SupplierName,
           ls_supplier-Country,
           ls_supplier-CityName.
ENDIF.

변경 포인트는 세 가지입니다. 첫째, FROM 절이 물리 테이블이 아닌 CDS 엔티티입니다. 둘째, 필드명이 비즈니스 시맨틱을 따릅니다. 셋째, 필터에 인라인 선언(@( ... ))과 이스케이프(@)를 사용해 최신 OpenSQL 스타일을 반영합니다.

실전 코드 2단계: 구매오더 JOIN 및 에러 로깅

실무에서는 구매오더 헤더(I_PurchaseOrder)와 공급사 정보를 함께 가져오는 경우가 많습니다. 예를 들어, 특정 회사코드의 지난 30일간 구매오더를 공급사 이름과 함께 리포트하는 시나리오를 가정합니다.

CLASS zcl_po_supplier_report DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_po_line,
             purchase_order TYPE ekko-ebeln,
             supplier_id    TYPE bu_partner,
             supplier_name  TYPE string,
             supplier_city  TYPE string,
             order_date     TYPE d,
           END OF ty_po_line,
           tt_po_lines TYPE STANDARD TABLE OF ty_po_line WITH EMPTY KEY.

    METHODS get_recent_pos
      IMPORTING iv_company_code TYPE bukrs
                iv_from_date    TYPE d
      RETURNING VALUE(rt_lines) TYPE tt_po_lines
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_po_supplier_report IMPLEMENTATION.
  METHOD get_recent_pos.
    TRY.
        SELECT po~PurchaseOrder            AS purchase_order,
               po~Supplier                 AS supplier_id,
               sup~SupplierName            AS supplier_name,
               sup~CityName                AS supplier_city,
               po~PurchaseOrderDate        AS order_date
          FROM I_PurchaseOrder AS po
          INNER JOIN I_Supplier AS sup
            ON  sup~Supplier = po~Supplier
          WHERE po~CompanyCode        = @iv_company_code
            AND po~PurchaseOrderDate  >= @iv_from_date
            AND sup~IsMarkedForDeletion = @abap_false
          INTO TABLE @rt_lines.

      CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
        " 로깅 - 실제 프로젝트는 Application Log(BAL)로 연동
        MESSAGE lx_sql->get_text( ) TYPE 'I'.
        RAISE EXCEPTION lx_sql.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

여기서 눈여겨볼 점은 IsMarkedForDeletion으로 삭제 표시된 공급사를 자연스럽게 걸러낼 수 있다는 점입니다. LFA1로 하려면 LOEVM <> 'X'처럼 도메인 값을 직접 알아야 하지만, CDS는 Boolean 시맨틱으로 노출합니다.

실전 코드 3단계: 프로덕션 품질(성능, 페이징, 권한, 테스트)

대량 데이터·권한·유닛테스트를 고려한 프로덕션 형태입니다. 페이징에 UP TO N ROWS + OFFSET을, 권한에는 WITH PRIVILEGED ACCESS가 아닌 명시적 체크를 사용합니다.

CLASS zcl_supplier_finder DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_result,
             supplier      TYPE bu_partner,
             supplier_name TYPE string,
             country       TYPE land1,
             city          TYPE string,
             tax_number    TYPE stcd1,
           END OF ty_result,
           tt_result TYPE STANDARD TABLE OF ty_result WITH EMPTY KEY.

    METHODS search
      IMPORTING iv_country      TYPE land1
                iv_name_pattern TYPE string DEFAULT '%'
                iv_page_size    TYPE i     DEFAULT 50
                iv_page_index   TYPE i     DEFAULT 0
      RETURNING VALUE(rt_data)  TYPE tt_result
      RAISING   cx_abap_invalid_value.
ENDCLASS.

CLASS zcl_supplier_finder IMPLEMENTATION.
  METHOD search.

    " 1) 입력 검증
    IF iv_page_size <= 0 OR iv_page_size > 500.
      RAISE EXCEPTION TYPE cx_abap_invalid_value.
    ENDIF.

    DATA(lv_offset) = iv_page_size * iv_page_index.

    " 2) 권한 체크 - 국가별 공급사 조회 권한 예시
    AUTHORITY-CHECK OBJECT 'F_LFA1_BUK'
      ID 'BUKRS' DUMMY
      ID 'ACTVT' FIELD '03'.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE cx_abap_invalid_value.
    ENDIF.

    " 3) I_Supplier + 페이징 + 정렬
    SELECT Supplier,
           SupplierName,
           Country,
           CityName,
           TaxNumber1
      FROM I_Supplier
      WHERE Country              = @iv_country
        AND SupplierName        LIKE @iv_name_pattern
        AND IsBlockedForPosting  = @abap_false
        AND IsMarkedForDeletion  = @abap_false
      ORDER BY SupplierName
      INTO CORRESPONDING FIELDS OF TABLE @rt_data
      UP TO @iv_page_size ROWS
      OFFSET @lv_offset.

  ENDMETHOD.
ENDCLASS.

ABAP Unit 테스트도 함께 작성하면, 향후 CDS View 필드 변경이 있을 때 회귀 테스트로 안전망이 됩니다.

CLASS ltc_supplier_finder DEFINITION FINAL FOR TESTING
  DURATION SHORT RISK LEVEL HARMLESS.
  PRIVATE SECTION.
    METHODS test_pagination FOR TESTING.
ENDCLASS.

CLASS ltc_supplier_finder IMPLEMENTATION.
  METHOD test_pagination.
    DATA(lo_finder) = NEW zcl_supplier_finder( ).
    DATA(lt_page1) = lo_finder->search(
      iv_country    = 'DE'
      iv_page_size  = 10
      iv_page_index = 0 ).
    cl_abap_unit_assert=>assert_true(
      act = xsdbool( lines( lt_page1 ) <= 10 ) ).
  ENDMETHOD.
ENDCLASS.

흔한 실수와 트러블슈팅

실제 프로젝트에서 반복적으로 마주치는 문제와 해결책을 정리합니다.

  • Q1. SELECT SINGLE ... FROM I_Supplier WHERE Supplier = '100234'가 값을 찾지 못합니다.
    Supplier 필드는 BU_PARTNER 타입으로 10자리 패딩된 문자열입니다. Literal을 그대로 넣으면 매칭이 실패할 수 있으니, CONV bu_partner( '100234' )로 변환하거나, ALPHA conversion routine을 명시적으로 태우세요.
  • Q2. ABAP Cloud에서 SELECT ... FROM lfa1이 컴파일 에러를 냅니다.
    정상 동작입니다. ABAP Cloud는 릴리스된 CDS View만 허용합니다. I_Supplier, I_SupplierPurchasingOrg, I_SupplierCompany 중 시나리오에 맞는 뷰로 대체하세요.
  • Q3. LFA1에 있던 사용자 정의 컬럼(CI include)이 I_Supplier에 안 보입니다.
    표준 릴리스 뷰는 커스텀 필드를 자동으로 노출하지 않습니다. Key User Extensibility로 필드를 확장하거나, Z_Supplier_Ext 형태의 커스텀 CDS를 만들어 I_Supplier와 Association으로 연결하는 방식이 권장됩니다.
  • Q4. JOIN 성능이 예상보다 느립니다.
    CDS View는 내부적으로 여러 물리 테이블을 조인하므로, WHERE 절에 Key 필드(Supplier, CompanyCode 등)를 반드시 포함시켜 조기 필터링을 유도하세요. ST05 SQL Trace로 실행 계획을 확인하는 습관이 중요합니다.
  • Q5. SupplierName이 예상과 다른 값을 반환합니다.
    BP 통합 이후 공급사 이름의 실제 저장 위치는 BUT000입니다. LFA1-NAME1을 갱신해도 BP 이름과 다를 수 있으니, 항상 I_Supplier가 반환하는 값을 신뢰하는 것이 원칙입니다.

이어서 살펴볼 만한 주제

I_Supplier에 익숙해졌다면 다음 주제로 확장해 보세요. 공급사와 조직 데이터를 결합한 I_SupplierPurchasingOrg, 회사코드 단위 정보는 I_SupplierCompany, 은행 정보는 I_SupplierBankDetails로 세분화되어 있습니다. RAP(RESTful ABAP Programming Model)에서 이 뷰들을 Projection View로 감싸 Fiori 앱을 만드는 실습도 좋은 다음 걸음입니다. 또한 커스텀 필드가 필요한 경우 ABAP Cloud ExtensibilityCustom CDS View 개발 흐름을 학습하면 실무 활용 폭이 크게 넓어집니다.

더 읽어볼 자료

댓글 0

아직 댓글이 없습니다.