OData (Open Data Protocol) es el estandar para APIs RESTful en SAP. Con SAP Gateway y el ABAP RESTful Application Programming Model (RAP) se pueden crear servicios web modernos.
Fundamentos de OData
| Concepto | Descripcion |
|---|---|
| Entity | Objeto de datos (ej. Pedido) |
| Entity Set | Coleccion de Entities |
| Navigation Property | Relacion entre Entities |
| Service Document | Describe los recursos disponibles |
Metodos HTTP y CRUD
| Metodo | Operacion | Metodo DPC |
|---|---|---|
| GET | Read | GET_ENTITY, GET_ENTITYSET |
| POST | Create | CREATE_ENTITY |
| PUT | Update (completo) | UPDATE_ENTITY |
| PATCH | Update (parcial) | UPDATE_ENTITY |
| DELETE | Delete | DELETE_ENTITY |
Gateway clasico (SEGW)
Data Provider Class - Leer 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.
" Leer clave del 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 es requerido'. ENDIF.
" Leer datos de base de datos 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 = 'Pedido no encontrado'. ENDIF.
" Devolver resultado er_entity = ls_order.
ENDMETHOD.Entity Set con filtro y paginacion
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.
" Leer opciones de filtro lt_filter = io_tech_request_context->get_filter( )->get_filter_select_options( ).
" Parametros de paginacion ls_paging = io_tech_request_context->get_paging( ).
" Crear condicion WHERE dinamica DATA(lo_filter) = io_tech_request_context->get_filter( ). DATA(lv_where) = lo_filter->get_filter_string( ).
" Leer datos 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 para $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.Crear 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.
" Leer body del request io_data_provider->read_entry_data( IMPORTING es_data = ls_order_in ).
" Validacion IF ls_order_in-customer_id IS INITIAL. RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception EXPORTING message = 'Customer ID es requerido'. ENDIF.
" Generar nuevo 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.
" Guardar datos 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 al crear'. ENDIF.
ENDMETHOD.Actualizar 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.
" Leer clave y datos READ TABLE it_key_tab INTO ls_key WITH KEY name = 'OrderId'. io_data_provider->read_entry_data( IMPORTING es_data = ls_order ).
" Verificar pedido 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 = 'Pedido no encontrado'. ENDIF.
" Ejecutar 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.Eliminar 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 = 'Eliminacion fallida'. 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.
" Cargar pedidos con $expand=Items SELECT * FROM zorders INTO CORRESPONDING FIELDS OF TABLE lt_orders.
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<ls_order>). " Cargar posiciones del pedido SELECT * FROM zorderitems WHERE order_id = <ls_order>-order_id INTO CORRESPONDING FIELDS OF TABLE lt_items.
" Agregar datos de expand 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.
" Leer estructura profunda io_data_provider->read_entry_data( IMPORTING es_data = ls_order ).
" Crear pedido INSERT zorders FROM CORRESPONDING #( ls_order ).
" Crear posiciones 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'. " Leer parametros DATA(lv_order_id) = VALUE #( it_parameter[ name = 'OrderId' ]-value OPTIONAL ).
" Confirmar pedido 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.Servicios OData basados en RAP (Moderno)
CDS View con Service Definition
@AbapCatalog.viewEnhancementCategory: [#NONE]@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Pedidos'@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: 'Proyeccion de Pedidos'@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. " Verificacion de autorizacion 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. " Leer pedidos READ ENTITIES OF zi_order IN LOCAL MODE ENTITY order FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
" Actualizar estado 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' ) ).
" Devolver resultado 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). " Verificar cliente 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 = 'Cliente no existe' ) ) 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.
" Leer filtro DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
" Paginacion DATA(lv_top) = io_request->get_paging( )->get_page_size( ). DATA(lv_skip) = io_request->get_paging( )->get_offset( ).
" Ordenamiento DATA(lt_sort) = io_request->get_sort_elements( ).
" Leer datos con filtro dinamico 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 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.Opciones de Query
| Opcion | Ejemplo | Descripcion |
|---|---|---|
| $filter | $filter=Status eq 'O' | Filtrado |
| $select | $select=OrderId,Status | Seleccion de campos |
| $expand | $expand=_Items | Cargar navegacion |
| $orderby | $orderby=OrderDate desc | Ordenamiento |
| $top | $top=10 | Limite |
| $skip | $skip=20 | Offset |
| $count | $count=true | Total |
Mejores practicas
- Versionamiento: Versiones de servicio para breaking changes
- Manejo de errores: Codigos HTTP y mensajes descriptivos
- Seguridad: Implementar verificaciones de autorizacion
- Rendimiento: Usar $select, cargar solo campos necesarios
- ETag: Bloqueo optimista para updates
- Preferir RAP: Para nuevos desarrollos RAP en lugar de SEGW
Transacciones importantes
| Transaccion | Descripcion |
|---|---|
| SEGW | Gateway Service Builder |
| /IWFND/MAINT_SERVICE | Activar servicio |
| /IWFND/GW_CLIENT | Gateway Client (Prueba) |
| /IWFND/ERROR_LOG | Protocolo de errores |
Temas relacionados
- CDS Views - Core Data Services
- Desarrollo BAPI - APIs clasicas
- RFC y Destinos - Llamadas remotas
- Verificaciones de autorizacion - Seguridad