This is a test message to test the length of the message box.
Login
ABAP Cloud Migration Example
Created by Software-Heroes

ABAP Cloud - Migration (example)

1112

In this article we will take an example of migrating a report from TIER-3 to ABAP Cloud.

In the last few articles we mainly looked at the theory of ABAP Cloud, how the 3-TIER model is structured and how software components contribute to the structure. This article is about practice.

 

Introduction

In today's example we are using a simple report that we want to migrate to the ABAP Cloud. Reports can be migrated in two directions because they perform two tasks in classic ABAP:

  • Online execution with result
  • Batch execution for background processing

 

For the example shown and the test data, we use a Cloud Appliance Library from SAP in version S/4 HANA 2022. If you would like to recreate our example, you will find the corresponding test data there.

 

Use Case

Therefore, we must first determine what the intended purpose is. In our example, we use the report for background processing and usually schedule this as a job at night. The report looks like this, with only a rudimentary implementation and output available:

REPORT zfi_book_finance_document.

START-OF-SELECTION.
  DATA(go_document) = NEW zcl_book_fi_document_app( ).
  DATA(gs_document) = go_document->book( ).

  IF gs_document IS INITIAL.
    cl_demo_output=>display( go_document->mo_log->get_messages_flat( ) ).
  ELSE.
    cl_demo_output=>display( go_document->get_document( gs_document ) ).
  ENDIF.

 

This calls a global class for processing that contains our actual logic. There is a method to create the FI documents and a method that reads the document based on the data and returns the information.

CLASS zcl_book_fi_document_app DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    TYPES: BEGIN OF ts_document,
             belnr TYPE bkpf-belnr,
             bukrs TYPE bkpf-bukrs,
             gjahr TYPE bkpf-gjahr,
           END OF ts_document.

    TYPES ts_header TYPE bkpf.

    DATA mo_log TYPE REF TO zif_application_log READ-ONLY.

    METHODS constructor.

    METHODS book
      RETURNING VALUE(rs_result) TYPE ts_document.

    METHODS get_document
      IMPORTING is_document      TYPE ts_document
      RETURNING VALUE(rs_result) TYPE ts_header.
ENDCLASS.


CLASS zcl_book_fi_document_app IMPLEMENTATION.
  METHOD constructor.
    mo_log = zcl_application_log=>create( ).
  ENDMETHOD.


  METHOD book.
    DATA ls_header         TYPE bapiache09.
    DATA lt_accountgl      TYPE STANDARD TABLE OF bapiacgl09.
    DATA lt_currencyamount TYPE STANDARD TABLE OF bapiaccr09.
    DATA ld_key            TYPE bapiache09-obj_key.
    DATA lt_return         TYPE zif_application_log=>tt_message.

    ls_header = VALUE #( comp_code       = '1710'
                         doc_type        = 'SA'
                         doc_date        = '20240101'
                         pstng_date      = sy-datum
                         ref_doc_no_long = |Test: { sy-uname }|
                         username        = sy-uname ).

    lt_accountgl = VALUE #( ( itemno_acc = 1 gl_account = '0011002080' )
                            ( itemno_acc = 2 gl_account = '0070200000' costcenter = '0017101101' ) ).

    lt_currencyamount = VALUE #( currency = 'EUR'
                                 ( itemno_acc = 1 amt_doccur = '65.99' )
                                 ( itemno_acc = 2 amt_doccur = '-65.99' ) ).

    CALL FUNCTION 'BAPI_ACC_DOCUMENT_POST'
      EXPORTING
        documentheader = ls_header
      IMPORTING
        obj_key        = ld_key
      TABLES
        accountgl      = lt_accountgl
        currencyamount = lt_currencyamount
        return         = lt_return.

    mo_log->add_msg_bapi( it_bapiret = lt_return ).

    IF line_exists( lt_return[ type = 'E' ] ) OR line_exists( lt_return[ type = 'W' ] ).
      CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
    ELSE.
      CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.
      rs_result = ld_key.
    ENDIF.
  ENDMETHOD.


  METHOD get_document.
    SELECT SINGLE FROM bkpf
      FIELDS *
      WHERE     bukrs = @is_document-bukrs
            AND belnr = @is_document-belnr
            AND gjahr = @is_document-gjahr
      INTO @rs_result.
  ENDMETHOD.
