Getting the Brownfield clean, but not green – Part II

Initial situation

In my previous blog post, I introduced an architecture which could be the backbone of a historically grown application. Certain design issues, called smells, were introduced which could prevent a new developer in the project from its main task, which is adding certain extra functionality to the existing application. Certain smells, that contradict an evolvable architecture, need to be eliminated. This process of improving the existing code before applying new functionality is called Refactoring, which is the subject of today’s blog post.
A good definition for an entity in this blog can be found in Domain Driven Design by Eric Evans. In short, an entity can be identified by its key and is an independent object that usually has a real life representation, as this is the case for movable objects or storage bins and even activities as they are a business document.
A historically grown application which tracks movements of objects through storage bins could consist of the following three entities:

  • Movable object
  • Storage bin
  • Movement activity

The main task of this example application is to keep track of the current storage bin of the moveable objects and their movement activities in the past. So here is the simplified class diagram:

Sample Legacy Application

Tell, don’t ask

ZCL_MOVEMENT_ACTIVITY expects a movable object as well as a destination to work properly. This data is passed as a flat structure via the constructor and the method +SET_DESTINATION(IS_DESTINATION : Z_STORAGE_BIN) . The intention is probably to provide some data sources in order to allow ZCL_MOVEMENT_ACTIVITY make some assumptions about the data it got.
This is not a good practice.

Providing structures as method input is a common anti-pattern in ABAP development. The habit certainly comes from programming with function modules, where your only opportunity to hand over complex data structures was to provide flat or nested structures or even internal tables.
In ABAP OO, you don’t need to do it. Think of structures differently: Objects are better structures since the can include basically the same data that can be included also by structures, with the difference of adding behavior to them. This behavior could also be restricted, which means that certain fields in the structure may not be accessible. This behavior could be validation which is processed before a certain field in the structure is populated.
The appropriate class in the example would be ZCL_STORAGE_BIN. As you can see, ZCL_STORAGE_BIN defines the attribute MS_STORAGE_BIN. In addition you can later add certain instance methods like IS_BLOCKED( ) or CHECK_DEST4MOVEABLE_OBJ(IO_MOVEABLE_OBJCT: ZCL_MOVEABLE_OBJECT) in order to support quantity checks or checks, if the storage bin may serve as a destination for an object’s movement. The method’s signature wouldn’t even need to change.
So instead of interpreting the data you got in your method, you are now telling the passed object to return some data to you which is needed for processing. Since the only data which is accessible for you can be accessed by the object’s methods (besides public attributes, never ever do that!), data encapsulation is far better in comparison to use simple structures, which cannot transfer any logic at all. A well written class is a pleasure for the developers who need to work with it. However, a badly written class may still expose to much information to it’s callers, but this is another smell.
Solution: The best approach would be to completely replace the signature of +SET_DESTINATION(IS_DESTINATION : Z_STORAGE_BIN)
With
+SET_DESTINATION(IO_DESTINATION: ZCL_STORAGE_BIN)
Inside the method, call appropriate Get-Methods of the passed object, or the common Is-Methods like IS_BLOCKED( ).
If this is not possible, introduce another, new method which is subject to future developments. The old method is marked as OBSOLETE in the comments or method’s description or both. Please do not forget to also include a hint of where the new method is located since one of the most annoying issues for a developer is to use an obviously deprecated method, which lacks the link to its newer version.
If the structure Z_STORAGE_BIN contains the storage bin key, you could consider another solution. The old method +SET_DESTINATION(IS_DESTINATION : Z_STORAGE_BIN) will remain as it is. But internally, you call a factory or repository to retrieve the storage bin’s object representation by its key attributes.

Storage Bin Repository

The class diagram currently gives you no hint about instance creation control patterns. You could consider to implement the repository as a Singleton, but this would usually prevent IoC Containers to create the repository for you as the contructor is usually made protected in these patterns. IoC Containers may bring you huge advantages when it comes to unit tests but this topic deserves its own section.

Don’t repeat yourself

Any logic hat has a natural relevance to the entity where it is related to should need to be implemented closely to the specific class which implements this entity.
This also applies to a status check on the movable object, wether it is blocked for movement activities or not. Don’t try to make this decision outside of the class ZCL_MOVEABLE_OBJECT, based on a STATUS-field in Z_MOVEABLE_OBJECT which may be retrieved by IV_MOVEABLE OBJECT in the constructor of ZCL_MOVEMENT_ACTIVITY.
Even if the status attribute only contains two possible values, “B” for “Blocked” and “R” for “Ready”, don’t do that. Playing with fire usually leads to burned fingers.
Instead, try to get an instance of type ZCL_MOVEABLE_OBJECT as fast as possible. This could happen by a method parameter or, again, a repository lookup. Implement the interpretation upon the status where it belongs to (ZCL_MOVEABLE_OBJECT).
To come back to the topic of this section: Interpreting the object’s status outside of the object’s main class usually leads to other code fragments where this interpretation is done again as the main class lacks appropriate methods. This way, you are starting to repeat yourself.

