This is a test message to test the length of the message box.
Login
ABAP RAP Report Pattern
Created by Software-Heroes

RAP - Report Pattern

1381

How is the Report Pattern structured in RAP and what can you do with it? Read more in this ABAP article.



In this article we will look at the report pattern and how you can best use it for yourself. We will look at the different layers and options.

 

Introduction

The ABAP RESTful Programming Model is the new model in ABAP for creating cloud ready and clean core applications. With RAP you can not only create applications but also provide interfaces for internal and external use. With the latest features, RAP is very flexible in terms of structure and use, which is why we would like to divide the applications into different patterns.

 

Structure

The "Report Pattern" is characterized by the fact that it is very similar to the classic report. The data source of the RAP application is a standard Core Data Service. We use an "unmanaged save" to save the data, but are free to connect additional entities and information. Before we start with the setup, here is the general legend:

 

The following features are used to differentiate:

  • Data source is a core data service (standard, custom)
  • Data should be displayed, but not changed
  • Additional data via association (additional entity, additional fields)
  • Unmanaged save to save the information

 

 

Example

In the following sections, we will create a reporting application and expand it bit by bit to include various requirements. The application should show us the current currencies in the system. We would also like to store our own information, such as documentation on the currency, an image and additional information on which countries our company uses the currency in.

 

Data

In the first step, we build our additional data model and create the various layers of the RAP object (interface and consumption).

 

Tables

In the first step, we create a table for additional information that we want to store on the currency. Since we do not want to directly extend the currency table and the Core Data Service, we create the table in the customer namespace.

@EndUserText.label : 'Additional Currency Informations'
define table zbs_drp_addcurr {
  key client    : abap.clnt not null;
  key currency  : waers not null;
  ccomment      : abap.char(60);
  documentation : abap.string(0);
  picture_url   : abap.string(0);
  last_editor   : abap.char(12);
}

 

For this we need an entity under the currency where we want to store additional information about the countries. The table therefore looks like this:

@EndUserText.label : 'Country Assignment'
define table zbs_drp_country {
  key client   : abap.clnt not null;
  key currency : waers not null;
  key country  : land1 not null;
  ranking      : abap.int2;
}

 

Basis

Since we want to work with Core Data Services later, we create a base layer above the two tables that makes the table available as simple views. Here we also have the opportunity to normalize the fields for our data model.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Additional Informations'
define view entity ZBS_B_DRPAdditionalCurrency
  as select from zbs_drp_addcurr
{
  key currency      as Currency,
      ccomment      as CurrencyComment,
      documentation as Documentation,
      picture_url   as PictureURL,
      last_editor   as LastEditor
}

 

The base view for the country entity now looks like this:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Country Assignment'
define view entity ZBS_B_DRPCurrencyCountry
  as select from zbs_drp_country
{
  key currency as Currency,
  key country  as Country,
      ranking  as CountryRanking
}

 

Hint: The base layer is optional, but in this case it serves to make the fields of the table available cleanly for further processing.

 

Interface

In the interface layer we now model the data that we need later in our model for display and make all the fields available for the consumption layer. In the root view we establish the relationship to the countries and adopt all the additional data that we need later for the extension.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Currency Overview'
define root view entity ZBS_R_DRPCurrency
  as select from I_Currency
  composition of many ZBS_I_DRPCurrencyCountry          as _Country
  association of one to one ZBS_B_DRPAdditionalCurrency as _Data on _Data.Currency = $projection.Currency
  association of one to one I_BusinessUserVH            as _User on _User.UserID = $projection.lasteditor
{
  key Currency,
      Decimals,
      CurrencyISOCode,
      AlternativeCurrencyKey,
      _Text[ Language = $session.system_language ].CurrencyName,
      _Text[ Language = $session.system_language ].CurrencyShortName,      
      _Data.CurrencyComment,
      _Data.Documentation,
      _Data.PictureURL,
      _Data.LastEditor,
      _Country,
      _User
}

 

