This is a test message to test the length of the message box.
Login
ABAP Quick Generic Query Implementation
Created by Software-Heroes

ABAP Quick - Generic Query Implementation

914

Tired of the same old query class implementation for custom entities in RAP? It's time for a reusable component.



In this article, we'll look at an implementation for starting a reusable query using a custom entity and easily accessing data from an OData, regardless of the system the data is located on.

 

Introduction

Using a custom entity, there's a simple way to create a value help for another system in one system. The data is read from a remote system at runtime and made available to the user. This makes it very easy to implement remote scenarios. However, implementing the query is quite complex, especially if you have to do it over and over again.

 

Query

The Query class is defined in the Core Data Service using the annotation "@ObjectModel.query.implementedBy: 'ABAP:xxx'" and the implementation is called in the SADL framework at runtime. We must implement the SELECT method of the IF_RAP_QUERY_PROVIDER interface.

 

In addition to the actual method, some methods (GET_SORT_ELEMENTS, GET_PAGING) must be called from the IO_REQUEST object, otherwise a dump will occur. You can find more information about this in the Deep Dive on Custom Entities.

 

Concept

In this article, we plan a generic implementation of the query in various constellations so that the functions can be easily reused in further implementations and services. This should save you implementation costs in subsequent projects.

 

Architecture

For the architecture, we use the Factory Pattern to cover the topic of decoupling and testability. We also need an exception class if certain steps cannot be implemented or if the query aborts.

 

Since such a component is usually developed across all components and we have to deal with the concept of software components in ABAP Cloud, we should consider the topic of decoupling and use via SWC.

 

Configuration

To have a configuration for access, we define it as a structure in the interface. The advantage of a structure is that we only have to pass data through the class once, without defining each individual parameter. This means the interface can be extended at any time without having to adapt much code.

TYPES property TYPE STANDARD TABLE OF if_com_arrangement_factory=>ty_query_param_prop WITH EMPTY KEY.

TYPES:
  BEGIN OF arrangement,
    comm_scenario  TYPE if_com_management=>ty_cscn_id,
    service_id     TYPE if_com_management=>ty_cscn_outb_srv_id,
    comm_system_id TYPE if_com_management=>ty_cs_id,
    property       TYPE property,
  END OF arrangement.

TYPES: BEGIN OF configuration,
         arrangement       TYPE arrangement,
         cloud_destination TYPE string,
         consumption_model TYPE /iwbep/if_cp_runtime_types=>ty_proxy_model_id,
         service_root      TYPE string,
         client            TYPE string,
         language          TYPE sy-langu,
         protocol          TYPE protocol_intern,
       END OF configuration.

 

In principle, you can choose an object (class/interface) or a structure. Here, you should simply decide for yourself whether you also need something like validation or additional methods. In the simplest case, a structure is sufficient to transport the data.

 

Methods

For the implementation, we choose three methods: complete data processing with request and response, processing via request, and manual passing of all values. This gives us the most flexibility later and allows us to control which components we pass to the component. In any case, we must pass the data via a CHANGING parameter so that the information is passed through our code.

METHODS read_odata_by_values
  IMPORTING setting       TYPE setting_by_value
  CHANGING  business_data TYPE ANY TABLE
            !count        TYPE count
  RAISING   zcx_bs_demo_provider_error.

METHODS read_odata_by_request
  IMPORTING setting       TYPE setting_by_request
  CHANGING  business_data TYPE ANY TABLE
            !count        TYPE count
  RAISING   zcx_bs_demo_provider_error.

METHODS read_odata_with_response
  IMPORTING setting       TYPE setting_with_response
  CHANGING  business_data TYPE ANY TABLE
  RAISING   zcx_bs_demo_provider_error.

 

Implementation

In this chapter, we'll look at the implementation of the various scenarios and which method has which task.

 

Hierarchy

Basically, the methods call each other in a hierarchy and pursue specific tasks. Depending on how much of the following steps you want to delegate, you start with a different method:

  • READ_ODATA_WITH_RESPONSE - Calls the next method and fills the result directly into the response.
  • READ_ODATA_BY_REQUEST - Unpacks the request object, deletes missing fields, and adds required fields. Also defines the pagination for the request.
  • READ_ODATA_BY_VALUES - Executes the final query, creates the client, and prepares the HTTP request. The result is then written to the variables.

 

Destination

In the first step, we need a destination for the query. The user currently has the option of specifying a cloud destination (destination service) or a communication arrangement via the configuration.

IF configuration-arrangement IS NOT INITIAL.
  result = cl_http_destination_provider=>create_by_comm_arrangement(
      comm_scenario  = configuration-arrangement-comm_scenario
      service_id     = configuration-arrangement-service_id
      comm_system_id = determine_communication_system( ) ).

ELSEIF configuration-cloud_destination IS NOT INITIAL.
  result = cl_http_destination_provider=>create_by_cloud_destination(
      i_name       = configuration-cloud_destination
      i_authn_mode = if_a4c_cp_service=>service_specific ).

ENDIF.

 

With the Communication Arrangement, we can theoretically store multiple communication systems using the "Additional Properties." To do this, we implement a determination of the correct system via the standard API.

DATA(query) = VALUE if_com_arrangement_factory=>ty_query(
    cscn_id_range = VALUE #( ( sign = 'I' option = 'EQ' low = configuration-arrangement-comm_scenario ) )
    ca_property   = configuration-arrangement-property ).

DATA(arrangement_factory) = cl_com_arrangement_factory=>create_instance( ).
arrangement_factory->query_ca( EXPORTING is_query           = query
                               IMPORTING et_com_arrangement = DATA(systems) ).

RETURN systems[ 1 ]->get_comm_system_id( ).

 

Client