ENDCLASS.

 

We use the function module BAPI_ACC_DOCUMENT_POST to generate the documents and the table BKPF to read the information.

 

Basic structure

In the first step we build the basic structure of the three TIERs. At the end the structure should look like our sketch.

 

Build TIER-1

In the first step, we build the basic structure as we described in the last article. We had already created the software component, which uses “ABAP for Cloud”. To do this, we now create the appropriate structure package with the same name.

 

In the second step, we assign the software component and the corresponding transport layer. Since we are traveling on the CAL system here and are not transporting, we leave the layer empty.

 

The TIER-1 package now looks like this, this is a structure package with the software component ZFI_T1_BNK and the language version "ABAP for Cloud Development", which it receives from the settings of the software component.

 

At the end of the first TIER, we create a development package where we will later map our development.

 

Build TIER-2

In the second TIER, for simplicity, we create a development package with the name of the software component and label it as TIER-2. Here you can also follow the pattern from TIER-1 and create the appropriate structure package first, but it would be a little more work. This way you can derive the assignment to the appropriate software component.

 

Since we created the package under the package ZCA_TIER2, the appropriate software component is pre-assigned. However, the component uses the language version “Standard ABAP” because we want to create our non-released wrappers here.

 

Logic migration

In the first step we create a new class that adopts the logic of the old class. We create an executable class here so that we can quickly see the first results and take over the report logic.

 

We adopt the logic from the report into the MAIN method. The class must be replaced by the new class. We also need to change the output slightly so that it now uses the OUT object of the MAIN method.

DATA(go_document) = NEW zcl_finance_document_new( ).
DATA(gs_document) = go_document->book( ).

IF gs_document IS INITIAL.
  out->write( go_document->mo_log->get_messages_flat( ) ).
ELSE.
  out->write( go_document->get_document( gs_document ) ).
ENDIF.

 

However, now that we have adopted all the logic from the ZCL_BOOK_FI_DOCUMENT_APP class, we cannot activate it yet and receive numerous error messages.

 

Neither the BAPI nor the table are released for use in ABAP Cloud. The compiler won't let us activate the class and we have to deal with the errors first.

 

Wrapper

Now how can we read the BKPF data? If there is no released API from SAP, we can build a wrapper.

 

Core Data Service

To do this, we create a core data service for the table. It is important here that we create the wrapper in TIER-2.

 

We create a simple structure with a few fields from the BKPF. If we were to create a real wrapper, we would also convert the field names to the English long names so that we bring the object closer to the standard.

@EndUserText.label: 'FI Header (Wrapper)'
define view entity ZFI_I_FinanceDocumentHeader
  as select from bkpf
{
  key bukrs,
  key gjahr,
  key belnr,
      blart,
      bldat,
      budat,
      cpudt,
      cputm,
      wwert,
      usnam,
      tcode,
      bvorg,
      xblnr,
      dbblg,
      stblg,
      stjah,
      bktxt,
      waers,
      kursf
}

 

After we have created and activated the CDS, we still have to release it so that we can use it in TIER-1. In Eclipse you will find the appropriate button in the “Properties” view in the “API State” tab.

 

We want to create a C1 Contract and release the object for "Use in Cloud Development" to be able to use it in our ABAP Cloud application. The status should be “Released”.

 

We can then validate the “API State” again and now see the new contract for the object.

 

Replacing the table

Now we can start replacing the BKPF calls with the new Core Data Service in the ZCL_FINANCE_DOCUMENT_NEW class.