The view for the country assignment establishes the relationship to the root entity and already provides all information, such as the name, for the consumption view.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Country Assignment'
define view entity ZBS_I_DRPCurrencyCountry
  as select from ZBS_B_DRPCurrencyCountry
  association        to parent ZBS_R_DRPCurrency as _Currency on _Currency.Currency = $projection.Currency
  association of one to one I_Country            as _Country  on _Country.Country = $projection.Country
{
  key Currency,
  key Country,
      _Country._Text[ Language = $session.system_language ].CountryName,
      CountryRanking,
      _Currency
}

 

Projection

In the next step, we create the projection layer for the currency. Here you will also find a general search using the key and the text fields (@Search). In addition, we read fields that we only want to display in the consumption layer, such as the name of the last changer.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Consumption for Currency'
@Metadata.allowExtensions: true
@Search.searchable: true
define root view entity ZBS_C_DRPCurrency
  provider contract transactional_query
  as projection on ZBS_R_DRPCurrency
{
      @Search.defaultSearchElement: true
      @Search.fuzzinessThreshold: 1.0
      @Search.ranking: #HIGH
  key Currency,
      Decimals,
      CurrencyISOCode,
      AlternativeCurrencyKey,
      @Search.defaultSearchElement: true
      @Search.fuzzinessThreshold: 0.7
      @Search.ranking: #MEDIUM
      CurrencyName,
      @Search.defaultSearchElement: true
      @Search.fuzzinessThreshold: 0.8
      @Search.ranking: #MEDIUM
      CurrencyShortName,
      CurrencyComment,
      Documentation,
      PictureURL,
      LastEditor,
      _User.PersonFullName as EditorName,
      _Country : redirected to composition child ZBS_C_DRPCurrencyCountry
}

 

We already connect the standard search help to assign the countries. You should also make sure that the annotation "@Metadata.allowExtensions: true" is set for the next step in order to generate the metadata and thus give the UI a look.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Consumption for Country Assignment'
@Metadata.allowExtensions: true
define view entity ZBS_C_DRPCurrencyCountry
  as projection on ZBS_I_DRPCurrencyCountry
{
  key Currency,
      @Consumption.valueHelpDefinition: [{ entity: { name: 'I_CountryVH', element: 'Country' } }]
  key Country,
      CountryName,
      CountryRanking,
      _Currency : redirected to parent ZBS_C_DRPCurrency
}

 

Service and UI

So that we can now see the first service and a first UI from the data, we can now release our RAP object to the outside. In the first step, we release the service to the outside with a binding, in the second step we create metadata to assign the elements to the image.

 

Service 

To do this, we create a service definition on our consumption view using the context menu. In this case, we can also define the "LeadingEntity", the annotation ensures a marking in the service binding.

@EndUserText.label: 'Currency Service'
@ObjectModel.leadingEntity.name: 'ZBS_C_DRPCurrency'
define service ZBS_DEMO_DRP_CURRENCY {
  expose ZBS_C_DRPCurrency        as Currency;
  expose ZBS_C_DRPCurrencyCountry as Country;
}

 

Finally, we create a service binding. In this case, we generate an OData v4 service as the UI, since we want to create our app here.

 

Finally, don't forget to press the "Publish" button so that the service is created and can be called from outside.

 

Metadata

If you have already called up the preview of your application, you will see an empty image, but at least with a list. You can use the gear wheel and adjust the filters to display elements and thus view the first data. To ensure that we find our tidy UI as a default, we create metadata extensions for our consumption views.

