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.|
Chatty versus ChunkyWell, 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?"
Compare this to a typical Web Service request:
- WS Client: "Please give a list of orders for the last 14 days"
- 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!"
- 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 codeFirst, 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:
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:
- Call some proxy method for your webservice
- Create BOL objects for the headers
- Store the order items in a buffer
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.
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.