Wednesday, June 12, 2013

The other side of the Business Object Layer (BOL)

When working with a CRM 7.0 system, especially when enhancing it, you very soon get to know some aspects of the framework behind its WebClient. The following picture gives an overview of the different layers.

For instance, if you want to change a screen configuration you need tools that are offered by the Presentation Layer. This layer is responsible for presenting the CRM data to the user and handling user interactions. The necessary data is not directly read from tables but instead are offered in a object oriented way by the layer below, the Business Layer.

The Business Layer presents the CRM data and logic in a nice uniform way by encapsulating data in Business Objects. These objects have attributes and may have, and this is a very powerful aspect, relations with other objects. The actual data is accessed through what is called the Generic Interaction Layer, or GenIL. This, CRM specific, layer is formed by ABAP classes that access the data by calling Application Programming Interfaces and provide it to the Business Object Layer.

In this blog I want to zoom in on the Business Layer and show a little bit of how it can be accessed from ABAP code, how the GenIL is implemented for a Business Partner and then...

...switch to the other side of the Business Object Layer and, instead of being a consumer of Business Objects, show how to enhance existing objects with new additional fields and relations and how to create completely new Business Objects.

Tools within the Business Object Layer

The Business Object Layer (BOL) offers:
  • the Model Browser, a design environment to view the properties of the available Business Objects. It can be started with transaction GENIL_MODEL_BROWSER.
  • the BOL Browser, a test environment for accessing Business Objects with real data. Transaction GENIL_BOL_BROWSER.
When starting either one of these tools you first have to select a component or a component set (which is a collection of components). Commonly used components are BP for the Business Partner objects and BT for the Business Transactions. An easy way to discover which component set is used behind a certain screen is by pressing F2 on a field on the screen to get the UI Component, then go to the UI Component Workbench by starting transaction BSP_WD_CMPWB and have a look at the models node in the Runtime Repository display. The displayed value represents the component set that is used within this UI component:

To see which components are in a component set, go to the IMG and follow path: CRM » CRM Cross-Application Components » Generic Interaction Layer/Object Layer » Basic Settings:

Business Object Layer APIs

The BOL comes with several APIs that you can use from ABAP code. Some examples of how to get a Business Object (also called entity) from the BOL:

  "Based on the internal key
  lr_entity = cl_crm_bol_core=>get_instance( )->get_root_entity(
    iv_object_name = 'BTOrder'
    iv_object_guid = 'BC305BEC6E911EE284C07BCD24DED4E3' ).

  "Utility class
  lr_entity = cl_crm_uiu_bp_tools=>get_builheader_entity( '200362166' ).

  lr_query = cl_crm_bol_query_service=>get_instance( 'BuilHeaderSearchNew' ).
  lr_query->set_property( iv_attr_name = 'MC_NAME1' iv_value = 'media store' ).
  lr_entity = lr_query->get_query_result( )->get_first( ).

Other examples of how to perform to CRUD 
(Create, Read, Update and Delete) operations:

  "Create a new private account
  ls_param-name  = 'LASTNAME'.
  ls_param-value = 'Demo'. 
  append ls_param to lt_params.
  lr_person = cl_crm_bol_core=>get_instance( )
    ->get_entity_factory( 'BuilHeader' )->create( lt_params ).

  "Create a new relation with a phone object

  lr_phone = lr_person->create_related_entity( 'BuilIndependantPhoneRel' ).

  "Read the e-mail attributes

  lr_address = lr_builheader->get_related_entity( 'BuilStandardAddressRel' ).
  if lr_address is bound.
    lr_email = lr_address->get_related_entity( 'BuilStandardAddressEmailRel' ).
    if lr_email is bound.
      lv_email = lr_email->get_property_as_string( 'E_MAIL' ).

Method GET_PROPERTY_AS_STRING provides a formatted value:
BP number: 200362202 / Date: 09.12.2012