Single responsibility principle

Take a look at the three classes again. Is there anything they have in common?
You might recognize the SAVE() Method at the end of each methods list.
So what are the classes are intended to do?

  • ZCL_MOVEABLE_OBJECT: carry moveable objects data and read it from DB and store it somewhere
  • ZCL_MOVEMENT_ACTIVITY: perform movements and store these movements somewhere
  • ZCL_STORAGE_BIN: carry storage bin data and store it somewhere

Found the smell?

Entity objects should not take care about how they are stored somewhere, for example in the database.
This is not because we like to switch from one database to another in the near future (despite the fact that ABAP offers you very good Open SQL support).
Instead, coupling entities like ZCL_MOVEABLE_OBJECT to the way they are stored usually leads to another issue: Caching requirements will likely be also implemented in ZCL_MOVEABLE_OBJECT.
This is where ZCL_MOVEABLE_OBJECT starts to grow and to serve more than just one purpose.
It is better to leave this task to repositories, as mentioned before. Repositories control the way, how entity objects are created or stored to the database. Hence, our SAVE-implementations should move to their appropriate repositories.

Storage Bin Repository with SAVE-method

Testability

Repositories manage the access to entities. Usually, they are seldom referenced by entities, since the classes that implement these entities should expect other entity instances wherever possible. Sometimes you cannot get an instance directly, for example if the method’s signature needs to stay the same and expects only the ID of the referenced entity from its callers.
Take a look at +SET_DESTINATION(IS_DESTINATION : Z_STORAGE_BIN) of ZCL_MOVEMENT_ACTIVITY again. In case, the key of the storage bin is already included in the structure Z_STORAGE_BIN and you have a repository for this entity, try to ask the repository for an instance inside the method.
However, the repository needs to be registered in the activity instance before. This could happen either via constructor injection or via a RESOLVE_DEPENDENCIES( ) – method which can be called in the constructor.
This method may acquire the instance of the storage bin repository by either calling the constructor, a factory or a static method of ZCL_STORAGE_BIN_REPOSITORY which implements the singleton pattern. If you share only one instance of ZCL_STORAGE_BIN_REPOSITORY in your application you may also utilize caching of objects on instance level.
In order to unit test ZCL_MOVEMENT_ACTIVITY you will need to have sample storage bins as well as sample movable objects. Creating these storage bins and moveable objects will likely cause you to setup database table entries for each test which is quite a big effort.
Instead, it is usually a far better idea, to describe repositories by specific interfaces.
Storage Bin Repository with interface

This way you can access the repository by its interface, not caring about any implementation details in your client’s classes. The implementation behind this interface can be easily mocked either by using a mocking framework or manually.
On the other side, IoC Containers, may serve as service locators in a RESOLVE_DEPENDENCIES( )-method and replace the productive implementation of a repository by a mocked repository in a unit test. The mocked repository will eventually always returns the same instance of a storage bin, no matter what is really stored in the database.
Warning: using IoC Containers as pure service locators is quite dangerous as you would start to call the container all over the place. Instead it is better idea to declare every dependency via dependency injection (CONSTRUCTOR- or SETTER-based) in the entity. This dependency will be satisfied by the entity’s repository, which in turn, declares its own dependencies, for example other repositories. The goal of IoC Containers is to create the backend objects like repositories and satisfy their dependencies automatically for you. In a clean design there is no need to create entities with the help of IoC Containers, because there are already repositories for this task, but this would certainly be a topic for a future blog post.

Result

In the end, if all client calls can be adjusted, the resulting UML diagram could look like this. Of course such a result would be the optimal and most optimistic scenario, but in most of the cases you can come close to this result by utilizing RESOLVE_DEPENDENCY( ) methods as described in the previous sections, without changing the old API at all. This solution wouldn’t be clean, but much better than the current design and certainly more robust.
The goal is not, to change the design of a working application without specific purpose. But in case enhancements would have to be done, it is often better to improve the design where you see obstacles in implementing the desired functionality in the first step.
Afterwards, the enhancement is implemented even faster and more robust, with less testing effort and side effects. New developers understand more easily the task of each class, at the cost of a higher learning curve at the beginning in order to get the whole picture of entity classes, repositories and cross-cutting concerns like IoC Containers or mocking frameworks.
Eventually, unit testing of certain components becomes easier. These unit tests contribute to both software quality as well as even a bit of documentation as you are testing an object in unit tests the way it is intended to be used (respectively not used).
Sample Legacy Application after Refactoring

Advertisements

Getting the Brownfield clean, but not green – Part I

Initial situation

