Technology Blog Posts by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
akmal1216
Active Participant
813

The Problem Every ABAP Developer Knows Too Well

You're running a year-end reconciliation report that loops through 80,000 vendor invoices, validates them against GL postings, and stamps each record with an approval status. In a test system with 500 entries it finishes in under a minute. In production it runs for four hours — and the business is calling.

The knee-jerk fix most developers reach for is raw asynchronous RFC (aRFC): sprinkle CALL FUNCTION … STARTING NEW TASK … DESTINATION IN GROUP, wire up callback handlers, manage semaphore flags, and pray that your RFC server group is configured correctly across all application servers. It works — eventually — but the resulting code is fragile, hard to test, and nearly impossible for the next developer to maintain without a war story attached.

There is a better way: CL_ABAP_PARALLEL.

What Is CL_ABAP_PARALLEL?

CL_ABAP_PARALLEL is a standard SAP class (available from SAP Basis 7.54 and fully released in ABAP Cloud) that wraps the complexity of aRFC-based parallel dispatch behind a clean, object-oriented interface. Instead of orchestrating raw function module calls, RFC groups, and callback patterns yourself, you define what each unit of work should do, hand the instances over to the framework, and let it handle the rest — process allocation, dispatching, timeout management, and result collection.

Under the hood, the framework still uses RS_ABAP_PARALLEL and the server dispatch class CL_SSI_DISPATCH, so the performance characteristics are identical to hand-rolled aRFC. The difference is in developer experience and maintainability.

Two Ways to Implement: Choose Your Pattern

SAP provides two distinct implementation strategies. Pick the one that fits your team's style and the complexity of your data contracts.

Pattern 1 — Interface Implementation (IF_ABAP_PARALLEL)

This is the recommended modern approach, particularly for ABAP Cloud and clean-core projects. Your processing class implements the IF_ABAP_PARALLEL interface and carries its own typed input data as instance attributes. No serialization gymnastics required.

"---------------------------------------------------------------------
" Step 1: Define the processing task class
"---------------------------------------------------------------------
CLASS zcl_invoice_validation_task DEFINITION
  PUBLIC FINAL CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_abap_parallel.
    INTERFACES if_serializable_object.

    TYPES: BEGIN OF ty_result,
             invoice_id  TYPE vbeln,
             is_valid    TYPE abap_bool,
             error_msg   TYPE string,
           END OF ty_result.

    METHODS constructor
      IMPORTING
        is_invoice TYPE rbkp.           " MIRO invoice header

    METHODS get_result
      RETURNING VALUE(rs_result) TYPE ty_result.

  PRIVATE SECTION.
    DATA ms_invoice TYPE rbkp.
    DATA ms_result  TYPE ty_result.
ENDCLASS.

CLASS zcl_invoice_validation_task IMPLEMENTATION.

  METHOD constructor.
    ms_invoice = is_invoice.
  ENDMETHOD.

  METHOD if_abap_parallel~do.
    " All validation logic lives here — runs in a separate dialog WP
    ms_result-invoice_id = ms_invoice-belnr.

    " Example: Cross-check the invoice amount against the PO tolerance
    DATA(lv_tolerance) = CONV dec15_5( '0.05' ).   " 5 % tolerance
    DATA lv_po_net TYPE wrbtr.

    SELECT SINGLE netwr INTO lv_po_net
      FROM ekko
      WHERE ebeln = ms_invoice-xblnr.  " vendor doc nr maps to PO

    IF lv_po_net IS NOT INITIAL.
      DATA(lv_deviation) = abs( ms_invoice-rwbtr - lv_po_net ) / lv_po_net.
      IF lv_deviation <= lv_tolerance.
        ms_result-is_valid = abap_true.
      ELSE.
        ms_result-is_valid  = abap_false.
        ms_result-error_msg = |Deviation { lv_deviation * 100 } % exceeds tolerance|.
      ENDIF.
    ELSE.
      ms_result-is_valid  = abap_false.
      ms_result-error_msg = 'No matching purchase order found'.
    ENDIF.
  ENDMETHOD.

  METHOD get_result.
    rs_result = ms_result.
  ENDMETHOD.

ENDCLASS.


