Friday 28 February 2020

ABAP with Coffee and Chain of Responsibility(CoR) OO Pattern

Let me ask you, what do you think about the flow diagram below?

SAP ABAP Tutorial and Material, SAP ABAP Guides, SAP ABAP Certification, SAP ABAP Exam Prep, SAP ABAP Learning

Left most image is often called “spaghetti wiring mess” , second one is an “organized and labelled wiring” and last two are the SAP icons for ‘Code’ and ‘Coffee’ respectively. Obviously I am at the last stage in this flow ! So let me explain a little bit about ‘Code’ while having this ‘Coffee’.

Developing software is fun for coding enthusiasts. It is indeed a lot of fun for experimentation, however when customers pay you for the software you create and maintain, it is a lot of responsibility on the designers and developers to deploy solution without causing maintenance nightmares. This is when the design patterns becomes very handy !

Design Patterns

Design patterns are primarily techniques envisaged by much experienced developers in order to solve common design problems in most efficient way by improving re-usability, maintainability, readability and extensibility of the software. There are many design patterns most developers are familiar with to name a few are – MVC , Factory, Singleton, Observer , Decorator etc. Today I will talk about Chain of Responsibility (CoR) pattern with a use case. Before we start, let me briefly touch upon the widely acclaimed principles in the OO design known as – S.O.L.I.D principles introduced by Robert C Martin (Uncle Bob) .

SOLID Principles

Each letter in ‘SOLID’ represent one principle as explained below –

◉ Single Responsibility Principle
     ◉ A class can undergo changes only due to changes in one part of the specifications; meaning classes shall have unique reason to change
◉ Open-Closed Principle
     ◉ A class shall be open for extension but closed for modification
◉ Liskov substitution principle
     ◉ An object shall be replaceable with instances of its own sub types
◉ Interface segregation principle
     ◉ Many smaller interfaces segregated by their tasks are preferable than one complex interface ; Rely more on composition rather than inheritance
◉ Dependency inversion principle
     ◉ A piece of code shall not get re-compiled due to changes in lower level components; Rely more on abstractions rather than concrete implementation.

With these principles in mind, let’s look at the pattern and the code

CoR Pattern Basics

In CoR pattern a request from caller is propagated through a chain of handlers in which the relevant handlers perform a task in response and calls next handlers until chain is ended.

All request handler classes are derived from a common interface each implementations shall have the handler methods with unique business logic to perform an action.

The reference to next handler instance in the chain is stored in a private variable within each object

SAP ABAP Tutorial and Material, SAP ABAP Guides, SAP ABAP Certification, SAP ABAP Exam Prep, SAP ABAP Learning

CoR pattern is most applicable in the following cases –

◉ A sequence of actions need to be executed upon an event
◉ Number of handlers and the order need to be determined at run time
◉ Handlers business logic is not related to each other
◉ Loose coupling between caller and handlers is desired

Use Case

Let’s say there is a requirement to trigger communication via various channels (email, sms, fax etc.) to business partners . Which channels are needed to be determined at runtime. Adding a new communication channel need to be plug-and-play with optimal testing required and no re-testing for other existing channels. It should be flexible to switch on/off a particular channel communication globally. Adding a new template of message (say new email message format) need to be quick and to be carried out with minimum testing required.

There are different ways to trigger messages, customize message templates etc. in ECC and S/4 by standard. But lets look at it from a custom abap development perspective this time.

How to fit this requirement to CoR pattern?

The crux of the design is an interface say request handler interface as explained above and all the handlers need to implement this interface. We can use factory pattern to instantiate each handler and all the handler classes need to be chained by adding reference to subsequent handlers in the private attribute.

SAP ABAP Tutorial and Material, SAP ABAP Guides, SAP ABAP Certification, SAP ABAP Exam Prep, SAP ABAP Learning

Code Snippets

CoR Request handler interface

INTERFACE zif_cor_request_handler
  PUBLIC .
* Reference to next handler in chain
  DATA o_next_obj TYPE REF TO zif_cor_request_handler .
* Factory method for instantiating chain handler
  CLASS-METHODS get_chain_instance
    IMPORTING
      VALUE(is_variant) TYPE char10 OPTIONAL
    EXPORTING
      VALUE(er_object)  TYPE REF TO zif_cor_request_handler .
