ABAP OData Services: RESTful Web Services mit SAP Gateway

kategorie
ABAP-Statements
Veröffentlicht
autor
Johannes

OData (Open Data Protocol) ist der Standard für RESTful APIs in SAP. Mit dem SAP Gateway und dem ABAP RESTful Application Programming Model (RAP) können moderne Web-Services erstellt werden.

OData-Grundlagen

KonzeptBeschreibung
EntityDatenobjekt (z.B. Bestellung)
Entity SetSammlung von Entities
Navigation PropertyBeziehung zwischen Entities
Service DocumentBeschreibt verfügbare Ressourcen

HTTP-Methoden und CRUD

MethodeOperationDPC-Methode
GETReadGET_ENTITY, GET_ENTITYSET
POSTCreateCREATE_ENTITY
PUTUpdate (komplett)UPDATE_ENTITY
PATCHUpdate (partiell)UPDATE_ENTITY
DELETEDeleteDELETE_ENTITY

Klassisches Gateway (SEGW)

Data Provider Class - Entity lesen

METHOD /iwbep/if_mgw_appl_srv_runtime~get_entity.
DATA: ls_key TYPE /iwbep/s_mgw_name_value_pair,
ls_order TYPE zcl_zorders_mpc=>ts_order.
" Schlüssel aus Request lesen
READ TABLE it_key_tab INTO ls_key WITH KEY name = 'OrderId'.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
EXPORTING
textid = /iwbep/cx_mgw_busi_exception=>business_error
message = 'OrderId ist erforderlich'.
ENDIF.
" Daten aus Datenbank lesen
SELECT SINGLE * FROM zorders INTO CORRESPONDING FIELDS OF ls_order
WHERE order_id = ls_key-value.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
EXPORTING
textid = /iwbep/cx_mgw_busi_exception=>business_error_with_nav
message = 'Bestellung nicht gefunden'.
ENDIF.
" Ergebnis zurückgeben
er_entity = ls_order.
ENDMETHOD.

Entity Set mit Filter und Paging

METHOD /iwbep/if_mgw_appl_srv_runtime~get_entityset.
DATA: lt_orders TYPE zcl_zorders_mpc=>tt_order,
lt_filter TYPE /iwbep/t_mgw_select_option,
ls_paging TYPE /iwbep/s_mgw_paging.
" Filter-Optionen auslesen
lt_filter = io_tech_request_context->get_filter( )->get_filter_select_options( ).
" Paging-Parameter
ls_paging = io_tech_request_context->get_paging( ).
" Dynamische WHERE-Bedingung erstellen
DATA(lo_filter) = io_tech_request_context->get_filter( ).
DATA(lv_where) = lo_filter->get_filter_string( ).
" Daten lesen
SELECT * FROM zorders
WHERE (lv_where)
ORDER BY order_id
INTO CORRESPONDING FIELDS OF TABLE lt_orders
UP TO ls_paging-top ROWS
OFFSET ls_paging-skip.
" Gesamtzahl für $inlinecount
IF io_tech_request_context->has_inlinecount( ) = abap_true.
SELECT COUNT(*) FROM zorders WHERE (lv_where)
INTO @DATA(lv_count).
es_response_context-inlinecount = lv_count.
ENDIF.
et_entityset = lt_orders.
ENDMETHOD.

Entity erstellen

METHOD /iwbep/if_mgw_appl_srv_runtime~create_entity.
DATA: ls_order_in TYPE zcl_zorders_mpc=>ts_order,
ls_order_out TYPE zcl_zorders_mpc=>ts_order.
" Request-Body lesen
io_data_provider->read_entry_data( IMPORTING es_data = ls_order_in ).
" Validierung
IF ls_order_in-customer_id IS INITIAL.
RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
EXPORTING
message = 'Customer ID ist erforderlich'.
ENDIF.
" Neue ID generieren
SELECT MAX( order_id ) FROM zorders INTO @DATA(lv_max_id).
ls_order_in-order_id = lv_max_id + 1.
ls_order_in-created_at = sy-datum.
ls_order_in-created_by = sy-uname.
" Daten speichern
INSERT zorders FROM ls_order_in.
IF sy-subrc = 0.
ls_order_out = ls_order_in.
er_entity = ls_order_out.
ELSE.
RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
EXPORTING
message = 'Fehler beim Erstellen'.
ENDIF.
ENDMETHOD.

Entity aktualisieren