TYPES: BEGIN OF ts_document,
         belnr TYPE ZFI_I_FinanceDocumentHeader-belnr,
         bukrs TYPE ZFI_I_FinanceDocumentHeader-bukrs,
         gjahr TYPE ZFI_I_FinanceDocumentHeader-gjahr,
       END OF ts_document.

TYPES ts_header TYPE ZFI_I_FinanceDocumentHeader.

 

Finally, we can now adjust the SELECT to get a result in the class again and thus get rid of the error messages.

SELECT SINGLE FROM ZFI_I_FinanceDocumentHeader
  FIELDS *
  WHERE     bukrs = @is_document-bukrs
        AND belnr = @is_document-belnr
        AND gjahr = @is_document-gjahr
  INTO @rs_result.

 

BAPI

In the last step we want to get rid of the BAPI. Here we could also build a wrapper, but first we should check whether there is already a successor object.

 

Successor

Using the Cloudification Browser or the Cloudification Repository Viewer, we can check whether there is already a defined successor for the BAPI_ACC_DOCUMENT_POST function module and we will find it:

 

This is the RAP object I_JOURNALENTRYTP, which replaces the BAPI.

 

Documentation

How does the RAP object actually work? SAP provides documentation here via Knowledge Transfer Document (KTD). If we look at the behavior for our RAP object I_JOURNALENTRYTP, we will see a link to navigation in the upper part.

 

By clicking on "Open Documentation" we load the KTD, here we can view the documentation for the object. The object is divided into different areas, we are particularly interested in the area of the “POST” and “REVERSE” campaigns, for booking and cancellation.

 

In this case, the documentation gives us a nice overview of the structure of the call and some examples of how to implement the object in the code.

 

RAP Object

Let's implement the RAP object to replace the BAPI in our class. The POST is an action, which means that before calling it we first have to fill the structure with all the data, which we then pass on to the RAP object.

DATA lt_new TYPE TABLE FOR ACTION IMPORT i_journalentrytpJournalEntry~Post.