Imagine a legacy application which has grown over the past few years. It has evolved for generations, maintained by several generations of developers. While the first generation of developers had a clear vision of what the application should do and what the design should be, this vision was lost somewhere in between. The application cannot be understood easily and specific documentation would be needed to understand implementation details. As developers often run out of time, this documentation is not existent at all.
As a new developer in an existing application, you often face this situation. If the application’s structure is not clear you will have a hard time to figure out what the core concepts are and how to make enhancements to the existing code.
A historically grown application which tracks movements of objects through storage bins could consist of the following three entities:

  • Movable object
  • Storage bin
  • Movement activity

The main task of this example application is to keep track of the current storage bin of the moveable objects and their movement activities in the past. So here is the simplified class diagram:
Sample Legacy Application
This class diagram looks quite handy at the beginning. In the next chapters I’m going to show what went wrong and what makes future enhancement unnecessarily complicated.

Tell, don’t ask

ZCL_MOVEMENT_ACTIVITY expects a movable object as well as a destination to work properly. This data is passed as an ID of the movable object via the constructor (which eventually leads to a flat structure Z_MOVEABLE_OBJECT) and the method +SET_DESTINATION(IS_DESTINATION : Z_STORAGE_BIN) . The intention is probably to provide some data sources in order to allow ZCL_MOVEMENT_ACTIVITY make some assumptions about the data it got.
This is not a good practice.
Example: If there was an object status which indicates of the object is blocked for any movements, the activity class needs to read the status from the structure Z_MOVEABLE_OBJECT. Also, it needs to make a decision based on this status, if the object is blocked for movements or not.
If there was another class, which would need to list all blocked moveable objects, it would need to perform this interpretation on the status field again und you would probably need to copy the status interpretation logic into some other method. This also violates the “Don’t repeat yourself” principle.
This way, you make assumptions on foreign objects. Instead of telling, you ask for something and interpret the object’s data somewhere outside of the object.

Don’t repeat yourself

As mentioned before, don’t repeat yourself. This also applies to the logic which needs to evaluate the structure Z_STORAGE_BIN. Again, you are getting inveigled to make some assumptions about another object. Whenever you want to perform the assumption and an interpretation about that again, the fastest way to do it is to copy it, which is a clear smell.

Example: If the storage bin is completely reserved with other movements, is something that needs to be retrieved from the data passed in Z_MOVEABLE_OBJECT, either directly or indirectly.
If there is no central interpretation logic for this, the logic will need to be implemented wherever it is needed – you are starting to repeat yourself.

Single responsibility principle

Take a look at the three classes again. Is there anything they have in common?
You might recognize the SAVE() Method at the end of each methods list.
So what are the classes are intended to do?
ZCL_MOVEABLE_OBJECT: carry moveable objects data and read it from DB and store it somewhere
ZCL_MOVEMENT_ACTIVITY: perform movements and store these movements somewhere
ZCL_STORAGE_BIN: carry storage bin data and store it somewhere

Found the smell?
Everytime you need to use an “and” in your class’ description, there could be smell. In fact, in all of these three classes, there is more than one responsibility for each of them: Besides their core competency, they are obviously also caring about the persistence of their data, in most of the cases this is where some update modules are called or open SQL statements are called directly.

Inappropriate Intimacy

Take a look at ZCL_MOVEMENT_ACTIVITY. What would need to be done if the class was interested in the current storage bin of the moveable object passed to it’s constructor via it’s ID? In case this information was included in the structure Z_MOVEABLE_OBJECT, which can be loaded using the method LOAD_DATA in ZCL_MOVEABLE_OBJECT, we would be fine.
But more often, these data structures directly inherit from database tables, and often, the bin status report would have its own database table.

This means, as a lack of appropriate access functionality in the current class diagram, the developer would certainly read the data directly from the database table. The developer would make assumptions about the implementation of the method +SET_CURRENT_STORAGE_BIN(IS_STORAGE_BIN : Z_STORAGE_BIN) from outside of the core class ZCL_MOVEABLE_OBJECT. This is a clear sign of inappropriate intimacy. ZCL_MOVEMENT_ACTIVITY would be dependent of implementation details of ZCL_MOVEABLE_OBJECT.
Often this smell regularly leads to another smell, “Don’t repeat yourself”, since this retrieval logic is often just copied wherever needed.

Testability

At some point of time you may decide to unit test ZCL_MOVEMENT_ACTIVITY. You figure out, that it expects an object ID in the constructor which likely leads to a call to ZCL_MOVEABLE_OBJECT=>LOAD_DATA(…) in order to retrieve the data which is needed to get the moveable object.
If ZCL_MOVEABLE_OBJECT=>LOAD_DATA(…) is not called, there needs to be database SELECT for the moveable object which is even worse.
In any case, just to pass a moveable object to the test, you need to prepare the database in the SETUP routine and clean it up in the TEARDOWN routine. This is hard work compared to the benefit you expect from a unit test.
Uli once told me, a good architecture is quite often an architecture which can be easily tested and I think this guy is right.

Conclusion

