Wednesday, July 31, 2013

Unlocking the web with GenIL/BOL

Last week Pieter gave a nice overview of the MVC layers in CRM. And Olaf gave a very good overview earlier on the BOL/GenIL. Today we are going to dive into the BOL/GenIL by making it do stuff it isn't supposed to do.

As you might have read in the blog of last week, the MVC concept is about separating concerns and hiding implementation details. In the context of BOL/GenIL* and the Web UI, the BOL feeds the model of the Web UI with data, and the layers in the Web UI should have no notion of where this data is coming from. It should not matter whether the data supplied by the BOL:
  • Is read from the local CRM database;
  • Is retrieved by a cool Web service or API;
  • Is being delivered by pigeons.
Please do not incorporate pigeons in your software architecture. It won't help.
NO: You definitely should not feed BOL/GenIL objects with pigeon-based data sources, but surely a Web Service would work effortles… Or would it?

Chatty versus Chunky

Well, it turns out there is some catch, as always: The way the BOL and GenIL layers interact is quite chatty and is focussed on retrieving only the bits of data that are needed in the current interaction:
  • BOL consumer: "Please give me a list of orders for the last 14 days"
  • BOL/GenIL: "There are 42 orders in the result lists. Here are the technical keys for those objects
  • BOL consumer: "Cool! Please show me the attributes of the first order"
  • BOL/GenIL: "There you go, a single record"
  • BOL consumer: "OK! Does the order have any items?"
And so on… As you can see, communication is very fine grained, only detailing with the data that is actually requested.

Compare this to a typical Web Service request:
  • WS Client: "Please give a list of orders for the last 14 days"
<Long pause>
  • WS Provider: "Here are all the details of the 42 orders I found. I have included all I could find: Order details, all 148 order items, I even included some 187 Schedule Lines."
  • WS Client: "Thanks!"
This sequence is far more Chunky, with a lot more details in the request. Of course both communication styles have their place:
  • The chatty style is good when communication overhead is low and speed is important;
  • The chunky style works best when there is lot of communication overhead.

Chatty and Chunky: Funky!

The interesting bit comes when you want to combine them: What do we do when we want to expose a Web service with BOL layer?

I'm going to show an implementation that employs buffering in the GenIL implementation classes to translate between the two communication styles. We'll employ the GenIL Handler concept so we can create nice small classes with clearly separated responsibilities.

I won't dive into the details of web service communication as we have covered that here and there already. I also assume knowledge of BOL/GenIL.

Show me the code

First, we'll define some models in transaction GENIL_MODEL_BROWSER:

When you subclass your component class from CL_WCF_GENIL_ABSTR_COMPONENT, you can model all your objects in GENIL_MODEL_BROWSER:

Next we'll define some handlers that are responsible for the implementation:

The responsibilities are divided as follows:
  • The query handler (ZCL_DEMO_QUERY_HANDLER) is responsible for calling the actual web service and creating the Order Header BOL entitiies
  • The order item handler (ZCL_DEMO_ITEM_HANDLER) is responsible for returning the items when the order items relation is requested.**
  • The Component class (ZCL_DEMO_IL) is responsible for containing the buffered Order Items. Both handler objects always have a reference to the Component Handler class, so that makes it a convenient place.

Show me the code already!

Ok, here is the code:

First the Query implementation:

METHOD if_genil_node_handler_query~get_query_result.

  FIELD-SYMBOLS: <order> type zdemo_order_header.

  DATA: ls_parameters TYPE zdemo_order_header.
  DATA: lt_orders     TYPE zcl_dummy_proxy=>tt_orders.
  DATA: lt_items      TYPE zcl_dummy_proxy=>tt_items.
  data: lo_component  type ref to ZCL_DEMO_IL.

  CALL METHOD zcl_dummy_proxy=>get_orders
      is_input  = ls_parameters
      et_orders = lt_orders
      et_items  = lt_items.

* Create BOL objects for found headers
  loop at lt_orders ASSIGNING <order>.
      iv_object_name := 'zDemoOrder'
      is_object_key  := <order> )->set_attributes( <order> ).


* Buffer the found items for later use
  lo_component ?= parent_component.
  lo_component->items = lt_items.


As you can see, the implementation is straigthforward:
  1. Call some proxy method for your webservice
  2. Create BOL objects for the headers
  3. Store the order items in a buffer
Second, the item class:

method get_keys_by_parent.

    lo_parent_component type ref to zcl_crmpi_il,
    lo_cast_error type ref to cx_sy_move_cast_error.

  data: ls_order_key type zdemo_order_key.

  field-symbols: <item> type zcl_crmpi_il=>ts_my_order_item.

  iv_parent->get_key( importing es_key = ls_order_key ).

  lo_parent_component ?= parent_component.

  loop at lo_parent_component->items assigning <item> 
    where order_id = ls_order_key-order_id.

    append <item>-key to ct_child_keys.


method get_attributes.

    lo_parent_component type ref to zcl_demo_il,

  lo_parent_component ?= parent_component.

  field-symbols: <item> type zdemo_order_item.

  read table lo_parent_component->items assigning <item> with key key = is_key binary search.

  if sy-subrc = 0.
    iv_cont_obj->set_attributes( <item> ).


When the relation is traversed, the item class reads its keys and attributes based on the given parent object and returns them. That's all custom code you need. All other BOL/GenIL related logic is handled by the employed base classes.

So next time anyone asks you to unlock a chunky Service or API with a chatty Model layer, you know just what to do!

And remember: If your lead developer is proposing POA (Pigeon Oriented Architecture), it's best to promote him/her to management.
*: I'm talking about BOL/GenIL as if it were a single layer. This is a simplification.

**: This could also have been implemented by an order header handler. The handler concept has a mechanism I like: It will first ask the Order header object if  it knows how to handle the relationship request. If it doesn't, the default implementation will automatically ask the Order Item wether it knows how to handle the request.