INSERT VALUE #(
    %cid   = to_upper( cl_uuid_factory=>create_system_uuid( )->create_uuid_x16( ) )
    %param = VALUE #(
        companycode            = '1710'
        createdbyuser          = sy-uname
        accountingdocumenttype = 'SA'
        documentdate           = '20240101'
        postingdate            = cl_abap_context_info=>get_system_date( )
        DocumentReferenceID    = |RAP: { sy-uname }|
        _glitems               = VALUE #(
            ( glaccountlineitem = '1' glaccount = '0011002080' _currencyamount = VALUE #( ( journalentryitemamount = '-100.55' currency = 'EUR' ) ) )
            ( glaccountlineitem = '2' glaccount = '0070200000' _currencyamount = VALUE #( ( journalentryitemamount = '100.55' currency = 'EUR' ) ) ) ) ) )
       INTO TABLE lt_new.

 

We define an internal table with the data type of the action and then fill the required structure. We fill the %CID with a unique ID for our process, and we then enter the contents of the booking via %PARAM. This means that all data is in the internal table. Now we can call the action on the RAP object.

MODIFY ENTITIES OF i_journalentrytp
       ENTITY journalentry
       EXECUTE post FROM lt_new
       FAILED DATA(ls_failed_deep)
       REPORTED DATA(ls_reported_deep).

 

If we receive an error message, the FAILED structure is filled and we can transfer the error messages to our log via REPORTED.

LOOP AT ls_reported_deep-journalentry INTO DATA(ls_entry).
  mo_log->add_msg_text( ls_entry-%msg->if_message~get_text( ) ).
ENDLOOP.

 

If everything has gone through the check cleanly, then we can have the document created in the database. To do this, we have to call a COMMIT ENTIRIES and get the document number created again via REPORTED.

COMMIT ENTITIES BEGIN
       RESPONSE OF i_journalentrytp
       REPORTED DATA(lt_commit_reported).

DATA(ls_commit) = lt_commit_reported-journalentry[ 1 ].
rs_result-belnr = ls_commit-AccountingDocument.
rs_result-bukrs = ls_commit-CompanyCode.
rs_result-gjahr = ls_commit-FiscalYear.

COMMIT ENTITIES END.

 

Structure

Now that all objects have been activated, the architecture should look like this. The class now exists in TIER-1 and we have a wrapper on TIER-2 that makes the table available for TIER-1 to read.

 

 

Final class

To compare, the final class with all changes and extensions. The class could now be activated and can be used in TIER-1.

CLASS zcl_finance_document_new DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

    TYPES: BEGIN OF ts_document,
             belnr TYPE ZFI_I_FinanceDocumentHeader-belnr,
             bukrs TYPE ZFI_I_FinanceDocumentHeader-bukrs,
             gjahr TYPE ZFI_I_FinanceDocumentHeader-gjahr,
           END OF ts_document.

    TYPES ts_header TYPE ZFI_I_FinanceDocumentHeader.

    DATA mo_log TYPE REF TO zif_application_log READ-ONLY.

    METHODS constructor.

    METHODS book
      RETURNING VALUE(rs_result) TYPE ts_document.

    METHODS get_document
      IMPORTING is_document      TYPE ts_document
      RETURNING VALUE(rs_result) TYPE ts_header.
ENDCLASS.


CLASS zcl_finance_document_new IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    DATA(go_document) = NEW zcl_finance_document_new( ).
    DATA(gs_document) = go_document->book( ).

    IF gs_document IS INITIAL.
      out->write( go_document->mo_log->get_messages_flat( ) ).
    ELSE.
      out->write( go_document->get_document( gs_document ) ).
    ENDIF.
  ENDMETHOD.


  METHOD constructor.
    mo_log = zcl_application_log=>create( ).
  ENDMETHOD.


  METHOD book.
    DATA lt_new TYPE TABLE FOR ACTION IMPORT i_journalentrytpJournalEntry~Post.

    INSERT VALUE #(
        %cid   = to_upper( cl_uuid_factory=>create_system_uuid( )->create_uuid_x16( ) )
        %param = VALUE #(
            companycode            = '1710'
            createdbyuser          = sy-uname
            accountingdocumenttype = 'SA'
            documentdate           = '20240101'
            postingdate            = cl_abap_context_info=>get_system_date( )
            DocumentReferenceID    = |RAP: { sy-uname }|
            _glitems               = VALUE #(
                ( glaccountlineitem = '1' glaccount = '0011002080' _currencyamount = VALUE #( ( journalentryitemamount = '-100.55' currency = 'EUR' ) ) )
                ( glaccountlineitem = '2' glaccount = '0070200000' _currencyamount = VALUE #( ( journalentryitemamount = '100.55' currency = 'EUR' ) ) ) ) ) )
           INTO TABLE lt_new.

    MODIFY ENTITIES OF i_journalentrytp
           ENTITY journalentry
           EXECUTE post FROM lt_new
           FAILED DATA(ls_failed_deep)
           REPORTED DATA(ls_reported_deep).

    IF ls_failed_deep IS INITIAL.
      COMMIT ENTITIES BEGIN
             RESPONSE OF i_journalentrytp
             REPORTED DATA(lt_commit_reported).

      DATA(ls_commit) = lt_commit_reported-journalentry[ 1 ].
      rs_result-belnr = ls_commit-AccountingDocument.
      rs_result-bukrs = ls_commit-CompanyCode.
      rs_result-gjahr = ls_commit-FiscalYear.

      COMMIT ENTITIES END.

    ELSE.
      LOOP AT ls_reported_deep-journalentry INTO DATA(ls_entry).
        mo_log->add_msg_text( ls_entry-%msg->if_message~get_text( ) ).
      ENDLOOP.
    ENDIF.
  ENDMETHOD.


  METHOD get_document.
    SELECT SINGLE FROM ZFI_I_FinanceDocumentHeader
      FIELDS *
      WHERE     bukrs = @is_document-bukrs
            AND belnr = @is_document-belnr
            AND gjahr = @is_document-gjahr
      INTO @rs_result.
  ENDMETHOD.
ENDCLASS.

 

Application Log

In our example we are already using an ABAP Cloud Application Log with the new classes. That's why we don't even re-describe the logic in this example. If you build a TIER-1 re-use component with the new classes, then you can also use it normally in your TIER-3 applications.

 

Hint: Currently TIER-3 components and applications can use any TIER-1 object, there is no review of the C1 contract.

 

Application Job

In the next step we want to create a job because our old report was used as a job. For this we use the new concept of application jobs.

 

Creation

In the first step, we create a new subpackage to have all job-relevant objects in one package.

 

As a second step, we need the actual job class, which is started when the job is executed. This class requires the two interfaces IF_APJ_DT_EXEC_OBJECT and IF_APJ_RT_EXEC_OBJECT.

 

Now we can implement the actual logic in the EXECUTE method. We call up our document class, book the FI document, output an additional message to the log and then save the message to the job. We don't need GET_PARAMETERS in our example because we don't expect any input from outside. The complete job class now looks like this:

CLASS zcl_finance_job DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_apj_dt_exec_object.
    INTERFACES if_apj_rt_exec_object.
ENDCLASS.


CLASS zcl_finance_job IMPLEMENTATION.
  METHOD if_apj_dt_exec_object~get_parameters.
  ENDMETHOD.


  METHOD if_apj_rt_exec_object~execute.
    DATA(go_document) = NEW zcl_finance_document_new( ).

    DATA(gs_document) = go_document->book( ).
    go_document->mo_log->add_msg_text(
        |BELNR: { gs_document-belnr }, BUKRS: { gs_document-bukrs }, GJAHR: { gs_document-gjahr }| ).

    DATA(lo_header) = cl_bali_header_setter=>create(
        object      = 'ZFI_LOG'
        subobject   = 'DOC'
        external_id = cl_abap_context_info=>get_system_date( ) && cl_abap_context_info=>get_system_time( ) ).

    go_document->mo_log->save( id_save_with_job = abap_true
                               io_header        = lo_header ).
  ENDMETHOD.
ENDCLASS.

 

Job

In the next step we have to create the catalog and the template for the job. If you are looking for a job, you will find the two objects in the system.

 

First we need the catalog and assign the class there.

 

In the next step we generate the template and use the catalog from the previous step as a reference.

 

Test

Let's test the new job and go to the "Application Jobs" app, where we schedule the job once and look at the result in the log.

 

In the second step, we look at the generated document via the Core Data Service in Eclipse (Data Preview).

 

The test has been successful so far, the components in ABAP Cloud work and give us the desired result.

 

Structure

The new structure looks like this with the new objects, although we have now left TIER-3 empty because all objects have been successfully migrated to TIER-1 and 2.

 

Move wrapper

In the last step we want to get rid of the TIER-2 wrapper. Here we assume that a released object has been delivered. In this case, there is a corresponding shared Core Data Service for the table that we can now use. Such releases are currently done by upgrading the system, but in the future this may also be done by notification if releases are only available every two years.

 

Successor

The successor to the BKPF can currently be determined in two ways. Firstly, you can open the table in the ABAP Development Tools and navigate to the "Properties" view. Here you will find the information under "API State" if it is stored in the system is.

 

If you still have an older release and there is no information about a successor, you can get the information via the Cloudification Repository. For this there is the viewer from SAP and a viewer from us. In both we get the successor for the table.

 

Exchange

Since there is now a successor to our wrapper, we have two options in this case:

  1. Replace wrapper - We replace our wrapper at all call points and then delete the wrapper because it is no longer needed.
  2. Adjust Wrapper - We adjust our wrapper and use the shared object instead of directly the table like before.

 

Here you should differentiate from case to case. The more often the wrapper is used, the more worthwhile it is to use variant 2 and only change the contents of the wrapper once. In this example we also use the second variant and only swap the content. When changing the shared object, we get a warning, but we just have to confirm it.

 

Now we change the data source in the Core Data Service and now have to map the English long names to the German short names. In this case, the mapping beforehand would have made a lot more sense.

define view entity ZFI_I_FinanceDocumentHeader
  as select from I_JournalEntry
{
  key CompanyCode                    as bukrs,
  key AccountingDocument             as belnr,
  key FiscalYear                     as gjahr,
      AccountingDocumentType         as blart,
      DocumentDate                   as bldat,
      PostingDate                    as budat,
      FiscalPeriod                   as monat,
      AccountingDocumentCreationDate as cpudt,
      CreationTime                   as cputm,
      LastManualChangeDate           as aedat,
      LastAutomaticChangeDate        as upddt,
      ExchangeRateDate               as wwert,
      AccountingDocCreatedByUser     as usnam,
      TransactionCode                as tcode,
      IntercompanyTransaction        as bvorg,
      DocumentReferenceID            as xblnr,
      RecurringAccountingDocument    as dbblg,
      ReverseDocument                as stblg,
      ReverseDocumentFiscalYear      as stjah,
      AccountingDocumentHeaderText   as bktxt,
      TransactionCurrency            as waers,
      ExchangeRate                   as kursf
}

 

Finally, just activate the object.

 

Move

Since our wrapper is now ready for ABAP Cloud, we can move it to a corresponding TIER-1 package. However, before we can move it, we need to change the language version of the object. In the "Properties" view in the "General" tab we can adjust the language version using the "Edit" button and then activate the object again.

 

By right-clicking on the object in the "Project Explorer" you can select the "Change Package Assignment..." function in the menu.

 

In the subsequent dialog, just enter the target package and carry out the action. If the object is ABAP Cloud Ready, it will be moved accordingly and we are done.

 

Structure

TIER-2 is now also empty and our application was completely implemented with ABAP Cloud. It won't always be that easy; the second TIER can be more or less large, depending on which module is used. In some cases it can also happen that objects from TIER-2 never come to TIER-1 and always remain there.

 

Summary

What we have done to migrate the coding, here is a brief summary.

  • Creating the structure for the TIER-1 components
  • Creation of a wrapper and release for TIER-1
  • Migration of coding and replacement of objects that have not been released
  • Creation of the job
  • Migration of the wrapper (possible after release)

 

Conclusion

The example is kept relatively simple in order to illustrate the various steps as simply as possible. They are intended to give you an insight into working with ABAP Cloud and give you a practical insight into the topic.

 


Included topics:
ABAP CloudABAPMigrationExample
Comments (0)



And further ...

Are you satisfied with the content of the article? We post new content in the ABAP area every Friday and irregularly in all other areas. Take a look at our tools and apps, we provide them free of charge.


ABAP Cloud - Parallel processing

Category - ABAP

The CL_ABAP_PARALLEL class has been around for a while and is also used in ABAP Cloud. In this article you will learn more about its use and effects.

04/12/2024

ABAP Cloud - 3-TIER Model

Category - ABAP

The 3-TIER model is an essential part of the transition architecture towards the ABAP Cloud. In this section we will go into more detail about the structure.

03/22/2024

ABAP Cloud - Introduction

Category - ABAP

What is ABAP Cloud, what does it do and how can we use it? This article is about introducing the topic.

03/08/2024

ABAP Cloud vs. ABAP in the Cloud

Category - ABAP

The terms actually sound quite similar and are unfortunately often mistaken for the same thing. In this article we will look at the differences.

03/01/2024

ABAP Cloud - Jobs

Category - ABAP

What do jobs actually look like in ABAP Cloud and how are they created and used in the new world? You can find out more about this in this article.

02/23/2024