Method GET_PROPERTY_AS_VALUE returns the real internal type without conversion:
BP number: 0200362202 / Date: 20120912

  "Attributes can also be read in a compact manner using BPath
  lv_email_ref ?= lr_builheader->get_properties_by_bpath(
    `BuilStandardAddressRel/BuilStandardAddressEmailRel/@E_MAIL` ).

  "Update a telephone number
    iv_attr_name = 'TELEPHONE'
    iv_value     = '010720527' ).

  "A modify is required to process and validate the new values
  cl_crm_bol_core=>get_instance( )->modify( ).

  "Delete a root entity (if allowed)
  lr_entity->delete( ).

The Generic Interaction Layer (GenIL)

The customizing for the GenIL can be found in the IMG using path: CRM » CRM Cross-Application Components » Generic Interaction Layer/Object Layer » Basic Settings. The most important object here is the implementation class. This class forms the main access point for the Business Object Layer to gather all the relevant information. 

Important views are:
  • CRMV_OBJ_IBIL for Business Transacties
  • CRMV_OBJ_BUIL for Business Partner
This last view shows the GenIL counterparts of the Business Partner objects from the BOL:

Business Engine

An example of the API call within the Business Engine can be found in the READ-method of class CL_BUPA_IL_HEADER:

Customer enhancements

Typical enhancements within the Presentation Layer concern adjusting UI elements like creating dropdown lists, checkboxes or hyperlinks.

Examples of enhancements in the Business Layer are:
  • Add additional search fields to existing queries
  • Adding new relations to existing Business Objects
  • Creating new Business Objects
All of these examples are discussed in more detail in the following paragraphs.

Enhancing existing Business Objects

Let's say you have a system in which a lot of Installed Bases (IBases) are linked to accounts and when searching for accounts you want to add the possibility to search for the external ID of an IBase as well. The first step is to find the query object that is used in the view you normally use to search for accounts. Again the F2-functionality on the screen gives you all the information to start with: the UI component, the view and the context node. Once you have found the context node in the UI Component Workbench, it will tell you the query object that it is linked to:

The next step is figuring out the implementation details of this query. The GenIL customizing for the BP-component (transaction SM30, view CRMV_OBJ_BUIL) reveals the details:

Now we can create our enhanced versions:
  • Structure ZCRMT_BUPA_IL_HEADER_SEARCH in which we first include the original structure and add a field ZEXTID for the external ID as well
  • Class ZCL_BUPA_IL_HEADER_SEARCH as a subclass of the original one with a redefinition of method GET_DYNAMIC_QUERY_RESULT. Place an external breakpoint in this method for testing purposes later on.
Go back to the IMG and make the necessary settings in CRM » CRM Cross-Application Components » Generic Interaction Layer/Object Layer » Component-Specific Settings » Extend Object Model for Business Partner:

We are now ready to perform the first test of the enhanced query. Start the BOL browser for the BP-component, navigate to the BuilHeaderAdvancedQuery and double click it. In the section for Dynamic Query Parameters see if you can select field ZEXTID. Provide some values and press the Find-button. If everything goes well the system now stops at the breakpoint in our enhanced version. Parameter table IT_SELECTION_PARAMETERS contains the selection criteria. You now have a starting point for implementing additional logic. This logic could look something like this:

method if_crm_bupa_il_query~get_dynamic_query_result.

    lt_params type genilt_selection_parameter_tab,
    ls_param  type genilt_selection_parameter.

  lt_params it_selection_parameters.

  "Search on IBase?
  read table lt_params transporting no fields with key attr_name 'ZEXTID'.
  if sy-subrc 0.
    "Execute an IBase query e.g. using IBIBaseToIBaseAdv in component IBASE

    "Retrieve the linked accounts and add the found account numbers to LT_PARAMS
    ls_param-attr_name 'PARTNER'.
    ls_param-sign      'I'.
    ls_param-option    'EQ'.
    ls_param-low       '<BP number>'.
    append ls_param to lt_params.
    clear ls_param.

    "Remove the IBase specific search parameters
    delete lt_params where attr_name 'ZEXTID'.

  "Search using the standard advanced query
    iv_query_name           iv_query_name
    is_query_parameters     is_query_parameters
    it_selection_parameters lt_params
    iv_root_list            iv_root_list
    iv_root_object          iv_root_object ).