@Metadata.layer: #CUSTOMER
@UI: {
  headerInfo: {
    typeName: 'Currency',
    typeNamePlural: 'Currencies',
    title: { value: 'Currency' },
    description: { value: 'CurrencyName' },
    imageUrl: 'PictureURL'
  }
}
annotate entity ZBS_C_DRPCurrency with
{
  @UI.facet      : [
    {
      id         : 'idTechFields',
      label      : 'Technical Details',
      position   : 10,
      type       : #IDENTIFICATION_REFERENCE,
      targetQualifier: 'TECH'
    },
    {
      id         : 'idAddFields',
      label      : 'Additional Info',
      position   : 20,
      type       : #IDENTIFICATION_REFERENCE,
      targetQualifier: 'ADD'
    },
    {
      id         : 'idDocumentation',
      label      : 'Documentation',
      position   : 30,
      type       : #IDENTIFICATION_REFERENCE,
      targetQualifier: 'DOCU'
    },
    {
      id         : 'idChanges',
      label      : 'Changes',
      position   : 40,
      type       : #IDENTIFICATION_REFERENCE,
      targetQualifier: 'CHANGE'
    },
    {
      id         : 'idCountryTable',
      label      : 'Country Assignment',
      position   : 50,
      type       : #LINEITEM_REFERENCE,
      targetElement: '_Country'
    }
  ]

  @UI:{
    lineItem: [{ position: 10 }],
    selectionField: [{ position: 10 }]
  }
  Currency;

  @UI:{
    lineItem: [{ position: 20 }]
  }
  CurrencyName;

  @UI:{
    lineItem: [{ position: 30 }]
  }
  CurrencyShortName;

  @UI:{
    identification: [{ position: 10, qualifier: 'TECH' }]
  }
  CurrencyISOCode;

  @UI:{
    identification: [{ position: 20, qualifier: 'TECH' }]
  }
  Decimals;

  @UI:{
    identification: [{ position: 30, qualifier: 'TECH' }]
  }
  AlternativeCurrencyKey;

  @UI:{
    identification: [{ position: 40, qualifier: 'ADD' }]
  }
  @UI.multiLineText: true
  @EndUserText.label: 'Comment'
  CurrencyComment;

  @UI:{
    identification: [{ position: 50, qualifier: 'DOCU' }]
  }
  @UI.multiLineText: true
  @EndUserText.label: 'Informations'  
  Documentation;

  @UI:{
    identification: [{ position: 60, qualifier: 'ADD' }]
  }
  @EndUserText.label: 'Currency Image'  
  PictureURL;

  @UI:{
    identification: [{ position: 70, qualifier: 'CHANGE' }]
  }
  @EndUserText.label: 'Last Changer (ID)'  
  LastEditor;

  @UI:{
    lineItem: [{ position: 40 }],
    identification: [{ position: 80, qualifier: 'CHANGE' }]
  }
  @EndUserText.label: 'Last Changer'
  EditorName;
}

 

Here we assign texts for our built-in types, place the elements on the image, create groups and filters. The assignment of countries is quite straightforward, since we only want to display the information in the detailed image and do not need the object page. We can hide the currency, since the information would only be duplicated.

@Metadata.layer: #CUSTOMER
annotate entity ZBS_C_DRPCurrencyCountry with
{
  @UI.hidden: true
  Currency;

  @UI:{
    lineItem: [{ position: 10 }]
  }
  Country;

  @UI:{
    lineItem: [{ position: 20 }]
  }
  CountryName;
  
  @UI:{
    lineItem: [{ position: 30 }]
  }
  CountryRanking;
}

 

Preview

Since the application now has an image, we can take a first look at the application. The list for the evaluation is now available. On the entry page we get all the necessary information about the currency.

 

The details page is now also tidied up and offers additional information about the currency, which comes from our own table. We have now "extended" the standard to include additional fields, which we now want to make changeable in the next section.

 

Behavior

In order for the app to now be interactive and changeable, we still need to implement a behavior. So far, it is purely a display app, but the user can already work with it to get the necessary information.

 

Draft

If we want to use the standard operations, we also have to activate draft handling for the OData v4 service. To do this, we have to add the following fields to our main table:

local_last_changed : abp_locinst_lastchange_tstmpl;
last_changed       : abp_lastchange_tstmpl;

 

We then have to publish the fields down to the interface level so that we can use them in the behavior definition. To do this, we need a draft table for each view, which then buffers all the fields of the view. For the currency, this would be:

@EndUserText.label : 'Draft für Currency'
define table zbs_drp_currd {
  key client             : abap.clnt not null;
  key currency           : waers not null;
  decimals               : abap.int1;
  currencyisocode        : abap.char(3);
  alternativecurrencykey : abap.char(3);
  currencyname           : abap.char(40);
  currencyshortname      : abap.char(15);
  currencycomment        : abap.char(60);
  documentation          : abap.string(0);
  pictureurl             : abap.string(0);
  lasteditor             : abap.char(12);
  locallastchanged       : abp_locinst_lastchange_tstmpl;
  lastchanged            : abp_lastchange_tstmpl;
  "%admin"               : include sych_bdl_draft_admin_inc;
}

 

For the country allocation, the draft table would look like this.

@EndUserText.label : 'Draft for Country'
define table zbs_drp_cound {
  key client     : abap.clnt not null;
  key currency   : waers not null;
  key country    : land1 not null;
  countryname    : abap.char(50);
  countryranking : abap.int2;
  "%admin"       : include sych_bdl_draft_admin_inc;
}

 

Behavior definition

Now we can implement the corresponding behavior. Since we want to use the normal UPDATE action of RAP, we have to implement Draft. To do this, we set CREATE and DELETE to Internal, since we do not want to offer these actions to the user, at least for the root entity. We use an Unmanaged Save to save the data ourselves. Managed helps us manage the data in the Draft and the Transactional Buffer, so we don't have to worry about it. We need the defined timestamps for the ETAGs. We set all fields that come from the standard to READONLY so that the user does not think that he can change them via the application.

managed implementation in class zbp_bs_drp_currency unique;
strict ( 2 );
with draft;

define behavior for ZBS_R_DRPCurrency alias Currency
with unmanaged save
draft table zbs_drp_currd
etag master LocalLastChanged
lock master total etag LastChanged
authorization master ( instance )
{
  internal create;
  update;
  internal delete;

  draft action Edit;
  draft action Activate optimized;
  draft action Discard;
  draft action Resume;
  draft determine action Prepare;

  field ( readonly )
  Currency,
  Decimals,
  CurrencyISOCode,
  AlternativeCurrencyKey,
  CurrencyName,
  CurrencyShortName,
  LastEditor;

  mapping for zbs_drp_addcurr
    {
      Currency         = currency;
      CurrencyComment  = ccomment;
      Documentation    = documentation;
      PictureURL       = picture_url;
      LastEditor       = last_editor;
      LocalLastChanged = local_last_changed;
      LastChanged      = last_changed;
    }

  association _Country { create; with draft; }
}

define behavior for ZBS_I_DRPCurrencyCountry alias Country
with unmanaged save
draft table zbs_drp_cound
lock dependent by _Currency
authorization dependent by _Currency
{
  update;
  delete;

  field ( readonly )
  Currency,
  CountryName;

  field ( readonly : update )
  Country;

  mapping for zbs_drp_country
    {
      Currency       = currency;
      Country        = country;
      CountryRanking = ranking;
    }

  association _Currency { with draft; }
}

 

In the projection we now provide the actions and options that the user needs to work with our object. The draft actions (Edit, Activate, Discard, Resume and Prepare) are mandatory so that the draft can be used.

projection;
strict ( 2 );
use draft;

define behavior for ZBS_C_DRPCurrency alias Currency
{
  use update;

  use action Edit;
  use action Activate;
  use action Discard;
  use action Resume;
  use action Prepare;

  use association _Country { create; with draft; }
}

define behavior for ZBS_C_DRPCurrencyCountry alias Country
{
  use update;
  use delete;

  use association _Currency { with draft; }
}

 

Behavior implementation

In the behavior implementation, we now take care of saving the changed data records in the SAVE_MODIFIED method. We benefit from the fact that we have configured the mapping in the behavior definition. With the mapping, we can use the MAPPING FROM ENTITY addition in CORRESPONDING to map the normalized fields of the entity to the database structure. However, you should also work with the %CONTROL structure when UPDATE, otherwise fields in the database can be accidentally deleted.

CLASS lhc_Currency DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.
    METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
      IMPORTING keys REQUEST requested_authorizations FOR Currency RESULT result.

ENDCLASS.


CLASS lhc_Currency IMPLEMENTATION.
  METHOD get_instance_authorizations.
  ENDMETHOD.
ENDCLASS.