* Method to execute handler logic
  METHODS handle_request
    IMPORTING
      !i_header  TYPE zscor_header
      !i_items   TYPE ztcor_items
      !i_control TYPE zscor_control .
  METHODS set_next
    IMPORTING
      !er_next_object TYPE REF TO zif_cor_request_handler .
ENDINTERFACE.

CoR Email handler factory

CLASS zcl_cor_email_handler DEFINITION
  PUBLIC
  CREATE PUBLIC .
  PUBLIC SECTION.
    INTERFACES zif_cor_request_handler .
  PROTECTED SECTION.
  PRIVATE SECTION.
    ALIASES o_next_obj
      FOR zif_cor_request_handler~o_next_obj .
ENDCLASS.

CLASS ZCL_COR_EMAIL_HANDLER IMPLEMENTATION.
* <SIGNATURE>-----------------------------------------------------------------------+
* | Static Public Method ZCL_COR_EMAIL_HANDLER=>ZIF_COR_REQUEST_HANDLER~GET_CHAIN_INSTANCE
* +--------------------------------------------------------------------------------+
* | [--->] IS_VARIANT                     TYPE        CHAR10(optional)
* | [<---] ER_OBJECT                      TYPE REF TO ZIF_COR_REQUEST_HANDLER
* +------------------------------------------------------------------------</SIGNATURE>
  METHOD zif_cor_request_handler~get_chain_instance.
    CASE is_variant.
      WHEN 'BILL'.
        er_object = NEW zcl_cor_email_handler_billing( ).
      WHEN 'DUNN'.
        er_object = NEW zcl_cor_email_handler_dunning( ).
      WHEN 'INFO'.
        er_object = NEW zcl_cor_email_handler_info( ).
      WHEN OTHERS.
        er_object = NEW zcl_cor_email_handler( ).
    ENDCASE.
  ENDMETHOD.

* <SIGNATURE>------------------------------------------------------------------------------+
* | Instance Public Method ZCL_COR_EMAIL_HANDLER->ZIF_COR_REQUEST_HANDLER~HANDLE_REQUEST
* +------------------------------------------------------------------------------------+
* | [--->] I_HEADER                       TYPE        ZSCOR_HEADER
* | [--->] I_ITEMS                        TYPE        ZTCOR_ITEMS
* | [--->] I_CONTROL                      TYPE        ZSCOR_CONTROL
* +----------------------------------------------------------------------------</SIGNATURE>
  METHOD zif_cor_request_handler~handle_request.
    WRITE:/'-> Executing email handler'.
*   <Implement handler logic here>
    IF o_next_obj IS BOUND.
      WRITE:/'........Calling next handler in chain.....'.
      CALL METHOD me->o_next_obj->handle_request
        EXPORTING
          i_header  = i_header
          i_items   = i_items
          i_control = i_control.
    ENDIF.
  ENDMETHOD.

* <SIGNATURE>--------------------------------------------------------------------------+
* | Instance Public Method ZCL_COR_EMAIL_HANDLER->ZIF_COR_REQUEST_HANDLER~SET_NEXT
* +-------------------------------------------------------------------------------+
* | [--->] ER_NEXT_OBJECT                 TYPE REF TO ZIF_COR_REQUEST_HANDLER
* +-------------------------------------------------------------------------------</SIGNATURE>
  METHOD zif_cor_request_handler~set_next.
    me->o_next_obj = er_next_object.
  ENDMETHOD.
ENDCLASS.

CoR Email handler (Billing)

Subclass ‘zcl_cor_email_handler’ and re-define the ‘handle_request’ method

class ZCL_COR_EMAIL_HANDLER_BILLING definition
  public
  inheriting from ZCL_COR_EMAIL_HANDLER
  create public .

public section.

  methods ZIF_COR_REQUEST_HANDLER~HANDLE_REQUEST
    redefinition .
protected section.
private section.

  aliases O_NEXT_OBJ
    for ZIF_COR_REQUEST_HANDLER~O_NEXT_OBJ .
ENDCLASS.