You have seen three classes and at least five different smells. The list of smells is not a complete list, there could be more, but this would go beyond the scope of this blog post. In real life applications you easily have several hundreds of classes. Every smell decreases reusability of your application’s components, makes it more and more inflexible and in the end very hard to maintain. This is where bugs may arise quite easy.
So what should we do? Throwing the application away is not an option. There will be no green field where we can start from scratch. Our options are refactoring and introducing unit tests which also serve as documentation for the behavior of specific functionalities to some extent. We need to get the brownfield clean, but not green. But this would be the subject of my next blog post.

Method invocation queue

Popups in Webdynpro for ABAP are annoying

Recently I had the pleasure to show one of these “Are you sure?” popups in the UI. The issue with popups in webdynpro is, due to the web based architecture, the fact that there is no real synchronous program flow as we were used to in SAP GUI.
Instead of stopping the program flow when a popup is shown, the Webdynpro runtime continues in order to fulfill the HTTP web request.
This means, instead of receiving the result of the user’s decision right after the popup call, we usually need to implement callback methods which are called when the user presses either “Ok”, “No” and so on.
In the sample this would be the view’s method ONACTIONAPPLY_CONFIRM.

  1. DATA lo_comp_api TYPE REF TO if_wd_component.
  2. DATA lo_controller_api TYPE REF TO if_wd_controller.
  3. DATA ls_config TYPE wdr_popup_to_confirm.
  4. DATA: lt_text_table TYPE string_table,
  5.           lv_text_table TYPE string,
  6.           lv_title TYPE string.
  7. ls_config-button_cancel-enabled = abap_false.
  8. lo_controller_api = wd_this->wd_get_api( ).
  9. lo_comp_api = wd_comp_controller->wd_get_api( ).
  10. lv_title = 'Are you sure?'.
  11. lv_text_table = 'Really???'.
  12. APPEND lv_text_table TO lt_text_table.
  13. TRY.
  14.           CALL METHOD cl_wd_popup_factory=>popup_to_confirm
  15.           EXPORTING
  16.             component        = lo_comp_api
  17.             text             = lt_text_table
  18.             window_title     = lv_title
  19.             configuration    = ls_config
  20.           RECEIVING
  21.             popup_to_confirm = wd_this->mo_popup.
  22.         wd_this->mo_popup->subscribe_to_events(
  23.           controller = lo_controller_api
  24.           handler_name = 'ONACTIONAPPLY_CONFIRM' ).
  25. *retrieve backend call parameters
  26. DATA lv_p1 TYPE i.
  27. DATA lv_p2 TYPE string.
  28. *...retrieve parameters
  29. *store backend call parameters in the view's attributes
  30. wd_this->mv_p1 = lv_p1.
  31. wd_this->mv_p2 = lv_p2.


Did you recognize the last few lines? Actually, what is done here is the preparation of the backend call which is to be executed depending on the user’s decision. The method parameters are already stored in some attributes and read for later purpose.
In the callback method, the backend invocation might look like the code below.

  1. METHOD ONACTIONAPPLY_CONFIRM.
  2.   CHECK wd_this->mo_popup->answer = if_wd_popup_to_confirm=>co_button_1_pressed.
  3.   wd_this->mo_some_weird_backend->perform_some_weird_operation( iv_p1 = wd_this->mv_p1 iv_p2 = wd_this->mv_p2 )
  4. ENDMETHOD.


Every parameter that will conditionally passed to the backend will have to be stored somewhere.
You do this kind of work one time, maybe two times or three times and you are going to be annoyed at it.
Couldn’t this behaviour be achieved easier and more conventiently?

What is a method invocation queue?

A method invocation queue records method calls for later execution. Instead of storing parameters for later usage, you will store the objects and their method calls including parameters for later usage. As ABAP still lacks function pointers we will need to implement it manually.
Compare the above shown coding, starting at line 29 with this one.

  1. *retrieve backend call parameters and record backend calls
  2. DATA lv_p1 TYPE i.
  3. DATA lv_p2 TYPE string.
  4. *...retrieve parameters
  5. wd_this->mo_method_queue = zcl_method_queue=>new_method_invocation( ).
  6. wd_this->mo_method_queue->with( wd_this->mo_some_weird_backend )->on( 'perform_some_weird_operation' ).
  7. wd_this->mo_method_queue->add_parameter( iv_name = 'iv_p1' iv_value = lv_p1 ).
  8. wd_this->mo_method_queue->add_parameter( iv_name = 'iv_p2' iv_value = lv_p2 ).


Looks more complicated at the beginning. But the big advantage of this concept is, that you will now only have to keep track of the method invocation queue, not method parameters. No further attributes are required.
Actually, using the fluent API, you could write lines 6 to 8 in just one single line.

The popup’s callback method now looks like this.

  1. METHOD ONACTIONAPPLY_CONFIRM.
  2.   CHECK wd_this->mo_popup->answer = if_wd_popup_to_confirm=>co_button_1_pressed.
  3.   wd_this->mo_method_queue->flush( ).
  4. ENDMETHOD.

