Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
laszlo_kajan2
Active Participant

Executive summary


Which approach to choose for bringing out OData services to expose entities?

  1. Do the Read and Query operations need coding that cannot be expressed in a CDS view, for example because BAPI (function module) calls are needed?

    1. Yes: Use the (traditional) code-based implementation approach, you cannot use SADL.

      1. Is the data volume per request small?

        1. Yes: Map OData operations to RFC calls.
          This has the advantage of simplifying, or eliminating the need for, middleware code.

        2. No: Implement operations by redefining methods in _DPC_EXT







  2. Read and Query operations can be expressed as CDS views.
    Is the data stored in existing tables?

    1. Yes: Is the data updated via existing APIs? (e.g. BAPIs?)

      1. Yes: Use a referenced or mapped data source, implement updates in _DPC_EXT

      2. No: Are the entities of the CDS view data source to be added to an existing OData service?

        1. Yes: Use a referenced or mapped data source

        2. No: Use CDS-BOPF-OData service exposure with @OData.publish: true







  3. The data is not stored in existing tables. New tables are to be created (SE11).
    Are the entities of the CDS view data source to be added to an existing OData service?

    1. Yes: Use a referenced or mapped data source

    2. No: Use CDS-BOPF-OData service exposure with @OData.publish: true



  4. Are you to use a referenced or mapped data source?

    1. Yes:
      Are there pre-existing properties, associations and actions (i.e. function imports) to be mapped to the data source?
      Or are there navigation properties to be defined between CDS- and non-CDS-based entities?
      Or is there a need to control the mapping of the properties, associations and actions to the referenced data source, e.g. set property EDM types?

      1. Yes: Map the implementation of the OData Service to a SADL model using the mapping editor in SEGW

      2. No: Use a referenced CDS view data source







Author and motivation

Laszlo Kajan is a full stack Fiori/SAPUI5 expert, present on the SAPUI5 field since 2015.

The motivation behind this blog post is to provide a tutorial for back-end developers working in Fiori/SAPUI5 development teams wishing to move forward from pure _EXT-class-method-redefinition-based OData service implementation to the 'ABAP Programming Model for SAP Fiori', including CDS views and BOPF. Guidance for choosing the right implementation method - extracted from blog posts and documentation - is provided in the 'Executive summary'.

Part 1 – OData service with existing tables, DDIC data source, referenced CDS view data source and custom action


Let’s assume that you have an existing custom OData service, which is to be extended with a referenced CDS view data source, and a custom action (i.e. function import). All tables already exist.

DDIC data source


Create the ‘existing’ OData service:

  1. SEGW, create new project ‘Zx_SADL_PART_1’, type 'Service with SAP Annotations'

  2. Add entity type ‘Customers’

    1. Right click ‘Data Model’ / Import / DDIC Structure

      1. Name = Customers

      2. ABAP Structure = SCUSTOM

      3. Select properties = [ID, NAME, CITY, EMAIL]

      4. Key = ID



    2. Optional: change type of property ‘Id’ to ‘Edm.Decimal’ with precision = 8 and ignore the warning/error



  3. Map implementation of CustomerSet to DDIC data source ‘SCUSTOM’

    1. Right click service implementation / CustomerSet / Map to Data Source / Type = Business Entity / Launch value help on ‘Name’ (F4)

    2. SADL Model Type = 'DDIC', SADL Model = 'SCUSTOM'



  4. Generate, register and test the OData service

    1. /sap/opu/odata/sap/Zx_SADL_PART_1_SRV/CustomersSet?$filter=Email eq ’’&$orderby=Id desc&$top=20

    2. Optional: try to update or create an entity, and notice how the response is ‘The requested service is not supported by entity ~SCUSTOM’




CDS interface view


Extend the existing OData service with a referenced CDS view data source.

