ABAP Code Patterns
Reference for ABAP development in ZROUTER_DISPATCH and related SAP projects.
Validated against Clean ABAP guidelines and abaplint rules.
Prerequisites
- SAP NetWeaver 7.40+ (inline declarations, VALUE, COND, FOR)
- abaplint configured (config in
package.json abaplint key, or root abaplint.json)
- Access to SE80/ADT for object creation
1. BAPI/RFC Pattern
" BAPI call with proper commit/rollback
CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
EXPORTING headdata = ls_header
IMPORTING return = ls_ret.
IF ls_ret-type = 'E' OR ls_ret-type = 'A'.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
RAISE EXCEPTION TYPE cx_zrouter EXPORTING mv_text = ls_ret-message.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = 'X'.
ENDIF.
2. GENERATE SUBROUTINE POOL (Dynamic Eval)
" Compile ABAP at runtime — lighter than INSERT REPORT + SUBMIT
DATA: lt_source TYPE TABLE OF string,
lv_pool TYPE string.
APPEND |PROGRAM.| TO lt_source.
APPEND |FORM eval CHANGING cv_result TYPE string.| TO lt_source.
APPEND LINES OF lt_lines TO lt_source.
APPEND |ENDFORM.| TO lt_source.
GENERATE SUBROUTINE POOL lt_source NAME lv_pool
MESSAGE DATA(lv_msg) LINE DATA(lv_line) WORD DATA(lv_word).
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_zrouter
EXPORTING mv_text = |Syntax error line { lv_line - 2 }: { lv_msg }|.
ENDIF.
PERFORM ('EVAL') IN PROGRAM (lv_pool) IF FOUND CHANGING cv_result.
3. Clean ABAP Quick Rules
- Field lists:
SELECT matnr, maktx FROM mara — never SELECT *
- Inline declarations:
DATA(lv_matnr) = 'MAT001'
- NEW operator:
mo_log = NEW zcl_logger( ) — not CREATE OBJECT
- VALUE:
lt_data = VALUE #( ( a = 1 ) ( a = 2 ) )
- COND/SWITCH:
lv_st = COND #( WHEN sy-subrc = 0 THEN 'OK' ELSE 'ERR' )
- Field symbols:
LOOP AT lt_tab ASSIGNING FIELD-SYMBOL(<ls>)
- No COMMIT WORK: Use
BAPI_TRANSACTION_COMMIT with wait = 'X'
- Exceptions: Inherit
CX_STATIC_CHECK, never CX_ROOT
- JSON: Use
/UI2/CL_JSON — never manual PCRE parsing
- BAL logging:
BAL_LOG_CREATE → BAL_LOG_MSG_ADD → BAL_DB_SAVE
4. ABAP OO Pattern — Abstract Handler + Factory
CLASS zcl_handler_abstract DEFINITION PUBLIC ABSTRACT.
PUBLIC SECTION.
INTERFACES zif_handler.
PROTECTED SECTION.
METHODS handle_action ABSTRACT.
ENDCLASS.
CLASS zcl_handler_mm DEFINITION PUBLIC FINAL
INHERITING FROM zcl_handler_abstract.
PROTECTED SECTION.
METHODS handle_action REDEFINITION.
ENDCLASS.
" Factory dispatch
CASE to_upper( iv_module ).
WHEN 'MM'. ro_handler = NEW zcl_handler_mm( ).
WHEN 'SD'. ro_handler = NEW zcl_handler_sd( ).
ENDCASE.
5. DDIC Table Design — Header + Item
- Header (
ZROUTER_TMPL_HD): MANDT (key), TEMPLATE_ID (key), MODULE,
ACTION, VERSION, TITLE, CREATED_BY, CREATED_AT, ACTIVE_FLAG
- Items (
ZROUTER_TMPL_CD): MANDT (key), TEMPLATE_ID (key),
LINE_NUM (key), CODE_LINE
- Use
CHAR1 for flags (X = true), NUMC4 for version, TIMESTAMPL for UTC
6. INSERT REPORT + SUBMIT (Full Dynamic)
" Heavier — use only when you need WRITE output capture
INSERT REPORT lv_report FROM lt_source.
SUBMIT (lv_report) VIA SELECTION-SCREEN AND RETURN.
" Capture via LIST_FROM_MEMORY + LIST_TO_ASCI
Pitfalls
- GENERATE SUBROUTINE POOL line offset:
- Cause: Error line numbers start at FORM, not PROGRAM.
- Solution: Subtract 2 to map back to user expression line.
- BAPI_TRANSACTION_COMMIT without WAIT:
- Cause: Update task runs async; next BAPI may read stale data.
- Solution: Always pass
wait = 'X'.
- RFC_READ_TABLE 512-char truncation:
- Cause: Field values silently truncated beyond 512 chars.
- Solution: Use ADT SQL or HANA query for long text fields.
- sy-subrc after BAPI call:
- Cause: Some BAPIs return
sy-subrc = 0 with BAPIRET2-TYPE = 'E'.
- Solution: Always check
BAPIRET2-TYPE, not sy-subrc.
Verification
# Lint the ABAP source
npx abaplint templates/**/*.abap
# Verify BAPI commit pattern present
grep -rn "BAPI_TRANSACTION_COMMIT" src/ | grep -c "wait.*X"
# Verify no COMMIT WORK in source
grep -rn "COMMIT WORK" src/ && echo "FAIL: found COMMIT WORK" || echo "OK"
1---2name: abap-code-patterns3description: ABAP development patterns and best practices — BAPI/RFC, dynamic programming, Clean ABAP, DDIC design, exception handling, BAL logging, ABAP OO. Use when writing, reviewing, or refactoring ABAP code; implementing ZROUTER handlers.4---56# ABAP Code Patterns78Reference for ABAP development in ZROUTER_DISPATCH and related SAP projects.9Validated against Clean ABAP guidelines and abaplint rules.1011## Prerequisites1213- SAP NetWeaver 7.40+ (inline declarations, VALUE, COND, FOR)14- abaplint configured (config in `package.json` `abaplint` key, or root `abaplint.json`)15- Access to SE80/ADT for object creation1617## 1. BAPI/RFC Pattern1819```abap20" BAPI call with proper commit/rollback21CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'22 EXPORTING headdata = ls_header23 IMPORTING return = ls_ret.2425IF ls_ret-type = 'E' OR ls_ret-type = 'A'.26 CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.27 RAISE EXCEPTION TYPE cx_zrouter EXPORTING mv_text = ls_ret-message.28ELSE.29 CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = 'X'.30ENDIF.31```3233## 2. GENERATE SUBROUTINE POOL (Dynamic Eval)3435```abap36" Compile ABAP at runtime — lighter than INSERT REPORT + SUBMIT37DATA: lt_source TYPE TABLE OF string,38 lv_pool TYPE string.3940APPEND |PROGRAM.| TO lt_source.41APPEND |FORM eval CHANGING cv_result TYPE string.| TO lt_source.42APPEND LINES OF lt_lines TO lt_source.43APPEND |ENDFORM.| TO lt_source.4445GENERATE SUBROUTINE POOL lt_source NAME lv_pool46 MESSAGE DATA(lv_msg) LINE DATA(lv_line) WORD DATA(lv_word).4748IF sy-subrc <> 0.49 RAISE EXCEPTION TYPE cx_zrouter50 EXPORTING mv_text = |Syntax error line { lv_line - 2 }: { lv_msg }|.51ENDIF.5253PERFORM ('EVAL') IN PROGRAM (lv_pool) IF FOUND CHANGING cv_result.54```5556## 3. Clean ABAP Quick Rules57581. **Field lists**: `SELECT matnr, maktx FROM mara` — never `SELECT *`592. **Inline declarations**: `DATA(lv_matnr) = 'MAT001'`603. **NEW operator**: `mo_log = NEW zcl_logger( )` — not `CREATE OBJECT`614. **VALUE**: `lt_data = VALUE #( ( a = 1 ) ( a = 2 ) )`625. **COND/SWITCH**: `lv_st = COND #( WHEN sy-subrc = 0 THEN 'OK' ELSE 'ERR' )`636. **Field symbols**: `LOOP AT lt_tab ASSIGNING FIELD-SYMBOL(<ls>)`647. **No COMMIT WORK**: Use `BAPI_TRANSACTION_COMMIT` with `wait = 'X'`658. **Exceptions**: Inherit `CX_STATIC_CHECK`, never `CX_ROOT`669. **JSON**: Use `/UI2/CL_JSON` — never manual PCRE parsing6710. **BAL logging**: `BAL_LOG_CREATE` → `BAL_LOG_MSG_ADD` → `BAL_DB_SAVE`6869## 4. ABAP OO Pattern — Abstract Handler + Factory7071```abap72CLASS zcl_handler_abstract DEFINITION PUBLIC ABSTRACT.73 PUBLIC SECTION.74 INTERFACES zif_handler.75 PROTECTED SECTION.76 METHODS handle_action ABSTRACT.77ENDCLASS.7879CLASS zcl_handler_mm DEFINITION PUBLIC FINAL80 INHERITING FROM zcl_handler_abstract.81 PROTECTED SECTION.82 METHODS handle_action REDEFINITION.83ENDCLASS.8485" Factory dispatch86CASE to_upper( iv_module ).87 WHEN 'MM'. ro_handler = NEW zcl_handler_mm( ).88 WHEN 'SD'. ro_handler = NEW zcl_handler_sd( ).89ENDCASE.90```9192## 5. DDIC Table Design — Header + Item9394- **Header** (`ZROUTER_TMPL_HD`): `MANDT` (key), `TEMPLATE_ID` (key), `MODULE`,95 `ACTION`, `VERSION`, `TITLE`, `CREATED_BY`, `CREATED_AT`, `ACTIVE_FLAG`96- **Items** (`ZROUTER_TMPL_CD`): `MANDT` (key), `TEMPLATE_ID` (key),97 `LINE_NUM` (key), `CODE_LINE`98- Use `CHAR1` for flags (`X` = true), `NUMC4` for version, `TIMESTAMPL` for UTC99100## 6. INSERT REPORT + SUBMIT (Full Dynamic)101102```abap103" Heavier — use only when you need WRITE output capture104INSERT REPORT lv_report FROM lt_source.105SUBMIT (lv_report) VIA SELECTION-SCREEN AND RETURN.106" Capture via LIST_FROM_MEMORY + LIST_TO_ASCI107```108109## Pitfalls110111- **GENERATE SUBROUTINE POOL line offset**:112 - Cause: Error line numbers start at FORM, not PROGRAM.113 - Solution: Subtract 2 to map back to user expression line.114- **BAPI_TRANSACTION_COMMIT without WAIT**:115 - Cause: Update task runs async; next BAPI may read stale data.116 - Solution: Always pass `wait = 'X'`.117- **RFC_READ_TABLE 512-char truncation**:118 - Cause: Field values silently truncated beyond 512 chars.119 - Solution: Use ADT SQL or HANA query for long text fields.120- **sy-subrc after BAPI call**:121 - Cause: Some BAPIs return `sy-subrc = 0` with `BAPIRET2-TYPE = 'E'`.122 - Solution: Always check `BAPIRET2-TYPE`, not `sy-subrc`.123124## Verification125126```bash127# Lint the ABAP source128npx abaplint templates/**/*.abap129130# Verify BAPI commit pattern present131grep -rn "BAPI_TRANSACTION_COMMIT" src/ | grep -c "wait.*X"132133# Verify no COMMIT WORK in source134grep -rn "COMMIT WORK" src/ && echo "FAIL: found COMMIT WORK" || echo "OK"135```