Services OData ABAP : Services Web RESTful avec SAP Gateway

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

OData (Open Data Protocol) est le standard pour les APIs RESTful dans SAP. Avec SAP Gateway et le ABAP RESTful Application Programming Model (RAP), des services web modernes peuvent etre crees.

Fondamentaux OData

ConceptDescription
EntityObjet de donnees (par ex. Commande)
Entity SetCollection d’Entities
Navigation PropertyRelation entre Entities
Service DocumentDecrit les ressources disponibles

Methodes HTTP et CRUD

MethodeOperationMethode DPC
GETReadGET_ENTITY, GET_ENTITYSET
POSTCreateCREATE_ENTITY
PUTUpdate (complet)UPDATE_ENTITY
PATCHUpdate (partiel)UPDATE_ENTITY
DELETEDeleteDELETE_ENTITY

Gateway classique (SEGW)

Data Provider Class - Lire une 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.
" Lire la cle depuis la requete
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 est requis'.
ENDIF.
" Lire les donnees depuis la base de donnees
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 = 'Commande non trouvee'.
ENDIF.
" Retourner le resultat
er_entity = ls_order.
ENDMETHOD.

Entity Set avec filtre et pagination

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.
" Lire les options de filtre
lt_filter = io_tech_request_context->get_filter( )->get_filter_select_options( ).
" Parametres de pagination
ls_paging = io_tech_request_context->get_paging( ).
" Creer la condition WHERE dynamique
DATA(lo_filter) = io_tech_request_context->get_filter( ).
DATA(lv_where) = lo_filter->get_filter_string( ).
" Lire les donnees
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.
" Nombre total pour $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.

Creer une 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.
" Lire le corps de la requete
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 est requis'.
ENDIF.
" Generer un nouvel 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.
" Enregistrer les donnees
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 = 'Erreur lors de la creation'.
ENDIF.
ENDMETHOD.

Mettre a jour une 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.
" Lire la cle et les donnees
READ TABLE it_key_tab INTO ls_key WITH KEY name = 'OrderId'.
io_data_provider->read_entry_data( IMPORTING es_data = ls_order ).
" Verifier la commande
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 = 'Commande non trouvee'.
ENDIF.
" Effectuer la mise a jour
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.

Supprimer une 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 = 'Suppression echouee'.
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.
" Charger les commandes avec $expand=Items
SELECT * FROM zorders INTO CORRESPONDING FIELDS OF TABLE lt_orders.
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<ls_order>).
" Charger les positions pour la commande
SELECT * FROM zorderitems
WHERE order_id = <ls_order>-order_id
INTO CORRESPONDING FIELDS OF TABLE lt_items.
" Ajouter les donnees 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.
" Lire la structure deep
io_data_provider->read_entry_data( IMPORTING es_data = ls_order ).
" Creer la commande
INSERT zorders FROM CORRESPONDING #( ls_order ).
" Creer les positions
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'.
" Lire les parametres
DATA(lv_order_id) = VALUE #( it_parameter[ name = 'OrderId' ]-value OPTIONAL ).
" Confirmer la commande
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.

Services OData bases sur RAP (Moderne)

CDS View avec Service Definition

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Commandes"
@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: 'Commandes 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: 'Service Commandes"
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.
" Verification des autorisations
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.
" Lire les commandes
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY order
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
" Mettre a jour le statut
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' ) ).
" Retourner le resultat
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).
" Verifier le client
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 = 'Le client n existe pas' )
) TO failed-order.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Handler de requete OData V4

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.
" Lire le filtre
DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
" Pagination
DATA(lv_top) = io_request->get_paging( )->get_page_size( ).
DATA(lv_skip) = io_request->get_paging( )->get_offset( ).
" Tri
DATA(lt_sort) = io_request->get_sort_elements( ).
" Lire les donnees avec filtre dynamique
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.
" Nombre 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.

Options de requete

OptionExempleDescription
$filter$filter=Status eq 'O'Filtrage
$select$select=OrderId,StatusSelection de champs
$expand$expand=_ItemsCharger les navigations
$orderby$orderby=OrderDate descTri
$top$top=10Limite
$skip$skip=20Offset
$count$count=trueNombre total

Bonnes pratiques

  1. Versionnement : Versions de service pour les breaking changes
  2. Gestion des erreurs : Statuts HTTP parlants et messages clairs
  3. Securite : Implementer les controles d’autorisation
  4. Performance : Utiliser $select, ne charger que les champs necessaires
  5. ETag : Verrouillage optimiste pour les mises a jour
  6. Preferer RAP : Pour les nouveaux developpements, RAP plutot que SEGW

Transactions importantes

TransactionDescription
SEGWGateway Service Builder
/IWFND/MAINT_SERVICEActiver le service
/IWFND/GW_CLIENTGateway Client (Test)
/IWFND/ERROR_LOGJournal des erreurs

Sujets connexes