Interested?

So here is the coding. Please be aware, this is just experimental and not meant for productive usage.
This code lacks the support of any other parameter type than IMPORTING parameters as well as proper exception handling. However you may consider to enhance it and write about what you found out 😉

  1. CLASS zcl_method_queue DEFINITION
  2.    PUBLIC
  3.    FINAL
  4.    CREATE PROTECTED .
  5.    PUBLIC SECTION.
  6.      TYPE-POOLS abap .
  7.      CLASS-METHODS new_method_invocation
  8.        RETURNING
  9.          value(ro_method_queue) TYPE REF TO zcl_method_queue .
  10.      METHODS with
  11.        IMPORTING
  12.          !io_caller TYPE REF TO object
  13.        RETURNING
  14.          value(ro_method_queue) TYPE REF TO zcl_method_queue .
  15.      METHODS on
  16.        IMPORTING
  17.          !iv_method TYPE seocpdname
  18.        RETURNING
  19.          value(ro_method_queue) TYPE REF TO zcl_method_queue .
  20.      METHODS add_parameter
  21.        IMPORTING
  22.          !iv_name TYPE abap_parmname
  23.          value(iv_value) TYPE any
  24.        RETURNING
  25.          value(ro_method_queue) TYPE REF TO zcl_method_queue .
  26.      METHODS finalize .
  27.      METHODS flush
  28.        RAISING
  29.          zcx_method_queue .
  30.    PROTECTED SECTION.
  31.      TYPES:
  32.        BEGIN OF ty_s_method_invocation,
  33.          caller TYPE REF TO  object,
  34.          method TYPE  seocpdname,
  35.          parameters TYPE  abap_parmbind_tab,
  36.        END OF ty_s_method_invocation .
  37.      DATA ms_method_invocation TYPE ty_s_method_invocation .
  38.      DATA:
  39.        mt_method_invocations TYPE TABLE OF ty_s_method_invocation .
  40.      METHODS copy_value
  41.        IMPORTING
  42.          !ir_ref TYPE REF TO data
  43.        RETURNING
  44.          value(rr_ref) TYPE REF TO data .
  45.      METHODS constructor .
  46.    PRIVATE SECTION.
  47.  ENDCLASS.
  48.  CLASS ZCL_METHOD_QUEUE IMPLEMENTATION.
  49.    METHOD add_parameter.
  50.      ro_method_queue = me.
  51.      DATA ls_parameter TYPE abap_parmbind.
  52.      DATA lr_value TYPE REF TO data.
  53.      ls_parameter-name = iv_name.
  54.      TRANSLATE ls_parameter-name TO UPPER CASE.
  55.      ls_parameter-kind = cl_abap_objectdescr=>exporting.
  56.      GET REFERENCE OF iv_value INTO lr_value.
  57.      ls_parameter-value = copy_value( lr_value ).
  58.      INSERT ls_parameter INTO TABLE ms_method_invocation-parameters.
  59.    ENDMETHOD.                    "add_parameter
  60.    METHOD constructor.
  61.    ENDMETHOD.                    "CONSTRUCTOR
  62.    METHOD copy_value.
  63.      FIELD-SYMBOLS <lv_in> TYPE any.
  64.      FIELD-SYMBOLS <lv_out> TYPE any.
  65.      ASSIGN ir_ref->* TO <lv_in>.
  66.      CREATE DATA rr_ref LIKE <lv_in>.
  67.      ASSIGN rr_ref->* TO <lv_out>.
  68.      <lv_out> = <lv_in>.
  69.    ENDMETHOD.
  70.    METHOD finalize.
  71.      CHECK: ms_method_invocation-caller IS NOT INITIAL AND ms_method_invocation-method IS NOT INITIAL.
  72.      APPEND ms_method_invocation TO mt_method_invocations.
  73.      CLEAR: ms_method_invocation.
  74.    ENDMETHOD.
  75.    METHOD flush.
  76.      FIELD-SYMBOLS <ls_call> TYPE ty_s_method_invocation.
  77.      DATA lo_cx_root TYPE REF TO cx_root.
  78.      DATA lo_objectdescr TYPE REF TO cl_abap_classdescr.
  79.      DATA lo_cx_sy_dyn_call_illegal_meth TYPE REF TO cx_sy_dyn_call_illegal_method.
  80.      DATA ls_method TYPE abap_methdescr.
  81.      DATA lv_has_method_been_called TYPE abap_bool.
  82.      DATA lv_count TYPE i.
  83.      DATA lt_method_parts TYPE string_table.
  84.      DATA lv_method_name TYPE string.
  85.      finalize( ).
  86.      LOOP AT mt_method_invocations ASSIGNING <ls_call>.
  87.        TRY.
  88.            CALL METHOD <ls_call>-caller->(<ls_call>-method) PARAMETER-TABLE <ls_call>-parameters.
  89.          CATCH cx_sy_dyn_call_illegal_method INTO lo_cx_sy_dyn_call_illegal_meth.
  90.  *         no such method - try to call named method in any interface that the current object might implement
  91.            lv_has_method_been_called = abap_false.
  92.            lo_objectdescr ?= cl_abap_classdescr=>describe_by_object_ref( <ls_call>-caller ).
  93.            LOOP AT lo_objectdescr->methods INTO ls_method.
  94.              TRY.
  95.                  lv_method_name = ls_method-name.
  96.                  SPLIT lv_method_name AT '~' INTO TABLE lt_method_parts.
  97.                  FIND <ls_call>-method IN TABLE lt_method_parts MATCH COUNT lv_count.
  98.                  IF lv_count > 0.
  99.                    CALL METHOD <ls_call>-caller->(ls_method-name) PARAMETER-TABLE <ls_call>-parameters.
  100.                    lv_has_method_been_called = abap_true.
  101.                    EXIT.
  102.                  ENDIF.
  103.                CATCH cx_root INTO lo_cx_root.
  104.                  CONTINUE.
  105.              ENDTRY.
  106.            ENDLOOP.
  107.  *         has method been found?
  108.            IF lv_has_method_been_called = abap_false.
  109.              RAISE EXCEPTION TYPE zcx_method_queue
  110.                EXPORTING
  111.                  previous = lo_cx_root
  112.                  method   = <ls_call>-method.
  113.            ENDIF.
  114.          CATCH cx_root INTO lo_cx_root.
  115.            RAISE EXCEPTION TYPE zcx_method_queue
  116.              EXPORTING
  117.                previous = lo_cx_root
  118.                method   = <ls_call>-method.
  119.        ENDTRY.
  120.      ENDLOOP.
  121.    ENDMETHOD.                    "flush
  122.    METHOD new_method_invocation.
  123.      CREATE OBJECT ro_method_queue.
  124.    ENDMETHOD.                    "new_method_invocation
  125.    METHOD on.
  126.      ro_method_queue = me.
  127.      ms_method_invocation-method = iv_method.
  128.      TRANSLATE ms_method_invocation-method TO UPPER CASE.
  129.    ENDMETHOD.                    "on
  130.    METHOD with.
  131.      ro_method_queue = me.
  132.      IF ms_method_invocation-caller IS NOT INITIAL AND ms_method_invocation-method IS NOT INITIAL.
  133.        finalize( ).
  134.      ENDIF.
  135.      ms_method_invocation-caller = io_caller.
  136.    ENDMETHOD.                    "with
  137.  ENDCLASS.

