RAP - Unmanaged (Remote)
Hier erfährst du mehr über das Unmanaged RAP Objekt mit einer Remote Datenquelle und wie du solche Szenarien umsetzen kannst.
Inhaltsverzeichnis
Im ersten Artikel der Reihe haben wir vor allem über die Theorie gesprochen und im zweiten Artikel ein lokales Szenario dargestellt. In diesem Artikel schauen wir uns einmal eine Applikation an, die auf Remote Daten basiert.
Einleitung
Wie sieht es also aus, wenn die Daten nicht im System sind, wie kommen wir an diese heran und wie können wir dazu eine lauffähige Anwendung implementieren? Das gezeigte Beispiel wird dazu eine Custom Entity verwenden und diese mit dem Unmanaged Ansatz verbinden. Als Grundlage verwenden wir den OData Service, den wir bereits in diesem Artikel angebunden und verwendet haben. Dieser unterstützt alle CRUDQ Operationen und kann damit auch leicht in unsere Anwendung implementiert werden.
Custom Entity
Der erste Schritt ist die Anlage der Custom Entity, hierzu können wir als Vorlage den abstrakten CDS View nehmen, der durch die Schnittstelle im System angelegt wurde. Die Custom Entity definiert die Felder und die Datentypen in einer Entität. Da wir hier mit RAP arbeiten wollen, benötigen wir weiterhin eine ROOT Entität, diese können wir ebenfalls schon im View definieren. Da wir auch Daten zur Verfügung stellen wollen, müssen wir auch eine Query Klasse zur Verfügung stellen. Nach der Anlage der Entität erhalten wir das folgende Ergebnis:
@EndUserText.label: 'Custom Entity with Unmanaged'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_CUSTOM_COMPANY_QRY'
define root custom entity ZBS_R_RAPCustomCompanyNames
{
key CompanyName : abap.char( 60 );
Branch : abap.char( 50 );
CompanyDescription : abap.char( 255 );
}
Da wir eine UI Anwendung definieren wollen, müssen wir noch entsprechende UI Annotationen anlegen, um die Oberfläche auszuprägen. Nach Anlage der Annotationen erhalten wir nun die vollständige Custom Entity:
@EndUserText.label: 'Custom Entity with Unmanaged'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_CUSTOM_COMPANY_QRY'
@UI: {
headerInfo: {
typeName: 'Company name',
typeNamePlural: 'Company names',
title: { value: 'CompanyName' }
}
}
define root custom entity ZBS_R_RAPCustomCompanyNames
{
@UI.facet : [
{
id : 'FacetDetailPage',
label : 'General',
type : #IDENTIFICATION_REFERENCE,
targetQualifier: 'DETAIL'
}
]
@UI : {
lineItem : [{ position: 10 }],
selectionField : [ { position: 10 } ],
identification : [{ position: 10, qualifier: 'DETAIL' }]
}
@EndUserText.label: 'Company name'
key CompanyName : abap.char( 60 );
@UI : {
lineItem : [{ position: 20 }],
identification : [{ position: 20, qualifier: 'DETAIL' }]
}
@EndUserText.label: 'Branch'
Branch : abap.char( 50 );
@UI : {
identification : [{ position: 30, qualifier: 'DETAIL' }]
}
@EndUserText.label: 'Description'
@UI.multiLineText: true
CompanyDescription : abap.char( 255 );
}
Hinweis: Verwenden wir eine Custom Entity als Startpunkt unserer Anwendung, können wir keine Datenmodellierung durchführen und somit keine Projektion anlegen. Auch wird für diese Art Entität keine Metadata Extension angeboten, sodass wir alle benötigten Komponenten in die View packen müssen.
Weitere Objekte
Um nun eine ausführbare Anwendung zu erhalten, müssen wir noch die restlichen Objekte implementieren. Dazu legen wir die Query Klasse an, die später unsere Abfragen Richtung Backend weiterleiten soll. Da wir ein Feld als Suchfeld definiert haben, sollten wir auch eine entsprechende Abfragelogik implementieren. Wie bereits im Artikel über die On-Premise Anbindung verwenden wir eine entsprechende Destination, rufen den Proxy auf und wandeln die Anfrage entsprechend um. Die fertige Klasse nun im Überblick:
CLASS zcl_bs_demo_custom_company_qry DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
CONSTANTS c_destination TYPE string VALUE `<destination-service-id>`.
CONSTANTS c_entity TYPE /iwbep/if_cp_runtime_types=>ty_entity_set_name VALUE 'COMPANYNAMES'.
CLASS-METHODS get_proxy
RETURNING VALUE(ro_result) TYPE REF TO /iwbep/if_cp_client_proxy.
PRIVATE SECTION.
TYPES tt_result TYPE STANDARD TABLE OF ZBS_I_RAPCustomEntityCNames WITH EMPTY KEY.
METHODS read_data_by_request
IMPORTING io_request TYPE REF TO if_rap_query_request
EXPORTING et_result TYPE tt_result
ed_count TYPE int8.
ENDCLASS.
CLASS zcl_bs_demo_custom_company_qry IMPLEMENTATION.
METHOD if_rap_query_provider~select.
DATA lt_result TYPE STANDARD TABLE OF ZBS_R_RAPCustomCompanyNames.
read_data_by_request( EXPORTING io_request = io_request
IMPORTING et_result = DATA(lt_company_names)
ed_count = DATA(ld_count) ).
IF io_request->is_total_numb_of_rec_requested( ).
io_response->set_total_number_of_records( ld_count ).
ENDIF.
IF io_request->is_data_requested( ).
lt_result = CORRESPONDING #( lt_company_names ).
io_response->set_data( lt_result ).
ENDIF.
ENDMETHOD.
METHOD get_proxy.
TRY.
DATA(lo_destination) = cl_http_destination_provider=>create_by_cloud_destination(
i_name = c_destination
i_authn_mode = if_a4c_cp_service=>service_specific ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
ro_result = cl_web_odata_client_factory=>create_v2_remote_proxy(
iv_service_definition_name = 'ZBS_DEMO_RAP_ONPREM_ODATA'
io_http_client = lo_client
iv_relative_service_root = '/sap/opu/odata/sap/ZBS_API_COMPANY_NAMES_O2' ).
CATCH cx_root.
ENDTRY.
ENDMETHOD.
METHOD read_data_by_request.
DATA lo_root_filter_node TYPE REF TO /iwbep/if_cp_filter_node.
TRY.
DATA(lo_request) = get_proxy( )->create_resource_for_entity_set( c_entity )->create_request_for_read( ).
DATA(lt_filter_condition) = io_request->get_filter( )->get_as_ranges( ).
DATA(lt_requested_elements) = io_request->get_requested_elements( ).
DATA(lt_sort_elements) = io_request->get_sort_elements( ).
DATA(ld_skip) = io_request->get_paging( )->get_offset( ).
DATA(ld_top) = io_request->get_paging( )->get_page_size( ).
DATA(ld_is_data_requested) = io_request->is_data_requested( ).
DATA(ld_is_count_requested) = io_request->is_total_numb_of_rec_requested( ).
DATA(lo_filter_factory) = lo_request->create_filter_factory( ).
LOOP AT lt_filter_condition INTO DATA(ls_filter_condition).
DATA(lo_filter_node) = lo_filter_factory->create_by_range( iv_property_path = ls_filter_condition-name
it_range = ls_filter_condition-range ).
IF lo_root_filter_node IS INITIAL.
lo_root_filter_node = lo_filter_node.
ELSE.
lo_root_filter_node = lo_root_filter_node->and( lo_filter_node ).
ENDIF.
ENDLOOP.
IF lo_root_filter_node IS NOT INITIAL.
lo_request->set_filter( lo_root_filter_node ).
ENDIF.
IF lt_requested_elements IS NOT INITIAL.
lo_request->set_select_properties( CORRESPONDING #( lt_requested_elements ) ).
ENDIF.
IF lt_sort_elements IS NOT INITIAL.
lo_request->set_orderby( CORRESPONDING #( lt_sort_elements MAPPING property_path = element_name ) ).
ENDIF.
IF ld_is_data_requested = abap_true.
lo_request->set_skip( CONV #( ld_skip ) ).
IF ld_top > 0.
lo_request->set_top( CONV #( ld_top ) ).
ENDIF.
ELSE.
lo_request->request_no_business_data( ).
ENDIF.
IF ld_is_count_requested = abap_true.
lo_request->request_count( ).
ENDIF.
DATA(lo_response) = lo_request->execute( ).
lo_response->get_business_data( IMPORTING et_business_data = et_result ).
ed_count = lo_response->get_count( ).
CATCH cx_root.
ENDTRY.
ENDMETHOD.
ENDCLASS.
Da wir bereits in einem Artikel die Verarbeitung behandelt haben, kann dort der Aufruf und die Funktionsweise nachgelesen werden. Im nächsten Schritt definieren wir die Verhaltensdefinition, um die Funktionen des RAP Objekts zu beschreiben und später ausprägen zu können:
unmanaged implementation in class zbp_bs_demo_company_names unique;
strict ( 1 );
define behavior for ZBS_R_RAPCustomCompanyNames alias CompanyNames
lock master
authorization master ( instance )
{
field ( readonly : update ) CompanyName;
create;
update;
delete;
}
Das Objekt steht auf Unmanaged und wir setzen den Schlüssel auf READONLY, wenn wir die Instanz aktualisieren. Im Anschluss generieren wir die leere Verhaltensimplementierung, damit das Objekt konsistent ist. Damit wir die Anwendung im Fiori Elements Preview aufrufen können, müssen wir noch eine Service Definition und ein Service Binding anlegen. Nach dem "Publish" des Services, in diesem Fall ein OData v2 Service, können wir zum ersten Mal die Anwendung starten.
Ausführung
Wenn wir nun einmal die Anwendung mit einem Klick auf "Go" starten, werden alle Datensätze On-Premise abgerufen und uns angezeigt.
Über den Filter können wir nun die Datenmenge eingrenzen und so zum Beispiel nach der passenden Firma suchen.
Mit einem Klick auf die Position navigieren wir auf die Object-Page bzw. Detailseite und sehen auch den Langtext. Über die Annotation "UI.multiLineText: true" verwandeln wir das einfache Eingabefeld in eine Textbox und können so mehr Text sehen.
Datenverwaltung
Wie bereits beim letzten Unmanaged Szenario für das lokale Szenario, müssen wir auch hier alle Operationen selbst implementieren. Dazu legen wir in der Verhaltensimplementierung ein Puffer Objekt an, um unsere Daten zu sichern:
CLASS lcl_data_buffer DEFINITION.
PUBLIC SECTION.
TYPES tt_data TYPE STANDARD TABLE OF ZBS_R_RAPCustomCompanyNames WITH EMPTY KEY.
CLASS-DATA gt_create TYPE tt_data.
CLASS-DATA gt_update TYPE tt_data.
CLASS-DATA gt_delete TYPE tt_data.
ENDCLASS.
Bei den CRUD Operationen müssen wir nun entsprechend den Puffer befüllen, dabei müssen wir beim Update die Daten noch einmal Remote nachlesen oder wir implementieren die READ Methode, um über EML an die Daten zu kommen. In der SAVE Methode müssen wir uns zum Abschluss nun um die schreibenden Zugriff Richtung On-Premise System kümmern. Die fertige Implementierung könnte wir folgt aussehen:
DATA ls_remote_create TYPE zbs_rap_companynames.
DATA ls_remote_update TYPE zbs_rap_companynames.
LOOP AT lcl_data_buffer=>gt_create INTO DATA(ls_create).
ls_remote_create = CORRESPONDING #( ls_create ).
DATA(lo_request_create) = zcl_bs_demo_custom_company_qry=>get_proxy( )->create_resource_for_entity_set(
zcl_bs_demo_custom_company_qry=>c_entity )->create_request_for_create( ).
lo_request_create->set_business_data( ls_remote_create ).
lo_request_create->execute( ).
ENDLOOP.
LOOP AT lcl_data_buffer=>gt_update INTO DATA(ls_update).
ls_remote_update = CORRESPONDING #( ls_update ).
DATA(ls_key) = VALUE zbs_rap_companynames( companyname = ls_remote_update-CompanyName ).
DATA(lo_request_update) = zcl_bs_demo_custom_company_qry=>get_proxy( )->create_resource_for_entity_set(
zcl_bs_demo_custom_company_qry=>c_entity
)->navigate_with_key( ls_key
)->create_request_for_update( /iwbep/if_cp_request_update=>gcs_update_semantic-put ).
lo_request_update->set_business_data( ls_remote_update ).
lo_request_update->execute( ).
ENDLOOP.
LOOP AT lcl_data_buffer=>gt_delete INTO DATA(ls_delete).
DATA(ls_key_delete) = VALUE zbs_rap_companynames( companyname = ls_delete-CompanyName ).
DATA(lo_request_delete) = zcl_bs_demo_custom_company_qry=>get_proxy( )->create_resource_for_entity_set(
zcl_bs_demo_custom_company_qry=>c_entity
)->navigate_with_key( ls_key_delete
)->create_request_for_delete( ).
lo_request_delete->execute( ).
ENDLOOP.
CLEAR: lcl_data_buffer=>gt_create, lcl_data_buffer=>gt_update, lcl_data_buffer=>gt_delete.
Hinweis: Bei der Implementierung haben wir bewusst auf die Fehlerbehandlung verzichtet, um die reinen Zugriffe und Aufrufe abzubilden. Eine saubere Behandlung von Ausnahmen und Fehlern beim Aufruf sollte unter allen Umständen implementiert werden.
Komplettes Beispiel
Das komplette Beispiel findest du wie immer in unserem GitHub Repository, über den Commit gelangst du zu den durchgeführten Änderungen und neuen Objekten.
Fazit
Die Implementierung eines Unmanaged Szenarios über eine Custom Entity ist etwas anders, als ein normales RAP Objekt aufzubauen. Durch diesen und die verlinkten Artikel sollte das für dich aber kein Problem mehr sein. Dieser Ansatz ist ebenso die Grundlage für hybride Anwendungen auf dem ABAP Environment.