"---------------------------------------------------------------------
" Step 2: Orchestrate the parallel run from your main program / method
"---------------------------------------------------------------------
CLASS zcl_invoice_processor DEFINITION
  PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    CLASS-METHODS run_validation.
ENDCLASS.

CLASS zcl_invoice_processor IMPLEMENTATION.

  METHOD run_validation.

    " Fetch the raw invoice headers to validate
    DATA lt_invoices TYPE TABLE OF rbkp.
    SELECT * FROM rbkp
      INTO TABLE @LT_invoices
      WHERE bukrs = '1000'
        AND bldat >= @( cl_abap_context_info=>get_system_date( ) - 30 ).

    " Build one task object per invoice
    DATA lt_tasks TYPE cl_abap_parallel=>t_in_inst_tab.
    LOOP AT lt_invoices ASSIGNING FIELD-SYMBOL(<invoice>).
      DATA(lo_task) = NEW zcl_invoice_validation_task( <invoice> ).
      APPEND lo_task TO lt_tasks.
    ENDLOOP.

    " Instantiate the framework — use 40 % of available dialog processes
    DATA(lo_parallel) = NEW cl_abap_parallel(
      p_percentage = 40
      p_timeout    = 300   " seconds per task
    ).

    " Fire!
    DATA lt_results TYPE cl_abap_parallel=>t_out_inst_tab.
    lo_parallel->run_inst(
      EXPORTING p_in_tab  = lt_tasks
      IMPORTING p_out_tab = lt_results
    ).

    " Harvest results
    LOOP AT lt_results ASSIGNING FIELD-SYMBOL(<res>).

      " Check for framework-level errors first (timeouts, system failures)
      IF <res>-message IS NOT INITIAL.
        MESSAGE <res>-message TYPE 'W'.
        CONTINUE.
      ENDIF.

      " Cast the generic reference back to our concrete task class
      DATA(lo_finished_task) = CAST zcl_invoice_validation_task( <res>-instance ).
      DATA(ls_val_result)    = lo_finished_task->get_result( ).

      IF ls_val_result-is_valid = abap_false.
        " Write rejection log, trigger workflow, etc.
        cl_demo_output=>write( |Invoice { ls_val_result-invoice_id }: { ls_val_result-error_msg }| ).
      ENDIF.

    ENDLOOP.

  ENDMETHOD.

ENDCLASS.

The key advantage here is type safety. Your invoice data lives in a properly typed class attribute (ms_invoice TYPE rbkp) — no intermediate XSTRING packing or XML transformation. When you cast <res>-instance back to zcl_invoice_validation_task, you get full IntelliSense and compile-time checks in ADT.

Pattern 2 — Inheritance from CL_ABAP_PARALLEL (Classic Style)

In this variant your task class inherits from CL_ABAP_PARALLEL and redefines the DO method. Input and output are exchanged via the inherited P_IN / P_OUT XSTRING parameters, which you serialize and deserialize yourself using EXPORT … TO DATA BUFFER / IMPORT … FROM DATA BUFFER.

This pattern is occasionally useful when working on older Basis releases or when you need to carry a heterogeneous shared payload (P_IN_ALL) across all tasks without repeating it for every record.

CLASS zcl_material_enrichment DEFINITION
  PUBLIC INHERITING FROM cl_abap_parallel
  FINAL CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS do REDEFINITION.
ENDCLASS.

CLASS zcl_material_enrichment IMPLEMENTATION.
  METHOD do.
    " Deserialize per-task input
    TYPES: BEGIN OF ty_input,
             matnr TYPE matnr,
             werks TYPE werks_d,
           END OF ty_input.
    DATA ls_input TYPE ty_input.
    IMPORT task_data = ls_input FROM DATA BUFFER p_in.

    " Do the heavy lifting — e.g. call an external pricing API
    DATA lv_price TYPE p LENGTH 10 DECIMALS 2.
    " ... your logic here ...

    " Serialize the result back
    TYPES: BEGIN OF ty_output,
             matnr TYPE matnr,
             price TYPE p LENGTH 10 DECIMALS 2,
           END OF ty_output.
    DATA ls_output TYPE ty_output.
    ls_output-matnr = ls_input-matnr.
    ls_output-price = lv_price.
    EXPORT result_data = ls_output TO DATA BUFFER p_out.
  ENDMETHOD.