Drawbacks of this approach

However, there’s no such thing as a free lunch.
If you decide to use such a method invocation queue, you lose the where-used-list support of your backend’s methods.
Also, you can only access real backend methods and not component controller methods, as they are accesssible for their views or windows, but not for any other objects, unless these methods are declared public.

So stay careful, be watchful and enjoy…

Wiring heterogeneous feeder models together – or the fairytale of Hänsel and Gretel

Introduction

Recently I was asked as a reply to one of my previous blog posts, wether it is possible to wire multiple UI building blocks to one target. The target could be a transaction component in order to save all of these models when the users presses the “Save” button in the UI.
The exact question was:

“If we have a GAF with several steps and at the last step we want to save all the GUIBB’s. Some GUIBB’s have there own model.We have created a WD component with interface IF_FPM_TRANSACTION. We want to transfer all the GUIBB models to the transaction component.Wiring would be nice, but we have more sources to the transaction component. That is the reason why I would like to have more sources to a target. What would you do? We can always use a factory singleton, but if we do that then the wiring would not be needed anymore.”

Unfortunately, SAP says clearly, that every UIBB may serve as a source for wiring multiple times, while it may serve as a target only once.
To answer the question, having a FPM transaction component as a target for multiple UIBBs is not possible. But there is a workaround, which could help solving this issue.

The fairytale of Hänsel and Gretel

The basic question is, how heterogeneous feeder models can be connected to a single UIBB feeder model which eventually performs a Save-operation on all the received feeder models.
This is where a commonly known fairytale comes into the scene.
Imagine, we had a Guided Activity FPM application. The first steps shows the “Hänsel” UI-Building Block which has its own feeder model to read the data and save it afterwards. The second step is the “Gretel” UI Building Block which has a totally different feeder model with its own data. The main question is: What happens to these feeder models and what are the further operations invoked?
Everyone who knows the fairytale already has the answer: Both are held captive by a witch. But what does that have to do with our FPM application?