Create the base CDS interface view:

  1. Launch Eclipse on perspective ABAP, connect to your back-end system

  2. Create interface (aka. base) view for carrier (i.e. airline) data

    1. Right click project / New / Other... / Data Definition

    2. Name = ‘Zx_I_SADL_PART_1_CARR’, Description = ‘CARR interface view for Zx_SADL’

      1. Note: ‘_I_’ is for interface view



    3. Template = ‘Define View’

    4. Change to:
      @AbapCatalog.sqlViewName: 'zx_i_carr1'
      @AbapCatalog.compiler.compareFilter: true
      @AccessControl.authorizationCheck: #NOT_REQUIRED
      @EndUserText.label: 'CARR interface view for Zx_SADL'
      @VDM.viewType: #BASIC
      define view Zx_I_SADL_PART_1_CARR
      as select from scarr as ca
      {
      key ca.carrid,
      ca.carrname,
      ca.currcode,
      ca.url
      }


    5. Activate



  3. Create interface view for flight data

    1. New Data Definition, Name = ‘Zx_I_SADL_PART_1_FLI’, Description = ‘FLIGHT interface view for Zx_SADL’, template = ‘Define View’

    2. Change to:
      @AbapCatalog.sqlViewName: 'zx_i_fli1'
      @AbapCatalog.compiler.compareFilter: true
      @AccessControl.authorizationCheck: #NOT_REQUIRED
      @EndUserText.label: 'FLIGHT interface view for Zx_SADL'
      @VDM.viewType: #BASIC
      define view ZX_I_SADL_PART_1_FLI
      as select from sflight as fl
      {
      key fl.carrid,
      key fl.connid,
      key fl.fldate,
      fl.planetype,
      fl.price,
      fl.currency,
      fl.seatsmax
      }​


    3. Activate



  4. Create associations between carriers and flights

    1. Change ‘Zx_I_SADL_PART_1_CARR’ to:
      define view Zx_I_SADL_PART_1_CARR
      as select from scarr as ca
      association [0..*] to Zx_I_SADL_PART_1_FLI as _fl on $projection.carrid = _fl.carrid
      {
      key ca.carrid,
      ca.carrname,
      ca.url,
      /* Associations */
      _fl
      }​


    2. Change ‘Zx_I_SADL_PART_1_FLI’ to:
      as select from sflight as fl
      association [1..1] to Zx_I_SADL_PART_1_CARR as _ca on $projection.carrid = _ca.carrid
      {
      […]
      fl.seatsmax,
      /* Associations */
      _ca
      }​


    3. Visualize the entities

      1. Right click DDL data definition / Open With / Graphical Editor






Business object annotations (BOPF)


