Introduction

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.

You can find the slides on http://tinyurl.com/sitwdfslides and the sourcecode on https://github.com/andau/zsitwdf.

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.

 

 

Conclusion

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.

To report this post you need to login first.

2 Comments

You must be Logged on to comment or reply to a post.

  1. Christian Lechner

    Hi Andreas,

    interesting scenario for the usage of a BRFplus function and nice approach to use the possibilities the BRFplus API offers.

    Some remarks concerning your Q&As:

    • Performance: Yes it is indeed a hot topic and several aspects have to be considered:
      In general, BRFplus generates the code of the functions and has a better performance than in the case of interpreting the rules.
      However, this is only one ingredient to assure optimal performance. Typical performance traps are currency or unit conversion (try to avoid it inside BRFplus) and usage of expressions that prevent code generation (fallback to interpretation mode in rule execution).
      In addition, one has to take into account how the BRFplus is called, so always use the pattern that the BRFplus code snippet of the workbench provides to you, namely the call of the static class method as in your case.
      Last but not least: How many data sets do you want to process and does it make sense to parallelizeĀ the call of the BRFplus function?
      In your case, it might be feasible to increase the performance by generating the code of the caller and adopt the generated code each time a new parameter is added (so in some sense mimic the behavior of BRFplus) The basics are already in your code as you have the metadata already at hand. It is just a code generator (and the code composer) away šŸ™‚
    • BRFplus vs.Business Department: Well you can tailor the BRFplus workbench and the catalog browser to quite some extent, however, from my experience at least at the start some guidance is necessary and only key users should make use of it.
      Here the future might look a bit brighter as Fiori UIs for some expressions are planned to be delivered (educatedĀ guess: when the HRF integration into BRFplus is doneĀ because HRF already has those nice UI5Ā components)
    • Transport and delivery to different systems: Yep I confirm that in this case SAP Decision Service Management is the weapon of choice … but it comes at a price namely a license fee.
    • Unit Tests and BRFplus: I am not aware of a Unit Test Framework in BRFplus. There is a package that contains test classes (SFT_TEST) but imhoĀ these classes are for internal purposes. I also do not see the necessity of real ABAP Unit Tests for BRFplus as the call of the BRFplus function is usually not difficult (you copy and paste the code snippet .. well, except for your case).
      When it comes to testing the business content in BRFplus namely the functions there is a test management available in BRFplus (seeĀ help.sap.com) that can be seen as a “unit test infrastructure” for BRFplus functions

    Keep on blogging!

    Best regards,

    Christian

    (2) 
    1. Andreas Gautsch Post author

      Thanks Christian for your valuable answers and clarifications.
      I am looking forward with a lot of curiosit to the improvements that will show up in the BRF and HRF area. Such powerful tools do also need a powerful UI.

      Greetings, Andreas

       

      (1) 

Leave a Reply