CLASS lsc_ZBS_R_DRPCURRENCY DEFINITION INHERITING FROM cl_abap_behavior_saver.
  PROTECTED SECTION.
    METHODS
      save_modified REDEFINITION.

    METHODS
      cleanup_finalize REDEFINITION.

ENDCLASS.


CLASS lsc_ZBS_R_DRPCURRENCY IMPLEMENTATION.
  METHOD save_modified.
    LOOP AT update-currency INTO DATA(ls_new_currency).
      ls_new_currency-LastEditor = cl_abap_context_info=>get_user_technical_name( ).

      INSERT zbs_drp_addcurr FROM @ls_new_currency MAPPING FROM ENTITY.
      IF sy-subrc <> 0.
        UPDATE zbs_drp_addcurr FROM @ls_new_currency INDICATORS SET STRUCTURE %control MAPPING FROM ENTITY.
      ENDIF.
    ENDLOOP.

    LOOP AT create-country INTO DATA(ls_create_country).
      INSERT zbs_drp_country FROM @ls_create_country MAPPING FROM ENTITY.
    ENDLOOP.

    LOOP AT update-country INTO DATA(ls_update_country).
      UPDATE zbs_drp_country FROM @ls_update_country INDICATORS SET STRUCTURE %control MAPPING FROM ENTITY.
    ENDLOOP.

    LOOP AT delete-country INTO DATA(ls_delete_country).
      DELETE zbs_drp_country FROM @( CORRESPONDING zbs_drp_country( ls_delete_country MAPPING FROM ENTITY ) ).
    ENDLOOP.
  ENDMETHOD.


  METHOD cleanup_finalize.
  ENDMETHOD.
ENDCLASS.

 

With the currency we only have to worry about the UPDATE case, since the data records come from the database. However, we have to use the MODIFY for this, since we do not know whether the data record has already been saved.

 

Summary

The "Report Pattern" can be implemented in several steps, depending on the requirements of the report. As a basis, you always need:

  • Data model
  • Service
  • Metadata
  • Authorization (Access Control)

 

This allows you to display the data in the UI and the user can interact with it. This would also correspond to a pure display ALV. Finally, you should also not forget to equip the various Core Data Services with authorizations (Access Control). Since our standard entities do not have any authorization checks, we have not implemented any either. If you would like to make further changes, you will need additional components:

  • Draft (if required)
  • Behavior definition
  • Behavior implementation

 

The last components already correspond very much to a report with changing functionalities. Here you also get added value for extending the standard entities without necessarily having to modify them. The end result now looks like this.

 

The app also has a hidden feature that allows us to dynamically adjust the image in the header of the object page using the "Image URL" field. In addition, we can use the "Edit" Button to customize all our own fields and save them to the database.

 

Complete example

Since the example is quite complex and contains many objects, you can find all created objects in a commit in our GitHub repository on the topic of ABAP RESTful Programming Model. Or you can take a look at the package ZBS_DEMO_RAP_PATTERN_REPORT in the repository for the complete application. This will allow you to compare the various objects again that we did not fully list after the change.

 

Conclusion

With the report pattern you can easily implement display reports, but also create complex change transactions to create customer extensions that process additional data.


Included topics:
RAPBTPPatternReport
Comments (1)



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.


RAP - Semantic Key

Category - ABAP

What do you need the semantic key for and how is it represented in the ABAP RESTful Programming Model? You can find out more here.

12/13/2024

RAP - File Upload (Stream)

Category - ABAP

How can you easily load files into your RAP object and make them available in ABAP? Let's take a look at the details.

12/10/2024

RAP - Translation app (example)

Category - ABAP

Let's take a look at a practical example of developing a RAP application in the ABAP environment and how you can create an app with little effort.

08/02/2024

RAP - Custom Entity Value Help (Deep Dive)

Category - ABAP

With the Custom Entity you have the most freedom in RAP when developing ABAP Cloud applications, but what about potential errors?

07/12/2024

RAP - Deep Action in OData v4

Category - ABAP

In this article we will look at actions with deep structures, how we can create them and pass data to an API endpoint.

05/24/2024