Add business object (BO) @ObjectModel annotations for BOPF (Adding Business Object Semantics to Data Model😞

  1. Change ‘Zx_I_SADL_PART_1_CARR’ to:
    @EndUserText.label: 'CARR interface view for Zx_SADL'
    @ObjectModel: {
    compositionRoot: true,
    modelCategory: #BUSINESS_OBJECT,
    representativeKey: 'carrid',
    semanticKey: [ 'carrid' ],
    transactionalProcessingEnabled: true,
    writeActivePersistence: 'scarr',

    createEnabled: false,
    updateEnabled: true,
    deleteEnabled: false
    }
    […]
    ca.url,
    /* Associations */
    @ObjectModel.association.type: [#TO_COMPOSITION_CHILD]
    _fl
    }​


  2. Change ‘Zx_I_SADL_PART_1_FLI’ to:
    @EndUserText.label: 'FLIGHT interface view for Zx_SADL'
    @ObjectModel: {
    representativeKey: 'fldate',
    semanticKey: [ 'carrid', 'connid', 'fldate' ],
    writeActivePersistence: 'sflight',

    createEnabled: true,
    deleteEnabled: true,
    updateEnabled: true
    }
    […]
    $projection.carrid = _ca.carrid
    {
    @ObjectModel.readOnly: true
    @ObjectModel.foreignKey.association: '_ca'
    key fl.carrid,
    […]
    /* Associations */
    @ObjectModel.association.type: [#TO_COMPOSITION_ROOT, #TO_COMPOSITION_PARENT]
    _ca
    }​


  3. Activate both views in one step

  4. Observe the generated artifacts

    1. SAP GUI / BOBX

      1. Find and open new local business object ‘Zx_I_SADL_PART_1_CARR’

      2. Observe the node structure and the actions of the nodes

      3. Test the business object: F8

        1. Load node instance by alternative key ‘*_CARR – DB KEY’ = ‘AA’

        2. Navigate the CARR object to its flights through the association





    2. Test the CDS views, follow association

      1. Right click CDS view definition of Zx_I_SADL_PART_1_CARR in Eclipse / Open With / Data Preview

      2. Place cursor on ‘carrid’ = ‘AA’, click triangle after ‘Zx_I_SADL_PART_1_CARR’ in title to get a list of associations, navigate to ‘_fl’

      3. Observe the list of flights retrieved via the association






Custom business object action


Define a new action (i.e. function import) ‘ZTOGL_URL_SLASH’:

  1. BOBX, open Zx_I_SADL_PART_1_CARR, enter ‘Edit’ mode

  2. New action on Zx_I_SADL_PART_1_CARR

    1. Action Name = ‘ZTOGL_URL_SLASH’

    2. Description = ‘Toggle slash at end of URL’

    3. Action cardinality = ‘Single Node Instance’

    4. Implementing Class = ‘ZCL_x_I_SADL_PART_1’

    5. Exporting Parameter = ‘Node’

    6. Exporting Parameter BO = ‘Zx_I_SADL_PART_1_CARR’

    7. Exporting Parameter Node = ‘Zx_I_SADL_PART_1_CARR’

    8. Exporting Cardinality = ‘1’

    9. Save

    10. Double click implementing class, create it

    11. Activate the implementing class without changes, (re)generate the business object



  3. Implement action ‘ZTOGL_URL_SLASH’

    1. Edit ‘ZCL_x_I_SADL_PART_1’ (in SE80 / Eclipse)

    2. Change superclass to ‘/BOBF/CL_LIB_A_SUPERCL_SIMPLE’, don’t keep redefinitions

    3. Description = ‘Action class for BO node Zx_I_SADL_PART_1_CARR’

    4. Look up the ‘Constants Interface’ of node ‘Zx_I_SADL_PART_1_CARR’ in BOBX: ‘ZIF_x_I_SADL_PART_1_CAR_C’

    5. Redefine ‘/BOBF/IF_FRW_ACTION~EXECUTE’:
          CASE is_ctx-act_key.
      WHEN zif_x_i_sadl_part_1_car_c=>sc_action-zx_i_sadl_part_1_carr-ztogl_url_slash.
      " Declare output table of Action
      DATA lt_carr TYPE ztx_i_sadl_part_1_carr.

      " Read input carrier instance
      io_read->retrieve(
      EXPORTING
      iv_node = is_ctx-node_key " Node Name
      it_key = it_key " Key Table
      IMPORTING
      et_data = lt_carr " Data Return Structure
      ).

      " Assuming single instance for the action
      READ TABLE lt_carr ASSIGNING FIELD-SYMBOL(<fs_carr>) INDEX 1.
      IF sy-subrc = 0.
      " Make changes to the business object:
      " * Set new URL
      IF substring( val = <fs_carr>-url off = numofchar( <fs_carr>-url ) - 1 len = 1 ) EQ '/'.
      <fs_carr>-url = substring( val = <fs_carr>-url off = 0 len = numofchar( <fs_carr>-url ) - 1 ).
      ELSE.
      <fs_carr>-url = |{ <fs_carr>-url }/|.
      ENDIF.

      " Now update the BO instance
      io_modify->update(
      EXPORTING
      iv_node = is_ctx-node_key " Node
      iv_key = <fs_carr>-key " Key
      iv_root_key = <fs_carr>-root_key " NodeID
      is_data = REF #( <fs_carr>-node_data )" Data
      it_changed_fields = VALUE #( ( zif_x_i_sadl_part_1_car_c=>sc_node_attribute-zx_i_sadl_part_1_carr-url ) ) ).

      et_data = lt_carr.
      ENDIF.
      WHEN OTHERS.
      ENDCASE.​


    6. Activate



  4. Test action ‘ZTOGL_URL_SLASH’:

    1. BOBX / Test (F8) / Load node instance by alternative key ‘*_CARR – DB KEY’ = ‘AA’

    2. Select CARRID ‘AA’, Execute Action ‘ZTOGL_URL_SLASH’

    3. Observe how a trailing slash ‘/’ appears and disappears

    4. You can save, or discard the changes made in the test suite




CDS consumption view


Create the CDS consumption view that is to be consumed by the referenced CDS view data source:

  1. Create carrier (airline) consumption view

    1. Eclipse / New Data Definition: Name = ‘Zx_C_SADL_PART_1_CARR’, Description = ‘CARRIER consumption view for Zx_SADL’, template = ‘Define View’

      1. Note: ‘_C_’ is for consumption view



    2. Change to:
      @AbapCatalog.sqlViewName: 'zx_c_carr1'
      @AbapCatalog.compiler.compareFilter: true
      @AccessControl.authorizationCheck: #NOT_REQUIRED
      @EndUserText.label: 'CARRIER consumption view for Zx_SADL'
      @ObjectModel: {
      compositionRoot: true,
      transactionalProcessingDelegated: true

      updateEnabled: true
      }
      @UI: {
      headerInfo: { typeNamePlural: 'Airlines' }
      }
      @VDM.viewType: #CONSUMPTION
      define view Zx_C_SADL_PART_1_CARR
      as select from Zx_I_SADL_PART_1_CARR as ca
      association [0..*] to Zx_C_SADL_PART_1_FLI as _fl on $projection.carrid = _fl.carrid
      {
      key ca.carrid,
      @EndUserText.label: 'Airline Name'
      ca.carrname as CarrierName,
      @Semantics.url: true
      ca.url,
      /* Associations */
      _fl
      }​


      1. Note how field aliases (‘as’), @Semantics and @UI annotations are placed into the consumption view

      2. Note how e.g. ‘updateEnabled’ is repeated. These annotations affect both the SADL and the BOPF level. Annotations on the interface view enforce the requested behavior. Annotations on the consumption view control the entity set metadata. Always keep these annotations in sync.





  2. Create flight consumption view

    1. Eclipse / New Data Definition: Name = ‘Zx_C_SADL_PART_1_FLI’, Description = ‘FLIGHT consumption view for Zx_SADL’, template = ‘Define View’

    2. Change to:
      @AbapCatalog.sqlViewName: 'zx_c_fli1'
      @AbapCatalog.compiler.compareFilter: true
      @AccessControl.authorizationCheck: #NOT_REQUIRED
      @EndUserText.label: 'FLIGHT consumption view for Zx_SADL'
      @ObjectModel: {
      createEnabled: true,
      updateEnabled: true,
      deleteEnabled: true
      }
      @VDM.viewType: #CONSUMPTION
      define view Zx_C_SADL_PART_1_FLI
      as select from Zx_I_SADL_PART_1_FLI as fl
      association [1..1] to Zx_C_SADL_PART_1_CARR as _ca on $projection.carrid = _ca.carrid
      {
      key fl.carrid,
      key fl.connid,
      key fl.fldate,
      @EndUserText.label: 'Airplante Type'
      fl.planetype,
      @Semantics.amount.currencyCode: 'CurrencyCode'
      fl.price,
      @Semantics.currencyCode: true
      fl.currency as CurrencyCode,
      fl.seatsmax,
      /* Association */
      _ca
      }​




  3. Activate both views at the same time

  4. Test the consumption views in Eclipse with data preview


Referenced CDS consumption view data source


Extend the existing OData service by referencing the new CDS consumption view:

  1. SEGW / open project ‘Zx_SADL_PART_1’

  2. Right click ‘Data Model’ / ‘Reference’ / ‘Data Source’ / CDS-Entity = ‘Zx_C_SADL_PART_1_CARR’ / Next

    1. Check in all the check boxes in ‘Selected’ column, Finish



  3. Observe and explore the new ‘Data Source References’ node

    1. Note how ‘…FLIType.connid’ is type ‘Edm.String’, instead of the best-practices ‘Edm.Decimal’

    2. Check out the permitted operations of the ‘Entity Sets’

    3. Note how the BO action ‘ZTOGL_URL_SLASH’ is exposed as a ‘Function Import’, even though this is not requested anywhere in the CDS



  4. Save and generate the project


Test the OData service. Launch /IWFND/GW_CLIENT or a REST client e.g. Postman:

  1. GET /sap/opu/odata/sap/Zx_SADL_PART_1_SRV/$metadata?sap-ds-debug=true

    1. Postman: retrieve the CSRF token with:

      1. Header ‘x-csrf-token’ = ‘Fetch’





  2. Get carrier ‘AA’

    1. GET /sap/opu/odata/sap/Zx_SADL_PART_1_SRV/Zx_C_SADL_PART_1_CARR?$filter=carrid eq 'AA'

      1. Use header ‘Accept’ = ‘application/json’





  3. Follow navigation properties

    1. GET /sap/opu/odata/sap/Zx_SADL_PART_1_SRV/Zx_C_SADL_PART_1_CARR('AA')/to_fl?$top=3

    2. GET /sap/opu/odata/sap/Zx_C_SADL_PART_2_CARR_CDS/Zx_C_SADL_PART_2_FLI(carrid='AA',connid='0017',fldate=datetime'2016-08-17T00%3A00%3A00')/to_ca



  4. Note down the function import name, e.g. ‘ACC0323FA175F5649EE81C514Ztogl_url_slash’

  5. Call function import

    1. POST /sap/opu/odata/sap/Zx_SADL_PART_1_SRV/…Ztogl_url_slash?carrid='AA'

    2. Get carrier ‘AA’ again



  6. Update ‘url’ of carrier ‘AA’ (using PATCH or MERGE if available)

    1. PATCH /sap/opu/odata/sap/Zx_SADL_PART_1_SRV/Zx_C_SADL_PART_1_CARR(‘AA’)

      1. Header ‘Content-Type’ = ‘application/json’

      2. Body: copy from GET above, change the URL, e.g.:
        {
        "carrid": "AA",
        "CarrierName": "American Airlines",
        "url": "http://www.aa.com/"
        }​


      3. Status 204 ‘No Content’ indicates success





  7. Attempt to create new airline ‘ZZ’

    1. POST /sap/opu/odata/sap/Zx_SADL_PART_1_SRV/Zx_C_SADL_PART_1_CARR

      1. Header ‘Content-Type’ = ‘application/json’

      2. Body: copy from GET above, change the URL, e.g.:
        {
        "carrid": "ZZ",
        "CarrierName": "Zambian Zoom",
        "url": "http://www.zz.com"
        }​


      3. The request fails with status 500 with message ‘The ASSERT condition was violated’: create is not allowed on the BOPF level.

        1. Note: in case e.g. create is allowed on the BOPF level (interface view) but isn’t allowed on the OData metadata level (consumption view), entity creation will succeed.








Part 2 – CDS-BOPF-OData service exposure with @OData.publish: true


In this solution no SEGW gateway project is used. The CDS view is edited in Eclipse.

  1. Eclipse / ABAP perspective / Right click project / New / Other... / Data Definition

    1. Create (or reuse) interface CDS views ‘Zx_I_SADL_PART_1_CARR’ and ‘Zx_I_SADL_PART_1_FLI’ from the section above

    2. Add (or reuse) custom action ‘ZTOGL_URL_SLASH’ to BO ‘Zx_I_SADL_PART_1_CARR’ from the section above

    3. Create new consumption views ‘Zx_C_SADL_PART_2_CARR’ and ‘Zx_C_SADL_PART_1_FLI’ as below:

      1. ‘Zx_C_SADL_PART_2_CARR’: description ‘CARRIER consumption view for @OData.publish: true’

        1. Create it like ‘Zx_C_SADL_PART_1_CARR’ above

        2. @AbapCatalog.sqlViewName: 'zx_c_carr2'

        3. Association to ‘Zx_C_SADL_PART_2_FLI’

        4. Add view-level annotation ‘@OData.publish: true’



      2. ‘Zx_C_SADL_PART_2_FLI’: description ‘FLIGHT consumption view for @OData.publish: true’

        1. Create it like ‘Zx_C_SADL_PART_1_FLI’ above

        2. @AbapCatalog.sqlViewName: 'zx_c_fli2'

        3. Association to ‘Zx_C_SADL_PART_2_CARR’





    4. Activate consumption views together

    5. Note the warning on the line with ‘@OData.publish: true’: “Service … is not active”

    6. Go to ‘/IWFND/MAINT_SERVICE’ and add service ‘Zx_C_SADL_PART_2_CARR’

      1. System alias = LOCAL






Test the OData service ‘/sap/opu/odata/sap/Zx_C_SADL_PART_2_CARR_CDS’:

  1. GET /sap/opu/odata/sap/Zx_C_SADL_PART_2_CARR_CDS/$metadata?sap-ds-debug=true

    1. Postman: retrieve the CSRF token with:

      1. Header ‘x-csrf-token’ = ‘Fetch’





  2. Continue the test like for ‘Zx_SADL_PART_1_SRV’ above

    1. GET /sap/opu/odata/sap/Zx_C_SADL_PART_2_CARR_CDS/Zx_C_SADL_PART_2_CARR?$filter=carrid eq 'AA'

    2. POST /sap/opu/odata/sap/Zx_C_SADL_PART_2_CARR_CDS/A5393E1DDD898C21B108F25FCZtogl_url_slash?carrid='AA'

    3. PATCH /sap/opu/odata/sap/Zx_C_SADL_PART_2_CARR_CDS/Zx_C_SADL_PART_2_CARR('AA')

    4. POST /sap/opu/odata/sap/Zx_C_SADL_PART_2_CARR_CDS/Zx_C_SADL_PART_2_CARR




Summary

Following this tutorial you created an OData service with existing tables, DDIC data source, referenced CDS view data source and custom function import as a BO action. You also created an OData service using the ABAP Development Tools for Eclipse, CDS views and BOPF.

You now know how to use an interface (base), and a consumption view for OData exposure. You gained experience to make the right implementation choice for your next OData project, and you know where to find further information on the topic.

Further reading


 

Afterword

Thank you for going through this tutorial. I hope you found it useful. I hope it raised questions as well. Do follow the above, or other links to satisfy your curiosity - a sure way to deepen your understanding.

Another very interesting topic and a candidate for future posting is Christian Theilemann's "Reactive state management in SAPUI5 via MobX". I recommend you check it out. Update: "Adding Reactive State Management with Validation to Existing UI5 Application – a Tutorial" has now been published.
5 Comments
Labels in this area