If the test went successful, the new field can be added to the screen configuration of view MainSearch in UI component BP_HEAD_SEARCH and by doing so it is made available to the WebClient:

And that's it! The enhancement which was made in the Business Layer can now be used both in the developer tools within the Business Layer as well as in the screen configuration in the Presentation Layer and is made available in the WebClient.

Adding new relations to existing Business Objects

The options within the GenIL customizing as described above also enable you to add new relations to existing Business Objects. The Model Browser in the following picture shows an enhancement on the standard relation BuilRelationshipRel on which a new relation zRelationDetailsRel is defined to manage relation specific details between two accounts.


The way to define this new relation is by going to the GenIL customizing in the IMG and enter the following:

Now it is a matter of implementing the implementation class ZCL_RELATION_DETAILS. The following example shows how to read some details from the standard table BUT051.

method read.

    lr_relation_details type ref to if_genil_container_object,
    ls_record           type ts_record,
    lr_relation         type ref to if_genil_container_object,
    ls_relation_key     type crmt_bupa_il_relation_key.

  "Cast the supplied object to a GenIL container object
  lr_relation_details ?= iv_ref.

  "Attributes requested?
  check lr_relation_details->check_attr_requested( ) = abap_true.

  "Determine the key
  lr_relation_details->get_key( importing es_key = ls_record-key ).
  if ls_record-key is initial.
    "Get the key of the parent object BuilRelationship
    lr_relation = lr_relation_details->get_parent( ).
    lr_relation->get_key( importing es_key = ls_relation_key ).
    move-corresponding ls_relation_key to ls_record-key.

  "Try to read any relation details
  select pavip
    into ls_record-attr
    from but051 up to 1 rows
   where partner1 = ls_relation_key-partner1
     and partner2 = ls_relation_key-partner2
     and date_to  = ls_relation_key-validuntildate
     and reltyp   = ls_relation_key-relationshipcategory.
  check sy-subrc = 0.

  "Initialize the new object
  lr_relation_details->copy_self_with_structure( is_object_key = ls_record-key ).
  lr_relation_details->set_attributes( ls_record-attr ).


Creating completely new Business Objects

And last but not least, it is also possible to create completely new Business Objects:

The advantages of starting with new developments in the Business Layer (it's a choice, you can do a lot in the Presentation Layer as well) is that Business logic is added to the CRM system in a way that is not intertwined with presentation logic and which can be independently tested in the BOL browser. In addition, the Business Objects can also be used in interfaces where no screens are involved at all.

The place to start is again the GenIL customizing in the IMG (CRM » CRM Cross-Application Components » Generic Interaction Layer/Object Layer » Basic Settings) where you can define a new component:

To dress up the new class with the necessary interfaces inherit from base class CL_CRM_GENIL_ABSTR_COMPONENT. Implement method IF_GENIL_APPL_MODEL~GET_OBJECT_PROPS to initialize the model with all the available objects. Some components (like BP) use a customizing table for storage of this information but that's optional, hardcoded works as well:

  "Root object
  ls_obj_props-object_name = 'DemoObject'.
  ls_obj_props-object_kind = if_genil_obj_model=>root_object.
  ls_obj_props-key_struct  = 'ZDEMO_OBJ_KEY'.
  ls_obj_props-attr_struct = 'ZDEMO_OBJ'.
  append ls_obj_props to rt_obj_props.
  clear ls_obj_props.

  "Query object
  ls_obj_props-object_name     = 'DemoObjectSearch'.
  ls_obj_props-object_kind     = if_genil_obj_model=>query_object.
  ls_obj_props-attr_struct     = 'ZDEMO_OBJ_SEARCH'.
  ls_obj_props-result_obj_name = 'DemoObject'.
  append ls_obj_props to rt_obj_props.
  clear ls_obj_props.

If you want to develop your own objects please have a look at document Custom GenIL BOL for Z-tables with Relationships which sums up nicely all the available methods within a GenIL class.

Final thought

Remember the picture with all the layers within the CRM system?

Please choose consciously the correct layer before you start making adjustments

...and hopefully this blog helps in making that choice