OData (Open Data Protocol) is the standard for RESTful APIs in SAP. With the SAP Gateway and the ABAP RESTful Application Programming Model (RAP), modern web services can be created.
OData Fundamentals
| Concept | Description |
|---|---|
| Entity | Data object (e.g., Order) |
| Entity Set | Collection of Entities |
| Navigation Property | Relationship between Entities |
| Service Document | Describes available resources |
HTTP Methods and CRUD
| Method | Operation | DPC Method |
|---|---|---|
| GET | Read | GET_ENTITY, GET_ENTITYSET |
| POST | Create | CREATE_ENTITY |
| PUT | Update (complete) | UPDATE_ENTITY |
| PATCH | Update (partial) | UPDATE_ENTITY |
| DELETE | Delete | DELETE_ENTITY |
Classic Gateway (SEGW)
Data Provider Class - Reading an Entity
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.
" Read key from request 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 is required'. ENDIF.
" Read data from database 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 = 'Order not found'. ENDIF.
" Return result er_entity = ls_order.
ENDMETHOD.Entity Set with Filter and 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.
" Read filter options lt_filter = io_tech_request_context->get_filter( )->get_filter_select_options( ).
" Paging parameters ls_paging = io_tech_request_context->get_paging( ).
" Create dynamic WHERE condition DATA(lo_filter) = io_tech_request_context->get_filter( ). DATA(lv_where) = lo_filter->get_filter_string( ).
" Read data 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.
" Total count for $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.Creating an Entity
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.
" Read request body io_data_provider->read_entry_data( IMPORTING es_data = ls_order_in ).
" Validation IF ls_order_in-customer_id IS INITIAL. RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception EXPORTING message = 'Customer ID is required'. ENDIF.
" Generate new ID 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.
" Save data 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 = 'Error creating entity'. ENDIF.
ENDMETHOD.Updating an Entity
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.
" Read key and data READ TABLE it_key_tab INTO ls_key WITH KEY name = 'OrderId'. io_data_provider->read_entry_data( IMPORTING es_data = ls_order ).
" Check order exists 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 = 'Order not found'. ENDIF.
" Perform update 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.Deleting an Entity
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 = 'Delete failed'. 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.
" Load orders with $expand=Items SELECT * FROM zorders INTO CORRESPONDING FIELDS OF TABLE lt_orders.
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<ls_order>). " Load items for the order SELECT * FROM zorderitems WHERE order_id = <ls_order>-order_id INTO CORRESPONDING FIELDS OF TABLE lt_items.
" Add expand data 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.
" Read deep structure io_data_provider->read_entry_data( IMPORTING es_data = ls_order ).
" Create order INSERT zorders FROM CORRESPONDING #( ls_order ).
" Create items 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'. " Read parameter DATA(lv_order_id) = VALUE #( it_parameter[ name = 'OrderId' ]-value OPTIONAL ).
" Confirm order 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-based OData Services (Modern)
CDS View with Service Definition
@AbapCatalog.viewEnhancementCategory: [#NONE]@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Orders'@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: 'Orders 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. " Authorization check 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. " Read orders READ ENTITIES OF zi_order IN LOCAL MODE ENTITY order FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
" Update status 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' ) ).
" Return result 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). " Check customer exists 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 = 'Customer does not exist' ) ) 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.
" Read filter 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( ).
" Sorting DATA(lt_sort) = io_request->get_sort_elements( ).
" Read data with dynamic 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.
" Total count 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 Options
| Option | Example | Description |
|---|---|---|
| $filter | $filter=Status eq 'O' | Filtering |
| $select | $select=OrderId,Status | Field selection |
| $expand | $expand=_Items | Load navigation |
| $orderby | $orderby=OrderDate desc | Sorting |
| $top | $top=10 | Limit |
| $skip | $skip=20 | Offset |
| $count | $count=true | Total count |
Best Practices
- Versioning: Use service versions for breaking changes
- Error Handling: Provide meaningful HTTP status codes and messages
- Security: Implement authorization checks
- Performance: Use $select, load only required fields
- ETag: Use optimistic locking for updates
- Prefer RAP: Use RAP instead of SEGW for new developments
Important Transactions
| Transaction | Description |
|---|---|
| SEGW | Gateway Service Builder |
| /IWFND/MAINT_SERVICE | Activate service |
| /IWFND/GW_CLIENT | Gateway Client (Test) |
| /IWFND/ERROR_LOG | Error log |
Related Topics
- CDS Views - Core Data Services
- BAPI Development - Classic APIs
- RFC and Destinations - Remote Calls
- Authorization Checks - Security