Developing data driven reusable READ functionality. Part 1
If you have been working with SAP Gateway for a while you will begin to notice the pattern of defining Entities, Entitysets, Associations, etc… and then the coding pattern of redefining GET_ENTITY, GET_ENTITYSET, etc… etc…
In this series of blogs I’d like to introduce you to a data driven pattern which I have found quite useful for speeding up the creation of the READ functionalities of CRUD. Leaving more time to focus on the CUD of CRUD…
Part 2 – Reusable READ functionality. $select, $orderby, $top, $skip, $inlinecount, $count
Part 3 – Reusable READ functionality. $expand and navigation
Part 4 – Reusable READ functionality. Extending with Base Entities
Just to cover off the prerequisites before we jump into the fun stuff. The functionality shown here has been implemented on an ECC6 system running ABAP 7.31 with a centralized Gateway 7.40 system, as below.
We will be making use of the good old SFLIGHT demo model for this one. So make sure you have access to that also. Though any table / views can be used of course and as you will see quite easily.
The beginnings of this idea started when defining another model in SEGW, and longing for CDS views, alas still sitting on an ABAP 7.31 system this isn’t going to happen right? It seems CDS is introduced with ABAP 7.40 SP05.
So why can we not use the model we have just defined in SEGW to drive the functionality of querying the entities? Good question I thought. Time to jump in and do some research/debugging.
By the end of this series of blogs we’ll be in a place to add the following to our redefined GET_ENTITY:
get_gw_helper( )->get_entity( exporting io_tech_request_context = io_tech_request_context changing cs_entity = er_entity cs_response_context = es_response_context ).
And this to our redefined GET_ENTITYSET:
get_gw_helper( )->get_entityset( exporting io_tech_request_context = io_tech_request_context changing ct_entityset = et_entityset cs_response_context = es_response_context ).
This will be all that’s required apart from defining the model and registering the service to get our basic query operations working in gateway.
The main component of the design is our helper class, this will handle the processing of the request. In this class we’ll be making use of the gateways model class, /iwbep/cl_mgw_odata_model. Using the model we can gain access to our tables/views, model entities, properties, referral constraints and much more. These will all aid in making a generic helper class.
We will also be making use of our trusted SE11 to define views when required, saving us the need to create inner joins within the code.
In a later blog we’ll add the functionality to extend our entities with properties not existing on our tables or views via the use of an interface class, and some help from SEGW.
Alright enough talk, let’s start development. We’ll begin by defining the model in SEGW to see how things should be setup for our helper class to work. Then move onto the helper class and how we can interrogate the model we have just created. First we will be setting up a simple entity and its related entity set.
Open up transaction SEGW and define a new project, ZFLIGHT and save it as a local object:
Next let’s create a new entity for the airline table SCARR by importing the DDIC structure:
We’ll call the entity Airline, and create the default entity set:
Select the attributes to include:
And finally the key:
Now let’s save and generate the run time objects:
Note here we have left the ABAP fieldnames as they exist on the table, also if you take a look at the Airline entity it has a reference to the table we used to import the structure SCARR. Using these two bits of information we can being to create a generic class to handle some of our basic query operations.
To reach the service we will have to call our transaction to maintain services, /IWFND/MAINT_SERVICE and add our service to activate it.
Test the service using the gateway client /IWFND/GW_CLIENT and the $metadata operator to see if our newly create service can be reached:
If all is good, then you should receive back the XML from the request.
Let’s continue with our generic class.
Gateway Helper Class
Initially we will be creating a very simple helper. We will extend this over a couple of blogs to be more robust. Be careful at first though as we are implementing no paging at this stage, so be sure not to use any tables with way too many records and no $filter…
Open up SE80 and create a new class ZCL_GATEWAY_HELPER.
We will give this class 2 attributes to start off with.
- MR_MODEL, will keep track of the current model we are working on.
- MV_DB_TABNAME, will keep track of the database table or view we are working with.
Now lets create our methods.
The constructor will take a parameter to the runtime, IR_RUNTIME. By using this we can gain access to the model via the façade as below. We will store the reference to the model in our attribute created above as it’ll be required often.
method constructor. data: lr_facade type ref to /iwbep/cl_mgw_dp_facade. * grab a reference to our facade to get our model reference lr_facade ?= ir_runtime->get_dp_facade( ). * save our model for reference later mr_model ?= lr_facade->/iwbep/if_mgw_dp_int_facade~get_model( ). endmethod.
This will be called both from the GET_ENTITY and GET_ENTITYSET methods of this class. Its purpose funnily enough to initialise everything ready for the next GET request. Here we really interrogate the model to get the main database table or view which we are working from and store this in the MV_DB_TABNAME attribute. The method takes one parameter, IV_ENTITY_NAME, being the current entity name being processed, from this we can grab the structure name, being the database table or view we used in SEGW.
METHOD init. DATA: lv_entity_name TYPE /iwbep/if_mgw_med_odata_types=>ty_e_med_entity_name, lr_table_entity TYPE REF TO /iwbep/cl_mgw_odata_entity_typ. * free last table name used FREE: mv_db_tabname. * grab table entity lv_entity_name = iv_entity_name. lr_table_entity ?= mr_model->get_entity_type( lv_entity_name ). * grab DDIC reference mv_db_tabname = lr_table_entity->/iwbep/if_mgw_odata_re_etype~get_structure( ). ENDMETHOD.
In this tutorial we’ll be calling this method from our gateway data provider class extension. In our case this will be class, ZCL_ZFLIGHT_DPC_EXT and method GET_ENTITY. It will have 3 parameters as below and will be responsible for requesting and returning the entity.
It first makes a call to out init() method mention above to get ready for the new request, this will essentially set up our mv_db_tabname attribute to be the table to select from. Next we create a data structure of our passed in entity, so we can get the values of the converted keys, followed by a call to the keys passed in. We then use these two bits of information to loop over the keys and build our where condition for the OSQL statement. Finally making the call to the OSQL statement with the predetermined values.
method get_entity. data: lt_keys type /iwbep/t_mgw_tech_pairs, lv_db_where type string, lv_db_and type string value '', lr_data type ref to data. field-symbols: <ls_key> like line of lt_keys, <ls_data> type any, <lv_value> type any. * initialise init( io_tech_request_context->get_entity_type_name( ) ). if mv_db_tabname is not initial. * create data struct to grab converted keys create data lr_data like cs_entity. assign lr_data->* to <ls_data>. io_tech_request_context->get_converted_keys( importing es_key_values = <ls_data> ). lt_keys = io_tech_request_context->get_keys( ). * loop over keys to build where condition loop at lt_keys assigning <ls_key>. assign component <ls_key>-name of structure <ls_data> to <lv_value>. if sy-subrc = 0. lv_db_where = lv_db_where && lv_db_and && `( ` && <ls_key>-name && ` = '` && <lv_value> && `' )`. lv_db_and = ` and `. endif. endloop. if lv_db_where is not initial. select single * from (mv_db_tabname) into corresponding fields of cs_entity where (lv_db_where). endif. endif. endmethod.
Again this method will be called from our data provider class, ZCL_ZFLIGHT_DPC_EXT and method GET_ENTITYSET. It will take 3 parameters listed below and be responsible for returning the entity set. Again this function makes a call to the init() method to prepare for our new request. Makes a call to the tech request to get the converted filter, from $filter, and then calls the OSQL with the determined values.
method get_entityset. data: lv_db_where type string. * initialise init( exporting iv_entity_name = io_tech_request_context->get_entity_type_name( ) ). if mv_db_tabname is not initial. * $filter, grab our converted filter lv_db_where = io_tech_request_context->get_osql_where_clause_convert( ). * execute our select select * from (mv_db_tabname) into corresponding fields of table ct_entityset where (lv_db_where). endif. endmethod.
That’s it for our first simple iteration of this class. We’ll be doing more work here in future blogs. But for now let’s see how we can implement this into our data provider class extension, and test it out.
Data Provider Extension Class
When we hit the generate button in SEGW one of the things that happened was the generation of our data provider extension class, ZCL_ZFLIGHT_DPC_EXT. This class is the one responsible for filling out all of our entities and sets, or at least the entry point to have those entities filled out via other business classes or functions. To implement our generic helper class from here we will create one attribute in this class, as below a reference to our helper class.
Now that’s created lets create a method to return our helper class.
This method with return a reference with lazy instantiation to our gateway helper class.
method get_gw_helper. * if our helper has not been created yet, then create... if mr_gw_helper is not bound. create object mr_gw_helper exporting ir_runtime = me. endif. * return instance rr_gw_helper = mr_gw_helper. endmethod.
Now we can redefine our AIRLINESET_GET_ENTITY() method and call the following:
method airlineset_get_entity. get_gw_helper( )->get_entity( exporting io_tech_request_context = io_tech_request_context changing cs_entity = er_entity cs_response_context = es_response_context ). endmethod.
We can also redefine our AIRLINESET_GET_ENTITYSET() method and call the following:
method airlineset_get_entityset. get_gw_helper( )->get_entityset( exporting io_tech_request_context = io_tech_request_context changing ct_entityset = et_entityset cs_response_context = es_response_context ). endmethod.
Activate all the changes and let’s give it a test.
Ensure that you have some data in your SFLIGHT tables, if not you can run program SAPBC_DATA_GENERATOR to generate some.
Open up the gateway client, /IWFND/GW_CLIENT and try the following:
/sap/opu/odata/sap/ZFLIGHT_SRV/AirlineSet()?$filter=AirlineCurrency eq ‘AUD’&$format=json
Well I hope you have enjoyed this introduction. There is still a lot more to cover in the following blogs, as we still have quite a few odata operators to take care of and you will see how these can be implemented as we move on though the series, so stay tuned.
I’m also happy for any feedback along the way so please let me know. I’d also be interested in any other methods of how people have been dealing with their gateway implementations.
I have read many a great blog,document,article and post on SCN, so thank you to everyone for sharing your knowledge, hopefully you will find mine just as useful as I have found yours.
See you soon, in part 2.
Nice work David. Very easy to follow. Thanks for sharing
Thanks Mike, appreciate the feedback. Hopefully you find it usefully some where along the lines.
Nice idea and implementation. I think this could be further simplified by redefining /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_ENTITYSET method. With this you would add a call to your helper once and it works for all entity sets.
Thanks Tomasz, I hope you find it useful.Good point, absolutely if the service was purely using entities exposing database tables and views this would be possible.
Just have to be aware if adding entities to the service that do not require this common logic.
Good Idea and More robust.I have question given below.
Is that purely works for DB tables not other ways means mapping from BAPI/RFC etc..
Thanks! In the current form, blog 1 through 3, yes only mapping from DB tables and views is supported.I'm almost finished writing up blog 4, hopefully published in the next couple of days.
In blog 4 I will go through the process of extending these tables/views though SEGW and using an interface. Using a class with this interface it's possible to enhance the data (table/view) via standard ABAP, be it a function module/class etc.
Hope this helps.
Thanks for clarification.Hopefully waiting for blog 4 🙂
Blog 4 (part 4) is up please see the link at the top of this blog.