ENDCLASS.

Practical tip: Prefer Pattern 1 (interface) for all new development. Reserve Pattern 2 for legacy codebases where you need XSTRING-based data exchange with an existing framework or where shared read-only context (P_IN_ALL) can meaningfully reduce payload size.

Constructor Parameters — What They Mean and How They Interact

One of the most common sources of confusion with CL_ABAP_PARALLEL is the interplay between its four resource-control parameters. Here is a clear breakdown:

Parameter Type Effect
P_PERCENTAGEI (0–100)Use X % of the dialog processes that the system deems available at startup. Takes precedence over P_NUM_TASKS if both are supplied.
P_NUM_TASKSIRequest a fixed number of parallel slots. Ignored if P_PERCENTAGE is also set or if the value exceeds the system maximum.
P_NUM_PROCESSESIHard cap on the total simultaneous processes across all app servers. A safeguard against monopolizing the system.
P_TIMEOUTI (seconds)Maximum wall-clock time a single task may run before the framework marks it as failed and populates <res>-message.
P_LOCAL_SERVERFlagWhen set, all tasks run on the same application server as the calling program. Useful for debugging; avoid in production.

Error Handling You Cannot Ignore

Every entry in p_out_tab (or p_out_inst_tab) has a MESSAGE field. This is not optional to check. If a task times out, encounters a system failure, or the framework cannot dispatch it, the result record will have a non-initial MESSAGE and a blank result. Silently skipping that check means silently dropping data.

LOOP AT lt_results ASSIGNING FIELD-SYMBOL(<r>).
  IF <r>-message IS NOT INITIAL.
    " Log to application log (SLG1) instead of just writing to screen
    cl_bali_log=>...   " or your preferred logging utility
    CONTINUE.
  ENDIF.
  " ... process normal result ...
ENDLOOP.

Additionally, wrap your entire run_inst call in a TRY … CATCH block for cx_sy_parallel_processing to handle catastrophic dispatch failures gracefully.

Quick Comparison: Before and After

Aspect Raw aRFC Pattern CL_ABAP_PARALLEL
BoilerplateRFC function module + callback handler + semaphore flagsOne class implementing IF_ABAP_PARALLEL
Type safetyEXPORT/IMPORT tables, genericTyped class attributes (Pattern 1)
Error handlingManual SYSTEM-EXCEPTIONS per callBuilt-in MESSAGE field per result
TestabilityDifficult (requires RFC infrastructure)Standard class — full unit-test coverage possible
ABAP Cloud compatibleNo (aRFC not available in Cloud)Yes (CL_ABAP_PARALLEL is released)

Closing Thoughts

CL_ABAP_PARALLEL does not do anything fundamentally new — parallel dialog dispatch has been in ABAP for decades. What it does is remove all the ceremony that made developers avoid it. With the interface-based pattern, adding parallelism to an existing data-processing class is often a matter of implementing one interface, one constructor, and adjusting a handful of lines in the orchestrating method.

If your team has been avoiding parallel processing because the old aRFC boilerplate felt too risky or too expensive to maintain, it is time to revisit that decision. Your business users — and the developers who come after you — will thank you.

4 Comments
akmal1216
Active Participant
0 Likes

Thank you Jelena for sharing them.

Sandra_Rossi
Active Contributor

Yes, why posting duplicate content without added value? For instance, CL_SSI_DISPATCH and RS_ABAP_PARALLEL have already been mentioned by human at SAP, here repeated without any explanation. I don't see the interest of mentioning them. Less clear than other blog posts, like mentioning both P_NUM_TASKS and P_NUM_PROCESSES, the latter being obsolete (still maintained for old applications using only P_NUM_PROCESSES).

Help - SAP Community:

« If you want to publish content that was helped/created by GenAI, you must add the user tag GenAI Assisted Content. »

d_schuitemaker62
Explorer
0 Likes

I disagree with the negative comments above.

Of the three mentioned, the first two posts are less clear to not so similar, and look more ChatGPT to me than this one.

The third post does address the same subject, equally comprehensive for implementation, but here I like the extra context more.