METHOD /iwbep/if_mgw_appl_srv_runtime~update_entity.
DATA: ls_order TYPE zcl_zorders_mpc=>ts_order,
ls_key TYPE /iwbep/s_mgw_name_value_pair.
" Schlüssel und Daten lesen
READ TABLE it_key_tab INTO ls_key WITH KEY name = 'OrderId'.
io_data_provider->read_entry_data( IMPORTING es_data = ls_order ).
" Bestellung prüfen
SELECT SINGLE * FROM zorders INTO @DATA(ls_existing)
WHERE order_id = @ls_key-value.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
EXPORTING
message = 'Bestellung nicht gefunden'.
ENDIF.
" Update durchführen
ls_order-order_id = ls_key-value.
ls_order-changed_at = sy-datum.
ls_order-changed_by = sy-uname.
UPDATE zorders FROM ls_order.
er_entity = ls_order.
ENDMETHOD.

Entity löschen

METHOD /iwbep/if_mgw_appl_srv_runtime~delete_entity.
DATA: ls_key TYPE /iwbep/s_mgw_name_value_pair.
READ TABLE it_key_tab INTO ls_key WITH KEY name = 'OrderId'.
DELETE FROM zorders WHERE order_id = ls_key-value.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
EXPORTING
message = 'Löschen fehlgeschlagen'.
ENDIF.
ENDMETHOD.
METHOD /iwbep/if_mgw_appl_srv_runtime~get_expanded_entityset.
DATA: lt_orders TYPE zcl_zorders_mpc=>tt_order,
lt_items TYPE zcl_zorders_mpc=>tt_orderitem.
" Bestellungen mit $expand=Items laden
SELECT * FROM zorders INTO CORRESPONDING FIELDS OF TABLE lt_orders.
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<ls_order>).
" Positionen zur Bestellung laden
SELECT * FROM zorderitems
WHERE order_id = <ls_order>-order_id
INTO CORRESPONDING FIELDS OF TABLE lt_items.
" Expand-Daten hinzufügen
APPEND VALUE #(
key = <ls_order>-order_id
data = lt_items
) TO et_expanded_tech_clauses.
ENDLOOP.
et_entityset = lt_orders.
ENDMETHOD.

Deep Insert

METHOD /iwbep/if_mgw_appl_srv_runtime~create_deep_entity.
DATA: ls_order TYPE zcl_zorders_mpc=>ts_order_deep.
" Deep Structure lesen
io_data_provider->read_entry_data( IMPORTING es_data = ls_order ).
" Bestellung anlegen
INSERT zorders FROM CORRESPONDING #( ls_order ).
" Positionen anlegen
LOOP AT ls_order-items ASSIGNING FIELD-SYMBOL(<ls_item>).
<ls_item>-order_id = ls_order-order_id.
INSERT zorderitems FROM CORRESPONDING #( <ls_item> ).
ENDLOOP.
er_deep_entity = ls_order.
ENDMETHOD.

Function Import

METHOD /iwbep/if_mgw_appl_srv_runtime~execute_action.
CASE iv_action_name.
WHEN 'ConfirmOrder'.
" Parameter lesen
DATA(lv_order_id) = VALUE #( it_parameter[ name = 'OrderId' ]-value OPTIONAL ).
" Bestellung bestätigen
UPDATE zorders SET status = 'C' WHERE order_id = lv_order_id.
IF sy-subrc = 0.
er_data = VALUE zcl_zorders_mpc=>ts_result( success = abap_true ).
ENDIF.
WHEN 'GetOrderStatistics'.
SELECT COUNT(*) AS total,
SUM( CASE WHEN status = 'O' THEN 1 ELSE 0 END ) AS open,
SUM( CASE WHEN status = 'C' THEN 1 ELSE 0 END ) AS closed
FROM zorders INTO @DATA(ls_stats).
er_data = ls_stats.
ENDCASE.
ENDMETHOD.

RAP-basierte OData Services (Modern)

CDS View mit Service Definition

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Bestellungen'
@Metadata.allowExtensions: true
define root view entity ZI_Order
as select from zorders
composition [0..*] of ZI_OrderItem as _Items
{
key order_id as OrderId,
customer_id as CustomerId,
order_date as OrderDate,
status as Status,
total_amount as TotalAmount,
currency as Currency,
@Semantics.user.createdBy: true
created_by as CreatedBy,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
_Items
}

Projection View

@EndUserText.label: 'Bestellungen Projection'
@AccessControl.authorizationCheck: #CHECK
@Metadata.allowExtensions: true
define root view entity ZC_Order
provider contract transactional_query
as projection on ZI_Order
{
key OrderId,
CustomerId,
OrderDate,
Status,
@Semantics.amount.currencyCode: 'Currency'
TotalAmount,
Currency,
_Items : redirected to composition child ZC_OrderItem
}

Service Definition

@EndUserText.label: 'Order Service'
define service ZSB_Order {
expose ZC_Order as Orders;
expose ZC_OrderItem as OrderItems;
}

Behavior Definition