Technically spoken, we need to transfer both feeder models to a composite feeder model. This is, where the composite pattern is used.
Both feeder models need to implement the same interface ZIF_FPM_SAVEABLE_FEEDER_MODEL which forces its implementers to implement a SAVE( )-Method.
ZIF_FPM_COMPOSITE_MODEL inherits from ZIF_FPM_SAVEABLE_FEEDER_MODEL and describes what a composite feeder model should be able to do. Despite the SAVE( )-method, it requires another method +ADD_MODEL(IO_MODEL : ZIF_FPM_SAVEABLE_FEEDER_MODEL). The composite feeder model is implemented by ZCL_WITCH_MODEL.
This means, the witch model may include other feeder models and subjects them to a common treatment, which is, in this very example, the Save-operation.
Wiring Model
The wiring behaviour of the UIBBs now needs to work like this:
1. If the input which comes from a wire, is not initial, the UIBB checks if the input is a composite feeder model of type ZIF_FPM_COMPOSITE_MODEL (to do so, simply down-cast the connector’s output to an instance of type ZIF_FPM_COMPOSITE_MODEL and catch the CX_SY_MOVE_CAST_ERROR exception, if raised)
2. If no input was provided, or the input had the wrong data type, a new composite model of type ZIF_FPM_COMPOSITE_MODEL is created by the UIBB
3. A new outport of the UIBB is created which outputs a composite model of type ZIF_FPM_COMPOSITE_MODEL
4. The UIBB’s feeder model is added to the output using the ADD_MODEL-Method of the composite model

This approach allows you to wire multiple UIBBs, who have a completely different feeder model, together. The output of every feeder model will be an instance of type ZIF_FPM_COMPOSITE_MODEL. Now we’ve got several wiring possibilities to wire the UIBBs and the FPM transaction component together.

Option 1 – The FPM transaction component pushes ZIF_FPM_COMPOSITE_MODEL to the feeders

Both feeders receive the witch, which is an instance of type ZIF_FPM_COMPOSITE_MODEL. Every UIBB adds its own model by calling the ADD_MODEL method. In the end, the witch holds captive all the feeder models.
Option 1 - FPM transaction component pushes ZIF_FPM_COMPOSITE_MODEL to the feeders

Option 2 – The FPM transaction component receives ZIF_FPM_COMPOSITE_MODEL

The UIBBs are connected in series. The last chain link is the transaction component. In contrast to the fairytale, the witch is created by Hänsel and is handed over to Gretel. The transaction component receives the witch in the end. Again, the witch holds captive the two other feeder models.
Option 2- The FPM ransaction component is the last chain link

Conclusion

If feeder models need to be transferred using wires, a common interface and the use of the composite pattern may be an option, if the implementations of the feeder models differ. This is often the case, if the UIBBs in the application were initially build up for another use case, but now need to be combined for a new application.
However, the interface which needs to be implemented yby the feeder models depends on the type of common treatment you want to apply to the feeder models. So there is still the effort of applying the above shown changes to existing feeders.

Perform Save-operations using a wire transaction handler in the FPM – Refactoring

Introduction

In the previous blog post, I introduced the Save-operation by using a wire transaction handler. The main statement below this blog post was, that an event can be enriched by an OVP-exit before it is getting processed by a wire transaction handler. The event receives a new parameter value, which is an instance of a saveable business object, that is, an ABAP Object implementing ZIF_FPM_DEMO_SAVEABLE.
There is a small issue with the way the wire transaction handler handles the event. In this short blog post I would like you to show what the problem is and how it can be fixed.

The existing wire transaction handler implementation

Right now, the wire transaction handler implementation reads the business object by a hard coded key-value:

METHOD if_fpm_wire_model_transaction~after_process_event.
DATA: lo_savable TYPE REF TO zif_fpm_demo_saveable.
CASE io_event->mv_event_id.
WHEN if_fpm_constants=>gc_event-save OR if_fpm_constants=>gc_event-save_and_back_to_main.
TRY.
io_event->mo_event_data->get_value(
EXPORTING iv_key = zif_fpm_demo_saveable=>gc_event_parameter_id
IMPORTING ev_value = lo_savable ).
CHECK lo_savable IS NOT INITIAL.
lo_savable->save( ).
CATCH cx_sy_move_cast_error.
ENDTRY.
ENDCASE.
ENDMETHOD.

The value represented by constant zif_fpm_demo_saveable=>gc_event_parameter_id serves as a key to retrieve the business object that has been hooked previously to the event by the OVP-exit.

This coding violates the Open-Closed-Principle which says “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification” (Meyer, Bertrand (1988). Object-Oriented Software Construction. Prentice Hall. ISBN 0-13-629049-3)
Why is this transaction handler affected by this principle?
The answer is quite easy: Every time we reuse the wire transaction handler in another application, we would also need to read business objects which have been hooked into the event by other exits, possibly with other key values. If this was the case, every reuse would cause the coding shown above to be changed in order to support other key values:

METHOD if_fpm_wire_model_transaction~after_process_event.
DATA: lo_savable TYPE REF TO zif_fpm_demo_saveable.
CASE io_event->mv_event_id.
WHEN if_fpm_constants=>gc_event-save OR if_fpm_constants=>gc_event-save_and_back_to_main.
TRY.
io_event->mo_event_data->get_value(
EXPORTING iv_key = zif_fpm_demo_saveable=>gc_event_parameter_id
IMPORTING ev_value = lo_savable ).
CHECK lo_savable IS NOT INITIAL.
lo_savable->save( ).
CATCH cx_sy_move_cast_error.
ENDTRY.

TRY.
io_event->mo_event_data->get_value(
EXPORTING iv_key = zif_fpm_demo_saveable=>gc_event_parameter_id2
IMPORTING ev_value = lo_savable ).
CHECK lo_savable IS NOT INITIAL.
lo_savable->save( ).
CATCH cx_sy_move_cast_error.
ENDTRY.
ENDCASE.
ENDMETHOD.

You recognized the changed coding already: another business object is read from the event with the key zif_fpm_demo_saveable=>gc_event_parameter_id2.
The way we deal with changes here is not a good practice.

Changes to the existing logic

Instead of supporting every thinkable FPM parameter key in the wire transaction handler, we improve the logic a little bit and make it more smart: The event parameters are now iteratively read and the handler tries to down-cast it to an instance of type ZIF_FPM_DEMO_SAVEABLE. If the down-cast fails, nothing happens (no SAVE-method is invoked) and the logic continues with the next event parameter until no more parameters are left.

METHOD if_fpm_wire_model_transaction~after_process_event.
DATA: lo_saveable TYPE REF TO zif_fpm_demo_saveable.
DATA lt_keys TYPE if_fpm_parameter=>t_keys.
FIELD-SYMBOLS LIKE LINE OF lt_keys.

CASE io_event->mv_event_id.
WHEN if_fpm_constants=>gc_event-save OR if_fpm_constants=>gc_event-save_and_back_to_main.
lt_keys = io_event->mo_event_data->get_keys( ).
LOOP AT lt_keys ASSIGNING .
TRY.
io_event->mo_event_data->get_value(
EXPORTING iv_key =
IMPORTING ev_value = lo_saveable ).
CHECK lo_saveable IS NOT INITIAL.
lo_saveable->save( ).
CATCH cx_sy_move_cast_error.
CONTINUE.
ENDTRY.
ENDLOOP.
ENDCASE.
ENDMETHOD.

Now the wire transaction handler does not need any further update for any new use case any longer. It works, no matter if you pass zero, one or multiple different instances of type ZIF_FPM_DEMO_SAVEABLE to it.
In order to avoid parameter key collisions, the OVP exit is also updated a little bit. Instead of setting the business object with the hard coded key value represented by ZIF_FPM_DEMO_SAVEABLE=>GC_EVENT_PARAMETER_ID, let’s create a guid as a key value by using the CL_UUID_FACTORY.
Since the wire transaction handler does not rely on any specific key values any longer, it is going to work even after this change:

METHOD override_event_ovp.
wd_this->mo_ovp = io_ovp.
DATA lo_event TYPE REF TO cl_fpm_event.
DATA: lo_datacontainer TYPE REF TO zif_fpm_demo_wire_flight_sel,
lo_savable TYPE REF TO zif_fpm_demo_saveable.
DATA lv_port_identifier TYPE fpm_model_port_identifier.
DATA lv_key TYPE string.
DATA lo_generator TYPE REF TO if_system_uuid.

CALL METHOD cl_uuid_factory=>create_system_uuid
RECEIVING
generator = lo_generator.
lv_key = lo_generator->create_uuid_c22( ).

lv_port_identifier = zif_fpm_demo_wire_flight_sel=>gc_name.

lo_event = io_ovp->get_event( ).
CASE lo_event->mv_event_id.
WHEN if_fpm_constants=>gc_event-save OR if_fpm_constants=>gc_event-save_and_back_to_main.
TRY.
lo_datacontainer ?= wd_this->mo_feeder_model->get_outport_data( iv_port_type = 'LS' iv_port_identifier = lv_port_identifier ).
CHECK lo_datacontainer IS BOUND.
lo_savable ?= lo_datacontainer->get_lead_selection( ).
lo_event->mo_event_data->set_value( iv_key = lv_key iv_value = lo_savable ).
CATCH cx_sy_move_cast_error.
ENDTRY.
ENDCASE.
ENDMETHOD.

Benefits

The wire transaction handler is now much more robust against changes in any exit which enriches the save-event with a saveable business object. This increases reusability without cutting functionality.
The usage of the handler has been made easier dramatically since it tries to extract instances of type ZIF_FPM_DEMO_SAVEABLE from any event parameter instead of event parameters with a specific key. Any application that reuses this handler does not need to be aware of the parameter key value of that object, it just needs to make sure, that an instance of a saveable business object has been hooked into the event data with a freely defineable key value that before the handler is getting executed – and that’s it.