CLASS ZCL_COR_EMAIL_HANDLER_BILLING IMPLEMENTATION.
* <SIGNATURE>----------------------------------------------------------------------------+
* | Instance Public Method ZCL_COR_EMAIL_HANDLER_BILLING->ZIF_COR_REQUEST_HANDLER~HANDLE_REQUEST
* +--------------------------------------------------------------------------------------+
* | [--->] I_HEADER                       TYPE        ZSCOR_HEADER
* | [--->] I_ITEMS                        TYPE        ZTCOR_ITEMS
* | [--->] I_CONTROL                      TYPE        ZSCOR_CONTROL
* +-------------------------------------------------------------------------------</SIGNATURE>
  METHOD zif_cor_request_handler~handle_request.
    WRITE:/'-> Executing email handler (Billing)'.
*   <Implement handler logic here>
    IF me->o_next_obj IS BOUND.
      WRITE:/'........Calling next handler in chain.....'.
      CALL METHOD o_next_obj->handle_request
        EXPORTING
          i_header  = i_header
          i_items   = i_items
          i_control = i_control.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

Similarly implement all other classes for sms, fax etc.

CoR Chain master control table

Create a table with appropriate data elements and maintain entries as shown below

SAP ABAP Tutorial and Material, SAP ABAP Guides, SAP ABAP Certification, SAP ABAP Exam Prep, SAP ABAP Learning

Triggering program

Create a test triggering program as shown below

REPORT ztrigger_cor_request.

* CoR Chain related control table
DATA lt_controls TYPE STANDARD TABLE OF ztcor_controller.
DATA wa_control TYPE ztcor_controller.
* Holds CoR Objects to build chain
DATA o_corobj TYPE REF TO zif_cor_request_handler.
DATA o_cormain TYPE REF TO zif_cor_request_handler.
DATA o_nextobj TYPE REF TO zif_cor_request_handler.

DATA lv_variant TYPE char10 VALUE 'BILL'.
* Header, item and control parameters for handlers to implement logic
DATA wa_header TYPE zscor_header.
DATA lt_items TYPE ztcor_items.
DATA wa_contr TYPE zscor_control.

SELECT * FROM ztcor_controller INTO TABLE lt_controls WHERE active = 'X'.

LOOP AT lt_controls INTO wa_control.

  IF o_corobj IS NOT BOUND.
    CALL METHOD (wa_control-handler_class)=>zif_cor_request_handler~get_chain_instance
      EXPORTING
        is_variant = lv_variant
      IMPORTING
        er_object  = o_corobj.
    o_cormain = o_corobj.
  ELSE.
    CALL METHOD (wa_control-handler_class)=>zif_cor_request_handler~get_chain_instance
      EXPORTING
        is_variant = lv_variant
      IMPORTING
        er_object  = o_nextobj.
    CALL METHOD o_corobj->set_next EXPORTING er_next_object = o_nextobj.

    o_corobj = o_nextobj.
  ENDIF.
ENDLOOP.

WRITE:/ 'Entering handler chain..'.
CALL METHOD o_cormain->handle_request 
EXPORTING i_header = wa_header i_items = lt_items i_control = wa_contr .
WRITE:/ '.. Handler chain Finished'.

Output

Trigger COR request - Testing

Entering handler chain..
-> Executing email handler (Billing)
........Calling next handler in chain.....
-> Executing fax handler
........Calling next handler in chain.....
-> Executing sms handler
.. Handler chain Finished

Extending the application

◉ Let’s assume in future , we need to add a new communication channel say – social media . We can achieve this by
     ◉ Create a new social media handler class referring to the request handler interface and implement the corresponding business logic in ‘handle_request’ method
     ◉ Add the channel configuration to the control table

Testing requirement – New class and its functionality only as none of other code re-compiles

◉ Let’s say we need to add a new email message template for ‘Escalation messages’. We can achieve this by –
     ◉ Subclass the email handler class and re-define the ‘handle_request’ method alone.
     ◉ Add the logic to create new handler class when criteria met.

Testing requirement – Required only for newly created class and its logic and it’s factory

Pros and Cons of CoR Pattern


Pros

◉ Flexible to add, remove and extend the handlers in the chain

◉ A runtime determination of handlers and sequencing is possible

Cons

◉ The caller have to wait until all the handlers in the chain has finished execution

◉ Every handler need to implement all the interface methods at least in main handler class

No comments:

Post a Comment