This blog is a summary of my presentation at Sap Inside Track Walldorf about connecting SAPs rule engine BRFplus to an ABAP program, userexit or any other piece of runnable code in ABAP.
The intention of this presentation is to point out the possibilities to dynamically call a function defined in BRF plus beside the standard approach to generate the necessary code to call a BRF function from ABAP.
For the reason that SIT Walldorf this year was IoT related I build up an IoT related example. Well, I am not sure if this example will be really going into production anytime, anywhere 😉 but at least it does its job as simplified model for explaining the interface between ABAP and BRFplus.
I am not giving an introduction into BRFplus in this reading; there is already a lot of content here on the SCN community space. From a business perspective I would recommend the following Youtube video: https://www.youtube.com/watch?v=2ouhJeH02HU as a starting point. From technical perspective for example this blog: BRFplus a real time example gives a good introduction.
The focus in this document is on the pretty cool interface class CL_FDT_FACTORY which can be used to extract metadata and later to call a BRFplus function dynamically. Why would you do this? Well normally yo won’t. If you have a well defined interface it’s for sure the better way to go for the standard way: generating the necessary source code for example with the report FDT_TEMPLATE_FUNCTION_PROCESS. But what if you do not know already which input parameters you will need in your function or the interface is expected to change a lot in future?
The business case
That brings me to my chosen example. Lets assume you own a production company and there beside others you have a special machine. This machine has a lot of sensors and modernisation continuously comes around to say “Hello” and add some new sensors. The maintenance people have a status monitor in the SAP ERP system where machine problems together with ERP information (e.g. the needed spare parts – type, actual location …) are visualised.
The maintenance people can add the sensors to the machine and enrich the status information already sent from the productio machine to the ERP system with the new sensor data, but they are not able to program in ABAP or JAVA. For some reason they like BRFplus and are keen on changing the function for maintenance data evaluation function with the BRFplus GUI.
Now lets have a closer look on our production machine and its connection to the ERP system (OK to be honest I am switchin to ean easier example 😉
BRFplus connected to a simple IoT dev 😉
The coffeemachine is a really high sophisticated one, if there is a change in the sensors (usually when somebody takes a coffee) the machine sends the data to the SAP ERP system. The SAP ERP system calls the BRFplus function where the maintenance people define how the sensor data should be evaluated.
Nothing special, just straightforward how you can use BRFplus. With only one differences before the BRFplus function is called the SAP ERP System gets the context data of the BRFplus function – particularly the defined input data (sensors) and propagates to the BRFplus function only that sensor data received from the coffeemachine that is also defined in the function.
The configuration of BRFplus would look something like this. Well the two screenshots shown below to not cover the entire configuration of the BRFplus function. But I am sure you get fast some useful ideas how to convert the input values BEANS and WATER to valuable maintenance messages.
Signature of the BRFplus function
Rules added to the BRFplus function
Extracting metadata with CL_FDT_FACTORY
To get the context data there is a nice interface class CL_FDT_FACTORY which you can use to retrieve the function metadata.
Code – Extract context parameters of a function
class ZCL_BRFPLUS_METADATA definition public final create public . public section. CLASS-METHODS: getFunctionContextParams importing pFunctionId type if_fdt_types=>id returning value(pContextParams) type ZT_CONTEXT_PARAMS, protected section. private section. ENDCLASS. CLASS ZCL_BRFPLUS_METADATA IMPLEMENTATION. method getFunctionContextParams. DATA: contextParam like line of pContextParams. * get all context Ids DATA(contextObjectIds) = CL_FDT_FACTORY=>get_instance( )->get_function( pFunctionId )->get_context_data_objects( ). LOOP AT contextObjectIds assigning FIELD-SYMBOL(<contextObjectId>). * get instance by context id CL_FDT_FACTORY=>get_instance_generic( EXPORTING iv_id = <contextObjectId> IMPORTING eo_instance = DATA(lo_instance) ). * populate result table contextParam-name = lo_instance->get_name( ). contextParam-type = CONV string( CAST if_fdt_element( lo_instance )->get_element_type( ) ). append contextParam to pContextParams. ENDLOOP. endmethod. ENCLASS. ENDCLASS.
Unittest – Extract Context parameters of a function
class ZCL_BRFPLUS_METADATA_UNIT definition FOR TESTING. "#AU Risk_Level Harmless PUBLIC SECTION. private section. CONSTANTS: sitWdfFunctionId type if_fdt_types=>id VALUE '0241750C32391EE6B4D8A1790D2D5E0C', sitWdfFunctionName type IF_FDT_TYPES=>NAME VALUE 'COFFEE_MACHINE_STATUS'. METHODS: testGetFunctionContextParams FOR TESTING. ENDCLASS. CLASS ZCL_BRFPLUS_METADATA_UNIT IMPLEMENTATION. method testGetFunctionContextParams. DATA(contextParams) = ZCL_BRFPLUS_METADATA=>getFunctionContextParams( pFunctionId = sitWdfFunctionId ). CL_AUNIT_ASSERT=>assert_equals( EXP = 2 ACT = lines( contextParams ) ). read table contextParams with key name = 'ZSENSOR_FILL_QUANTITY_BEANS' into DATA(contextParam). CL_AUNIT_ASSERT=>assert_equals( EXP = 'T' ACT = contextParam-type ). clear contextParam. read table contextParams with key name = 'SENSOR_NOT_EXISTING' into contextParam. CL_AUNIT_ASSERT=>assert_initial( contextParam ). endmethod. . ENDCLASS.
With the defined context parameters the function can be called dynamically. When there is added a new input parameter (sensor) in the BRFfunction the call out of ABAP has not to be changed.
Code – Dynamic call of BRFplus function
class zcl_brfplus_function definition public final create public . public section. class-methods process importing pFunctionname type IF_FDT_TYPES=>NAME pSensorValues type ref to ZCL_SENSOR_VALUES returning value(pMaintenanceMessages) type ZT_MAINTENANCE_MESSAGES. protected section. private section. endclass. class zcl_brfplus_function implementation. method process. DATA: contextParams TYPE abap_parmbind_tab, contextparam like line of contextParams. FIELD-SYMBOLS <resultDataAny> TYPE any. GET TIME STAMP FIELD DATA(currentTimestamp). "get defined context params of brfplus function DATA(functionId) = zcl_brfplus_metadata=>getFunctionId( pFunctionname ). DATA(definedContextParams) = zcl_brfplus_metadata=>getfunctioncontextparams( functionId ). "build context information by matching defined context params and sensor values pSensorValues->getSensorValues( importing pSensorValues = DATA(sensorValues) ). loop at definedContextParams assigning field-symbol(<definedContextParam>). loop at sensorValues assigning field-symbol(<sensorvalue>). if <definedContextParam>-name = <sensorvalue>-name. "move definedcontextParams into context params format for BRFplus call. contextparam-name = <definedContextParam>-name. GET REFERENCE OF <sensorvalue>-value INTO contextparam-value. INSERT contextparam INTO TABLE contextparams. endif. endloop. endloop. "prepare and process BRFplus function cl_fdt_function_process=>get_data_object_reference( EXPORTING iv_function_id = functionId iv_data_object = 'ZTABLE_MAINTAINANCE_MESSAGES' iv_timestamp = currentTimestamp iv_trace_generation = abap_false IMPORTING er_data = DATA(resultData) ). ASSIGN resultData->* TO <resultDataAny>. cl_fdt_function_process=>process( EXPORTING iv_function_id = functionId iv_timestamp = currentTimestamp IMPORTING ea_result = <resultDataAny> CHANGING ct_name_value = contextParams ). "return result of brfplus function pMaintenanceMessages = <resultDataAny>. endmethod. endclass.
Unittest – Dynamic call of BRFplus function
class ltcl_ definition final for testing duration short risk level harmless. private section. DATA brfplusFunction type ref to zcl_brfplus_function. methods: setup, okTest2Sensors for testing raising cx_static_check, okTest3Sensors for testing raising cx_static_check, okTest2SensorsWithInputFrom3 for testing raising cx_static_check, failTest2Sensors for testing raising cx_static_check, generateDataFor2Sensors importing pSensorFillQuantityBeans type int2 pSensorFillQuantityWater type int2 returning value(pSensorValues) type ref to ZCL_SENSOR_VALUES, generateDataFor3Sensors importing pSensorFillQuantityBeans type int2 pSensorFillQuantityWater type int2 pSensorFillQuantityTrash type int2 returning value(pSensorValues) type ref to ZCL_SENSOR_VALUES. endclass. class ltcl_ implementation. method setup. create object brfplusFunction. endmethod. method okTest2Sensors. DATA(sensorValues) = generateDataFor2Sensors( exporting pSensorFillQuantityBeans = 80 pSensorFillQuantityWater = 80 ). DATA(maintenanceMessages) = brfplusFunction->process( exporting pFunctionName = `COFFEE_MACHINE_STATUS` pSensorValues = sensorValues ). cl_abap_unit_assert=>assert_equals( exp = 0 act = lines( maintenanceMessages ) ). endmethod. method okTest3Sensors. DATA(sensorValues) = generateDataFor3Sensors( exporting pSensorFillQuantityBeans = 80 pSensorFillQuantityWater = 80 pSensorFillQuantityTrash = 20 ). DATA(maintenanceMessages) = brfplusFunction->process( exporting pFunctionName = `COFFEE_MACHINE_STATUS_3SENSORS` pSensorValues = sensorValues ). cl_abap_unit_assert=>assert_equals( exp = 0 act = lines( maintenanceMessages ) ). endmethod. method okTest2SensorsWithInputFrom3. DATA(sensorValues) = generateDataFor3Sensors( exporting pSensorFillQuantityBeans = 80 pSensorFillQuantityWater = 80 pSensorFillQuantityTrash = 80 ). DATA(maintenanceMessages) = brfplusFunction->process( exporting pFunctionName = `COFFEE_MACHINE_STATUS` pSensorValues = sensorValues ). cl_abap_unit_assert=>assert_equals( exp = 0 act = lines( maintenanceMessages ) ). endmethod. method failTest2Sensors. DATA(sensorValues) = generateDataFor2Sensors( exporting pSensorFillQuantityBeans = 20 pSensorFillQuantityWater = 80 ). DATA(maintenanceMessages) = brfplusFunction->process( exporting pFunctionName = `COFFEE_MACHINE_STATUS` pSensorValues = sensorValues ). cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( maintenanceMessages ) ). sensorValues = generateDataFor2Sensors( exporting pSensorFillQuantityBeans = 80 pSensorFillQuantityWater = 10 ). maintenanceMessages = brfplusFunction->process( exporting pFunctionName = `COFFEE_MACHINE_STATUS` pSensorValues = sensorValues ). cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( maintenanceMessages ) ). endmethod. method generateDataFor2Sensors. create object pSensorValues. pSensorValues->addSensorValue( pSensorname = `ZSENSOR_FILL_QUANTITY_BEANS` pSensorValue = pSensorFillQuantityBeans ). pSensorValues->addSensorValue( pSensorname = `ZSENSOR_FILL_QUANTITY_WATER` pSensorValue = pSensorFillQuantityWater ). endmethod. method generateDataFor3Sensors. create object pSensorValues. pSensorValues->addSensorValue( pSensorname = `ZSENSOR_FILL_QUANTITY_BEANS` pSensorValue = pSensorFillQuantityBeans ). pSensorValues->addSensorValue( pSensorname = `ZSENSOR_FILL_QUANTITY_WATER` pSensorValue = pSensorFillQuantityWater ). pSensorValues->addSensorValue( pSensorname = `ZSENSOR_FILL_QUANTITY_TRASH` pSensorValue = pSensorFillQuantityTrash ). endmethod. endclass.
Finally to come back at our initial coffee machine example. If you, for example start with two sensors – water and beans and decide later to add a trash sensor or a milk sensor or whatever sensor else you have nothing to change in the ABAP code. The key to do this is using the ABAP class CL_FDT_FACTORY. Further you can define your maintenance messages without the need of custom tables and enrich your messages with useful data of the ERP system by selecting the data for example with BRFplus data.
Questions and answers at the presentation
During the Q&A part after the presentations there where some interesting questions raised. First of all to stress it onces more this is not a productive running system and was only created for the presentation at SIT Walldorf. I want to point out some of them:
- How is the performance of BRFplus
When using BRFplus, performance is of course always a topic. So extracting the metadata before the function call is sometimes not possible and recommendable.
- Is BRFplus the right tool for a business department?
A main target of BRFplus is to hand over the configuration to the business department. Is the BRFplus really the right interface for not techie people. I think there is no general answer but its worth a try. Starting to work with BRFplus needs for sure some patience but there are some really nice features like integrated simulation and features like up-/ and download of decision tables with Excel.
- Handling of several production clients or systems with different configuration.
As far as I know in this case you should include also the SAP Decision Service Management (DSM) – but I am not already quite familiar with this system
- What are some examples of scenarios where BRFplus is already used in productive systems?
In the “Book Business Rule Management with ABAP” from the SAP Press there are shown some cases in the Collection Management, Claim management or finance statisitics.
Variant configuration is one area where BRFplus fits good in my opinion. The usage of often changing product configurations which are not performance critical can be done with the above shown example as the variant configuration works with characteristics (key/value) pairs and that was also the first area I was thinking about using BRFplus.
- Is it possible to UnitTest BRFplus?
Of course its possible to test it with ABAP unittests. There should be also a standalone UnitTest Framework in BRFplus, if somebody has more information please add it below in a comment.
Well there is sure a lot more to discuss. Thus feel free to use the comment section below. This section impatently waits to your question, remark or hint.