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
| Konzept | Beschreibung |
|---|---|
| Entity | Datenobjekt (z.B. Bestellung) |
| Entity Set | Sammlung von Entities |
| Navigation Property | Beziehung zwischen Entities |
| Service Document | Beschreibt verfügbare Ressourcen |
HTTP-Methoden und CRUD
| Methode | Operation | DPC-Methode |
|---|---|---|
| GET | Read | GET_ENTITY, GET_ENTITYSET |
| POST | Create | CREATE_ENTITY |
| PUT | Update (komplett) | UPDATE_ENTITY |
| PATCH | Update (partiell) | UPDATE_ENTITY |
| DELETE | Delete | DELETE_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.Navigation Property
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: truedefine 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: truedefine 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 Orderpersistent table zorderslock masterauthorization 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
| Option | Beispiel | Beschreibung |
|---|---|---|
| $filter | $filter=Status eq 'O' | Filterung |
| $select | $select=OrderId,Status | Feldauswahl |
| $expand | $expand=_Items | Navigation laden |
| $orderby | $orderby=OrderDate desc | Sortierung |
| $top | $top=10 | Limit |
| $skip | $skip=20 | Offset |
| $count | $count=true | Gesamtanzahl |
Best Practices
- Versionierung: Service-Versionen für Breaking Changes
- Fehlerbehandlung: Aussagekräftige HTTP-Status und Meldungen
- Security: Berechtigungsprüfungen implementieren
- Performance: $select nutzen, nur benötigte Felder laden
- ETag: Optimistic Locking für Updates
- RAP bevorzugen: Für neue Entwicklungen RAP statt SEGW
Wichtige Transaktionen
| Transaktion | Beschreibung |
|---|---|
| SEGW | Gateway Service Builder |
| /IWFND/MAINT_SERVICE | Service aktivieren |
| /IWFND/GW_CLIENT | Gateway Client (Test) |
| /IWFND/ERROR_LOG | Fehlerprotokoll |
Verwandte Themen
- CDS Views - Core Data Services
- BAPI-Entwicklung - Klassische APIs
- RFC und Destinationen - Remote Calls
- Berechtigungsprüfungen - Security