In the next step, we create the appropriate client for our query. Using the configuration, we create the appropriate object for OData v2 or v4. We could also implement other clients using the constants if we needed them.

DATA(http_client) = cl_web_http_client_manager=>create_by_http_destination( destination ).

CASE configuration-protocol.
  WHEN zif_bs_demo_service_prov=>protocol-odata_v2.
    result = /iwbep/cl_cp_factory_remote=>create_v2_remote_proxy(
        is_proxy_model_key       = VALUE #( repository_id       = 'DEFAULT'
                                            proxy_model_id      = configuration-consumption_model
                                            proxy_model_version = '0001' )
        io_http_client           = http_client
        iv_relative_service_root = configuration-service_root ).

  WHEN zif_bs_demo_service_prov=>protocol-odata_v4.
    result = /iwbep/cl_cp_factory_remote=>create_v4_remote_proxy(
        is_proxy_model_key       = VALUE #( repository_id       = 'DEFAULT'
                                            proxy_model_id      = configuration-consumption_model
                                            proxy_model_version = '0001' )
        io_http_client           = http_client
        iv_relative_service_root = configuration-service_root ).

ENDCASE.

 

We should now have all the objects we need to begin implementing the methods.

 

READ_ODATA_WITH_RESPONSE

The method calls the subsequent method RED_ODATA_BY_REQUEST and then passes the result directly to the response. This saves us the assignment, since we first have to check whether the data was actually requested by the request.

IF setting-request->is_total_numb_of_rec_requested( ).
  setting-response->set_total_number_of_records( local_count ).
ENDIF.

IF setting-request->is_data_requested( ).
  setting-response->set_data( business_data ).
ENDIF.

 

READ_ODATA_BY_REQUEST

In the first step, we break down the request and retrieve all the information for our query. To do this, we fill the structure for the next step.

DATA(local_setting) = CORRESPONDING zif_bs_demo_service_prov=>setting_by_value( setting ).
local_setting-filter_condition   = setting-request->get_filter( )->get_as_ranges( ).
local_setting-requested_elements = setting-request->get_requested_elements( ).
local_setting-sort_order         = setting-request->get_sort_elements( ).
local_setting-is_data_requested  = setting-request->is_data_requested( ).
local_setting-is_count_requested = setting-request->is_total_numb_of_rec_requested( ).

 

Before we call the subsequent method READ_ODATA_BY_VALUES, we adjust the corresponding fields that are no longer needed in the query. We also add fields that we might need for other derivations.

LOOP AT setting-delete_fields REFERENCE INTO DATA(field_for_deletion).
  DELETE local_setting-filter_condition WHERE name = field_for_deletion->*.
  DELETE local_setting-requested_elements WHERE table_line = field_for_deletion->*.
  DELETE local_setting-sort_order WHERE element_name = field_for_deletion->*.
ENDLOOP.

LOOP AT setting-read_fields REFERENCE INTO DATA(field_to_read).
  INSERT field_to_read->* INTO TABLE local_setting-requested_elements.
ENDLOOP.

 

Our custom entity may also contain virtual fields or fields that are determined in another way. If the user filters, displays, or sorts the fields in the app, they will be included in the request. If we execute the request against our OData service, these fields would result in an error.

 

READ_ODATA_BY_VALUES

The last method constructs the actual HTTP request and retrieves our data. To do this, we create a destination and an OData request.

DATA(odata_client) = create_client( ).
DATA(odata_request) = odata_client->create_resource_for_entity_set( setting-entity_name )->create_request_for_read( ).

 

In the next step, we prepare the request and add the filter, elements, additional options, and restrictions. For the additional options, we set the client and language as additional options if desired in the settings.

set_filter_for_request( odata_request = odata_request
                        setting       = setting ).
set_elements_for_request( odata_request = odata_request
                          setting       = setting ).
set_options_for_request( odata_request = odata_request
                         setting       = setting ).
set_query_options_for_request( odata_request ).

 

Once we're done, we can execute the query and assign the result to the variables.

DATA(odata_response) = odata_request->execute( ).
IF setting-is_data_requested = abap_true.
  odata_response->get_business_data( IMPORTING et_business_data = business_data ).
ENDIF.
IF setting-is_count_requested = abap_true.
  count = odata_response->get_count( ).
ENDIF.

 

This lays the foundation for its use, and the class will be used in one of the next articles.

 

Complete example

You can find all objects in our GitHub repository in the package ZBS_DEMO_RAP_UTILITY. The source code shown above only shows excerpts of the implementation. In principle, you may have different requirements and should modify the implementation accordingly. For 99% of our reading scenarios, the current implementation is sufficient.

 

Conclusion

Having a generic implementation for reading remote data saves a lot of time and development work. However, you should be aware of the special features associated with software components.


Included topics:
QuickCustom EntityQuery
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 Quick - Ranges and Select Options

Category - ABAP

Ranges and Select Options in ABAP are very similar, yet there are subtle differences in their use in the ABAP OO context. Here we'll look at their modern usage.

05/09/2025

ABAP in Practice - String Processing

Category - ABAP

In this practical example we look at the string processing to determine the CDS names in CamelCase and how you can implement this with ABAP.

10/15/2024

ABAP in Practice - Test Driven Development

Category - ABAP

How does TDD actually work in practice and are there simple examples for learning in ABAP? In this exercise we will look at the practical part.

09/24/2024

ABAP in Practice - Merge data sets

Category - ABAP

How do we merge two different data sets in ABAP, especially with regard to Modern ABAP? A practical task for this topic.

09/17/2024

ABAP in Practice - Modern ABAP

Category - ABAP

In this small task we look at existing classic ABAP source code and try to optimize it according to Modern ABAP.

08/27/2024