📖 개요 및 핵심 체크포인트
ECC 시절에 만든 Classic BAdI와 CMOD/SMOD 기반 확장 코드를 그대로 S/4HANA로 가져오면, 컴파일은 통과해도 런타임에서 조용히 무너지는 경우가 많습니다. S/4HANA(예: S/4HANA 2023 On-Premise)에서는 커널 기반 New BAdI와 Enhancement Spot 구조가 표준이며, 일부 Classic BAdI 정의는 심플리피케이션 과정에서 제거되거나 호출 지점 자체가 사라졌습니다. 이 글은 구버전 BAdI를 신규 구조로 옮기는 과정을 실무 시나리오(판매 오더 검증 로직)로 단계별로 다룹니다.
- Classic BAdI와 New BAdI의 런타임 구조 차이를 설명할 수 있다
- Enhancement Spot을 직접 정의하고 필터 BAdI를 구현할 수 있다
- CL_EXITHANDLER 호출 코드를 GET BADI / CALL BADI로 전환할 수 있다
- ATC 기반 커스텀 코드 점검으로 마이그레이션 대상을 식별할 수 있다
📚 사전에 갖추면 좋은 배경
ABAP Objects(인터페이스, 클래스), SE18/SE19 트랜잭션 사용 경험, 그리고 ECC에서 Enhancement(User Exit, BAdI, Implicit Enhancement)를 한 번이라도 구현해 본 경험이 있다면 수월합니다. ATC(ABAP Test Cockpit)와 Simplification Item 개념을 알고 있으면 마이그레이션 대상 선별 단계를 빠르게 이해할 수 있습니다.
🔧 환경 · 버전 · 준비물
이 글의 예제는 다음 환경을 기준으로 작성했습니다.
- SAP S/4HANA 2023 On-Premise (ABAP Platform 2023) — New BAdI 문법은 NetWeaver 7.0 이후 공통이므로 S/4HANA 1909 이상에서도 일반적으로 동일하게 동작합니다
- 개발 도구: ADT(Eclipse 기반 ABAP Development Tools) 권장, SE18/SE19/SE20도 병행 사용
- 점검 도구: ATC + 체크 배리언트
S4HANA_READINESS, 참조 시스템의 Simplification Database(SYCM으로 다운로드) - 권한: 개발 키, Enhancement Spot 생성 권한, 커스텀 패키지(예:
ZSD_ENH)
클라우드 에디션(SAP S/4HANA Cloud Public Edition)은 Key User 확장/개발자 확장(Embedded Steampunk) 모델을 쓰므로, 이 글의 On-Premise 절차와는 구분해서 접근해야 합니다.
💡 핵심 개념 — Classic BAdI와 New BAdI는 다른 엔진이다
두 방식의 차이를 비유하면, Classic BAdI는 전화 교환원을 거치는 구조입니다. 호출할 때마다 CL_EXITHANDLER라는 교환원이 구현 클래스를 데이터베이스 테이블에서 조회해 연결해 줍니다. 반면 New BAdI는 단축 다이얼입니다. GET BADI가 커널 레벨에서 처리되어 구현 정보가 로드 시점에 최적화되고, 호출 오버헤드가 크게 줄어듭니다.
구조적으로 정리하면 다음과 같습니다.
| 구분 | Classic BAdI | New BAdI (Enhancement Spot) |
|---|---|---|
| 런타임 | ABAP 레벨 (CL_EXITHANDLER) | 커널 통합 (GET BADI / CALL BADI) |
| 컨테이너 | BAdI 정의(SE18 단독) | Enhancement Spot 안의 BAdI 정의 |
| 다중 구현 | 제한적, 정렬 어려움 | 다중 구현 + 필터 + Fallback 클래스 |
| 호출 시 미구현 | 인스턴스 NULL 체크 | CX_BADI_NOT_IMPLEMENTED 예외 또는 Fallback |
| 전환 관리 | Enhancement Framework 밖 | Switch Framework 연동 가능 |
S/4HANA에서 문제가 되는 이유는 세 가지입니다. 첫째, 심플리피케이션으로 호출 지점 자체가 제거된 Classic BAdI가 존재합니다(예: 구 트랜잭션이 Fiori 앱으로 대체되면서 해당 Exit이 더 이상 실행되지 않는 경우). 둘째, 데이터 모델 변경(KONV → PRCD_ELEMENTS, VBUK/VBUP 필드의 VBAK/VBAP 통합 등)으로 BAdI 시그니처가 참조하던 구조가 바뀌었습니다. 셋째, Implicit Enhancement로 표준 코드 중간에 끼워 넣은 로직은 업그레이드 때마다 SPAU_ENH 조정 대상이 되어 유지비용이 누적됩니다. 따라서 "돌아가니까 그대로 두자"는 판단은 다음 업그레이드에서 장애로 돌아올 가능성이 높습니다.
💻 실전 코드 — 판매 오더 검증 BAdI를 3단계로 전환하기
시나리오: ECC에서 판매 오더 저장 전 여신·납기 검증을 수행하던 Classic BAdI ZBADI_SO_VALIDATE를 S/4HANA의 Enhancement Spot 기반 New BAdI로 이관합니다.
1단계 — 기존 Classic BAdI 호출 구조 확인
ECC 시절 호출 코드는 일반적으로 아래 형태입니다.
" [AS-IS] Classic BAdI 호출 (ECC)
DATA: lo_validator TYPE REF TO if_ex_zbadi_so_validate.
CALL METHOD cl_exithandler=>get_instance
CHANGING
instance = lo_validator.
IF lo_validator IS BOUND.
CALL METHOD lo_validator->validate_before_save
EXPORTING
is_order_head = ls_vbak
IMPORTING
ev_blocked = lv_blocked
ev_reason = lv_reason.
ENDIF.
먼저 ATC로 이 코드가 S/4HANA에서 유효한지 점검합니다. 체크 배리언트 S4HANA_READINESS를 실행하면 제거된 BAdI 정의, 변경된 데이터 요소 참조가 Simplification Item 번호와 함께 보고됩니다.
2단계 — Enhancement Spot과 New BAdI 정의 (에러 처리·로깅 포함)
SE20(또는 ADT)에서 Enhancement Spot ZES_SD_ORDER를 만들고 그 안에 BAdI 정의 ZBD_SO_VALIDATE를 생성합니다. 인터페이스는 반드시 IF_BADI_INTERFACE를 포함해야 합니다.
" [TO-BE] New BAdI 인터페이스 정의
INTERFACE zif_so_validate_badi PUBLIC.
INTERFACES if_badi_interface.
METHODS validate_before_save
IMPORTING
is_order_head TYPE vbak
EXPORTING
ev_blocked TYPE abap_bool
ev_reason TYPE bapi_msg
RAISING
zcx_so_validation.
ENDINTERFACE.
호출부는 필터(판매 조직)와 예외 처리, 로깅을 갖춘 형태로 전환합니다.
" [TO-BE] New BAdI 호출 — 필터 + 예외 + 로깅
DATA lo_badi TYPE REF TO zbd_so_validate.
TRY.
GET BADI lo_badi
FILTERS
sales_org = ls_vbak-vkorg.
CALL BADI lo_badi->validate_before_save
EXPORTING
is_order_head = ls_vbak
IMPORTING
ev_blocked = lv_blocked
ev_reason = lv_reason.
CATCH cx_badi_not_implemented.
" 구현이 없으면 검증 통과로 간주 (Fallback 클래스 대안 가능)
lv_blocked = abap_false.
CATCH zcx_so_validation INTO DATA(lx_val).
" Application Log(BAL) 기록 후 차단 처리
DATA(lo_log) = cl_bali_log=>create_with_header(
header = cl_bali_header_setter=>create(
object = 'ZSD'
subobject = 'SO_CHECK' ) ).
lo_log->add_item( cl_bali_exception_setter=>create(
severity = if_bali_constants=>c_severity_error
exception = lx_val ) ).
cl_bali_log_db=>get_instance( )->save_log( log = lo_log ).
lv_blocked = abap_true.
ENDTRY.
필터를 판매 조직으로 걸어두면 조직별로 다른 검증 구현을 SE19에서 독립 배포할 수 있어, 기존 Classic BAdI에서 IF 분기로 처리하던 로직이 구조적으로 분리됩니다.
3단계 — 프로덕션 전환: 병행 운영 디스패처와 단위 테스트
운영 시스템에서 한 번에 교체하는 대신, 전환 스위치를 둔 디스패처로 구·신 로직을 병행 검증하는 방식이 일반적으로 안전합니다.
CLASS zcl_so_validate_dispatch DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS run
IMPORTING is_order_head TYPE vbak
EXPORTING ev_blocked TYPE abap_bool
ev_reason TYPE bapi_msg.
ENDCLASS.
CLASS zcl_so_validate_dispatch IMPLEMENTATION.
METHOD run.
" TVARVC 스위치로 신규 BAdI 경로 점진 활성화
SELECT SINGLE low FROM tvarvc
INTO @DATA(lv_mode)
WHERE name = 'ZSD_SO_BADI_MODE' AND type = 'P'.
IF lv_mode = 'NEW'.
DATA lo_badi TYPE REF TO zbd_so_validate.
TRY.
GET BADI lo_badi FILTERS sales_org = is_order_head-vkorg.
CALL BADI lo_badi->validate_before_save
EXPORTING is_order_head = is_order_head
IMPORTING ev_blocked = ev_blocked
ev_reason = ev_reason.
CATCH cx_badi_not_implemented.
ev_blocked = abap_false.
ENDTRY.
ELSE.
" 전환 완료 시 제거 예정인 레거시 경로
NEW zcl_so_validate_legacy( )->check(
EXPORTING is_head = is_order_head
IMPORTING ev_blocked = ev_blocked ev_reason = ev_reason ).
ENDIF.
ENDMETHOD.
ENDCLASS.
단위 테스트는 BAdI 구현 클래스를 직접 인스턴스화해 검증합니다. 인터페이스가 명확한 New BAdI 구조의 장점이 여기서 드러납니다.
CLASS ltc_so_validate DEFINITION FINAL FOR TESTING
RISK LEVEL HARMLESS DURATION SHORT.
PRIVATE SECTION.
METHODS blocked_when_credit_over FOR TESTING.
ENDCLASS.
CLASS ltc_so_validate IMPLEMENTATION.
METHOD blocked_when_credit_over.
DATA(lo_cut) = NEW zcl_so_validate_impl_1000( ).
lo_cut->zif_so_validate_badi~validate_before_save(
EXPORTING is_order_head = VALUE #( vkorg = '1000' netwr = '9999999' )
IMPORTING ev_blocked = DATA(lv_blocked) ).
cl_abap_unit_assert=>assert_equals( act = lv_blocked exp = abap_true ).
ENDMETHOD.
ENDCLASS.
보안 측면에서는 BAdI 구현 내부에서 권한 체크(AUTHORITY-CHECK)를 생략하지 않도록 하고, TVARVC 스위치 변경 권한을 운영 Basis로 제한하는 것이 좋습니다.
⚠️ 흔한 실수와 트러블슈팅
- Q1. GET BADI에서
CX_BADI_MULTIPLY_IMPLEMENTED가 발생합니다.
BAdI 정의를 단일 구현(Single Use)으로 만들었는데 SE19에 구현이 두 개 이상 활성화된 경우입니다. 정의의 다중 구현 허용 여부를 확인하거나, 필터 값이 겹치지 않게 구현별 필터 조건을 조정하세요. - Q2. 마이그레이션 후 BAdI가 아예 호출되지 않습니다.
S/4HANA에서 해당 트랜잭션이 Fiori 앱으로 대체되어 구 호출 지점이 실행되지 않는 경우가 대표적입니다. Simplification Item을 확인하고, 신규 앱이 제공하는 대체 BAdI(예: SD 영역의 릴리스별 표준 Enhancement Spot)를 찾아 로직을 옮겨야 합니다. 코드 문제가 아니라 호출 지점 소멸 문제일 수 있습니다. - Q3. Classic BAdI 인터페이스를 New BAdI에 재사용해도 되나요?
기술적으로IF_BADI_INTERFACE를 추가하면 되는 경우도 있지만, 시그니처가 구 데이터 모델(예: VBUK 상태 필드)을 참조한다면 재설계가 권장됩니다. KONV 참조는 PRCD_ELEMENTS 기반으로 바꿔야 합니다. - Q4. Implicit Enhancement가 수십 개인데 전부 BAdI로 바꿔야 하나요?
전부는 아닙니다. 다만 표준 코드 내부 로직에 의존하는 Implicit Enhancement는 업그레이드마다 SPAU_ENH 조정이 필요하므로, 표준이 제공하는 명시적 Enhancement Spot이 있다면 그쪽으로 옮기는 것이 유지보수 비용을 줄입니다.
🚀 이후 확장 방향
New BAdI 전환이 끝났다면, 다음 주제로 확장해 보세요. RAP(ABAP RESTful Application Programming Model)의 Behavior 확장과 Fiori 앱의 표준 Enhancement Spot 활용, Clean Core 원칙에 따른 릴리스드 API 기반 확장 전략, 그리고 SAP S/4HANA Cloud의 개발자 확장(Embedded Steampunk)과 On-Premise Enhancement Framework의 관계를 이해하면 업그레이드 안정성이 한 단계 올라갑니다. ATC를 CI 파이프라인(gCTS/abapGit)에 연결해 커스텀 코드 품질을 상시 점검하는 체계도 함께 검토할 만합니다.
📚 더 읽어볼 문서
- SAP Help Portal — Enhancement Framework 개요 (ABAP Platform)
- SAP Help Portal — ABAP 키워드 문서: GET BADI / CALL BADI
- SAP Help Portal — SAP S/4HANA On-Premise 제품 문서 및 Simplification 정보
- SAP Help Portal — ABAP Test Cockpit(ATC)와 커스텀 코드 점검
- SAP Community — ABAP 확장/마이그레이션 관련 블로그 및 Q&A
- SAP Note 2190420 — SAP S/4HANA 커스텀 코드 점검 관련 노트(SAP for Me 로그인 필요)
댓글 0
아직 댓글이 없습니다.