managed implementation in class zbp_i_order unique;
strict ( 2 );
define behavior for ZI_Order alias Order
persistent table zorders
lock master
authorization master ( instance )
{
field ( readonly ) OrderId, CreatedBy, CreatedAt;
field ( mandatory ) CustomerId;
create;
update;
delete;
action confirmOrder result [1] $self;
determination setDefaults on modify { create; }
validation validateCustomer on save { field CustomerId; }
association _Items { create; }
}

Behavior Implementation

CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS:
get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations
RESULT result,
confirmOrder FOR MODIFY
IMPORTING keys FOR ACTION order~confirmOrder
RESULT result,
setDefaults FOR DETERMINE ON MODIFY
IMPORTING keys FOR order~setDefaults,
validateCustomer FOR VALIDATE ON SAVE
IMPORTING keys FOR order~validateCustomer.
ENDCLASS.
CLASS lhc_order IMPLEMENTATION.
METHOD get_instance_authorizations.
" Berechtigungsprüfung
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
APPEND VALUE #(
%tky = <key>-%tky
%op-%update = COND #(
WHEN has_authority( ) = abap_true
THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized )
) TO result.
ENDLOOP.
ENDMETHOD.
METHOD confirmOrder.
" Bestellungen lesen
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY order
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
" Status aktualisieren
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY order
UPDATE FIELDS ( Status )
WITH VALUE #( FOR order IN lt_orders
( %tky = order-%tky
Status = 'C' ) ).
" Ergebnis zurückgeben
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY order
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_result).
result = VALUE #( FOR order IN lt_result
( %tky = order-%tky
%param = order ) ).
ENDMETHOD.
METHOD setDefaults.
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY order
FIELDS ( OrderDate Status )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY order
UPDATE FIELDS ( OrderDate Status )
WITH VALUE #( FOR order IN lt_orders
WHERE ( OrderDate IS INITIAL )
( %tky = order-%tky
OrderDate = cl_abap_context_info=>get_system_date( )
Status = 'O' ) ).
ENDMETHOD.
METHOD validateCustomer.
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY order
FIELDS ( CustomerId )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order).
" Kunde prüfen
SELECT SINGLE @abap_true FROM zcustomers
WHERE customer_id = @ls_order-CustomerId
INTO @DATA(lv_exists).
IF lv_exists = abap_false.
APPEND VALUE #(
%tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Kunde existiert nicht' )
) TO failed-order.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

OData V4 Query Handler

CLASS zcl_order_query DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
ENDCLASS.
CLASS zcl_order_query IMPLEMENTATION.
METHOD if_rap_query_provider~select.
DATA: lt_orders TYPE TABLE OF zi_order.
" Filter auslesen
DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
" Paging
DATA(lv_top) = io_request->get_paging( )->get_page_size( ).
DATA(lv_skip) = io_request->get_paging( )->get_offset( ).
" Sortierung
DATA(lt_sort) = io_request->get_sort_elements( ).
" Daten lesen mit dynamischem Filter
SELECT * FROM zi_order
WHERE customer_id IN @( VALUE #( FOR filter IN lt_filter
WHERE ( name = 'CUSTOMERID' )
( filter-range ) ) )
ORDER BY order_id
INTO TABLE @lt_orders
UP TO @lv_top ROWS
OFFSET @lv_skip.
" Gesamtzahl
IF io_request->is_total_numb_of_rec_requested( ).
SELECT COUNT(*) FROM zi_order INTO @DATA(lv_count).
io_response->set_total_number_of_records( lv_count ).
ENDIF.
io_response->set_data( lt_orders ).
ENDMETHOD.
ENDCLASS.

Query-Optionen

OptionBeispielBeschreibung
$filter$filter=Status eq 'O'Filterung
$select$select=OrderId,StatusFeldauswahl
$expand$expand=_ItemsNavigation laden
$orderby$orderby=OrderDate descSortierung
$top$top=10Limit
$skip$skip=20Offset
$count$count=trueGesamtanzahl

Best Practices

  1. Versionierung: Service-Versionen für Breaking Changes
  2. Fehlerbehandlung: Aussagekräftige HTTP-Status und Meldungen
  3. Security: Berechtigungsprüfungen implementieren
  4. Performance: $select nutzen, nur benötigte Felder laden
  5. ETag: Optimistic Locking für Updates
  6. RAP bevorzugen: Für neue Entwicklungen RAP statt SEGW

Wichtige Transaktionen

TransaktionBeschreibung
SEGWGateway Service Builder
/IWFND/MAINT_SERVICEService aktivieren
/IWFND/GW_CLIENTGateway Client (Test)
/IWFND/ERROR_LOGFehlerprotokoll

Verwandte Themen