8.2.7 – Validations
In “ABAP to the future” it is being said that validations in BOPF are meant to check the consistency and prevent saving. This is only partially correct. Validations are those parts of the model which represent idempotent (repeatable without changing the state) checks. When a validation is being executed and what the effect of a failed validation is, depends on the validation’s configuration. In general, there are two types of validation configuration: Action validations and consistency validations. The implementation of both types is based on the same interface (/BOBF/IF_FRW_VALIDATION) and thus, the same implementation class can be used in multiple places. Therefore, I also recommend to restrict the scope of a validation as much as possible in order to facilitate re-use.
Action validations
We still don’t really know what an action is, but we’ll start with action validations.(Actions and action validations will be covered in the next chapter of the book, but I believe it's better to have a chapter on both types of validations as they are so similar. However you might the chapter on actions first, it will be linked here once available.) An action validation is – surprise, surprise – registered to be executed before an action is being executed. If the validation fails, the action will not be invoked for the failed instances. Let’s have a look at a sample which we foreshadowed some chapter ago: The business requirement reads that howling at the moon should only be possible for monsters with at least one head. In addition, werewolves should be able to howl at the moon only if it’s full moon. The two aspects are semantically independent, therefore we create two validations which we both register for the action HOWL_AT_THE_MOON: HAS_AT_LEAST_ONE_HEAD and IS_FULL_MOON. If later on, we might for example add an action BITE, we could also register the HAS_AT_LEAST_ONE_HEAD for it, but not the full-moon-check (assuming that also human-bodied werewolves can bite). This additional configuration would not require a single line of code and makes our model more readable with respect to the pictured business.
The implementation of HAS_AT_LEAST_ONE_HEAD looks very similar to what we coded in the previous chapter’s property determination with one big difference: We now want to prevent the action execution and provide a meaningful error message. Remember: The property-determination was only executed on request of the consumer (the UI-layer).
METHOD /BOBF/IF_FRW_VALIDATION~EXECUTE.
DATA lt_root TYPE ZMONSTER_T_ROOT. “The combined table type of the node to be retrieved
CLEAR: ET_FAILED_KEY, EO_MESSAGE.
io_read->retrieve(
exporting
iv_node = zif_monster_c=>sc_node-root
it_key = it_key
it_requested_attributes = VALUE #( (zif_monster_c=>sc_node_attribute-root-number_of_heads ) )
importing
it_data = lt_root ).
LOOP AT lt_root ASSIGNING FIELD-SYMBOL( <ls_root> ).
IF <ls_root>-number_of_heads = 0.
* Get a message container. Unfortunately, the message container is not injected as a handler, so we must instantiate it actively. #architectural_fail
IF eo_message IS INITIAL.
eo_message = /bobf/cl_frw_factory=>get_message( ).
ENDIF.
* Create a message by instantiating the message-class. Don’t use the instantiation of /bobf/cm_frw_core as written in the book (this is bad style), but simply make your exception-message-class inherit from /bobf/cm_frw. There’s also a blog post about message-objects in BOPF on SCN ;). The constructor-expressions pay-off very well in this case!
eo_message->add_cm( NEW zcm_monster(
textid = zcm_monster=>no_head
severity = zcm_monster=>co_severity_error
symptom = /bobf/if_frw_message_symptoms=>co_bo_inconsistency
lifetime = /bobf/if_frw_c=>sc_lifetime_set_by_bopf
ms_origin_location = VALUE #(
bo_key = zif_monster_c=>sc_bo_key
node_key = zif_monster_c=>sc_node-root
key = <ls_root>-key ) ) ).
* The message just created is not meant to be interpreted at all, neither by the framework nor by a consumer. What really matters (and what makes the framework not execute the action in case of an action validation) is the indication of the instance as failed
INSERT VALUE#( key = <ls_root>-key ) INTO TABLE et_failed_key.
ENDIF. “monster has no head
ENDLOOP.
ENDMETHOD.
The validation code will always be executed if the action to howl at the moon is about to being performed or if the core-service “check_action” is being executed by a consumer: In this case, all action validations configured for the requested action will be executed. In contrast to what’s being said in the book, there is no option to invoke a particular validation.
Consistency validations
A consistency validation is a check which is being executed as a side-effect of an interaction with a business object changing the state of one or multiple instances (without a change of its state, the consistency of the instance cannot change anyway). As such, the configuration of a consistency validation is comparable to the configuration of a determination: You can select trigger nodes and modification (CUD). Additionally, all consistency validations which have been configured on “check” are being executed through the core-service check_consistency. While an action validation prevents the action if it fails, the consistency validation’s reaction on failure is defined by a so-called “consistency-group”: Either it can prevent saving or it can set a consistency-status (which is a dedicated node-attribute). The latter is particularly interesting if you think about stateless-applications in which a save is effectively being requested after each round-trip: The framework will mark the instance as inconsistent if a consistency validation fails on save. With a new stateless request, this consistency-status can be interpreted in order to prevent a subsequent action which should only be possible for consistent instances.
With respect to implementation, there’s no difference compared to action validations (both implement the same interface). But as consistency-validations are being executed as a result of a modification (which – once again – the consistency validation will not prevent), there’s good reason to also implement the check and check_delta-methods, which don’t make much sense for action validations. I will not provide a complete implementation now (you can read one in “ABAP to the future”), but I want to point out some of the major mistakes made (which you unfortunately can also find in the book):
- As all interface-methods, also the validation methods are mass-enabled! If you ever see a READ INDEX 1 in BOPF code, this will most probably not work in mass-scenarios.
- Most of the BOPF framework-classes (containg _frw_ in their names) are meant to be instantiated by the framework only. You should not reference those classes or instantiate them directly. General advice: Program to the interface, not to the implementation. Usually, either/bobf/cl_frw_factory will have a method providing you a proper instance or you will get it injected at runtime (so it’s available as an importing-parameter, such as the io_read and io_modify).
- For message-classes or libraries, inheritance is a common reuse-mechanism in BOPF.
- In addition, all the pitfalls mentioned in the chapter on determinations apply – including the lengthy “no need for a single model class”-pledge.
About raising exceptions
As you might have noticed, the signature also features an exception of type /BOBF/CX_FRW which can be raised and you may wonder whether it would not be suitable for failed validations. The short answer: No, it is not. The system will go for a (“controlled”) short-dump. I could also go for a long explanation why this is desired, but I fear no-one is really interested in reading it. If you would like to know more about it, please comment ![]()