Servicios OData en ABAP: Servicios web RESTful con SAP Gateway

Kategorie
ABAP-Statements
Veröffentlicht
Autor
Johannes

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

ConceptoDescripcion
EntityObjeto de datos (ej. Pedido)
Entity SetColeccion de Entities
Navigation PropertyRelacion entre Entities
Service DocumentDescribe los recursos disponibles

Metodos HTTP y CRUD

MetodoOperacionMetodo DPC
GETReadGET_ENTITY, GET_ENTITYSET
POSTCreateCREATE_ENTITY
PUTUpdate (completo)UPDATE_ENTITY
PATCHUpdate (parcial)UPDATE_ENTITY
DELETEDeleteDELETE_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.
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: 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: 'Proyeccion de Pedidos'
@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.
" 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

OpcionEjemploDescripcion
$filter$filter=Status eq 'O'Filtrado
$select$select=OrderId,StatusSeleccion de campos
$expand$expand=_ItemsCargar navegacion
$orderby$orderby=OrderDate descOrdenamiento
$top$top=10Limite
$skip$skip=20Offset
$count$count=trueTotal

Mejores practicas

  1. Versionamiento: Versiones de servicio para breaking changes
  2. Manejo de errores: Codigos HTTP y mensajes descriptivos
  3. Seguridad: Implementar verificaciones de autorizacion
  4. Rendimiento: Usar $select, cargar solo campos necesarios
  5. ETag: Bloqueo optimista para updates
  6. Preferir RAP: Para nuevos desarrollos RAP en lugar de SEGW

Transacciones importantes

TransaccionDescripcion
SEGWGateway Service Builder
/IWFND/MAINT_SERVICEActivar servicio
/IWFND/GW_CLIENTGateway Client (Prueba)
/IWFND/ERROR_LOGProtocolo de errores

Temas relacionados