ABAP to the future – my version of the BOPF chapters – Part 2 Addendum: Model class
In “ABAP to the future” (A2tF), the author explains in this chapter how his model class (based on the domain model) is structured and why this is necessary. I want to explain in this chapter how business logic is coupled and where the model class is in BOPF.
Short answer to the chapter’s title-question: There is no need for a dedicated model class. Be brave, move on.
The longer version: When I first encountered BOPF and heard this it was an object model, I was desperately looking for a model class which I could instantiate which represents the instance of my real-world-object. Without knowing it, I implied “domain model” architecture. However, this is not the case with BOPF: BOPF is built to leverage the strengths of ABAP and meant to be used also for mass-data-processing. Due to the fact that instantiation of an ABAP class is quite an expensive thing and the benefits of a table as first-level-citizen of the language, the BOPF inventors decided that a service layer on top of a table-data-gateway-alike consumption with command-pattern-based methods would suit best. A service layer means that there are a couple of defined “core-services” which are offered by a service manager which have to be provided by all entities. The interface for those services (/BOBF/IF_TRA_SERVICE_MANAGER) is agnostic to the actual entity which is accessed through it. As most managers, the service layer validates the contract, doesn’t really add a lot of semantical value and delegates the consumer’s request to the implementation of the entity.
The entity which is being addressed is part of the signature of a service façade. You can compare this e. g. to the HTTP-methods, where the resource which shall be addressed (URI) is a parameter of the actual method (e. g. GET). For each entity, the same interface is instantiated in order to manage one business object (the one to manage is passed as parameter during instantiation).
At runtime, in our sample, we will operate with a “monster-manager”. The most familiar services are the so-called CRUD-operations. But there are a couple of other services as well, such as invoking behavior (executing an action in BOPF language). Think of each method as a predefined command which is very imperative (I sometimes imagine a commander shouting this aloud to a couple of soldiers 😉 ).
Reading an entity via a service layer looks something like this: monster = monster_manager->retrieve( node = HEAD key = 4711).
As the framework only knows at runtime which entity (node data) shall be transported, all the data transported can’t be typed statically. The same applies for example to parameters of an action: monster_manager->do_action( node = ROOT key = 4711 action = eat parameters = new monster_eating_parameters( number_of_crackers = 5) ), where parameters is a generic type ref to data.
Even from these simplified examples you can guess that those patterns given, method calls occupy a lot of space on your screen. So why not build a wrapper (helper class) around it in order to shorted code?
From an architectural perspective, there is no semantical value added by a wrapping model class. It shall only simplify code. From a development perspective, you’ll end up with a simplification which – in the beginning – may cover 80% of the usages, but time over time you’ll end up extending and extending the signatures of your helper class, add a lot of methods and in the end will have an even more complex (but manually maintained) artifact which doesn’t really reduce complexity anymore. And there will always be developers that just consume the service layer directly as it just feels more appropriate in that moment. A generic simplification is also likely to ignore performance tunings which you could have had if you didn’t default a parameter in your simplification. From a QM-perspective you might also give up a major benefit of the pattern (if you create dedicated typed model classes for each entity): The way you talk to business objects is the same across all business objects. Whether you want to read a monster’s leg data or the engine-information of a rocket: You don’t have to learn new signatures if you know how to talk to the manager. I cannot go into detail about all the negative aspects and anyway, an encapsulation might really make your code shorter in the end. I can’t stop you from doing it your style anyway, but I can tell you that during my seven year experience coding real applications in BOPF, I would not go for any type of wrapper anymore.
Finally, if you still are keen to have an encapsulation, then at least generate it as typed access class. You don’t even need to write the generator itself: The BOPF team had this idea as well, but it didn’t make it into the official features (due to the above reasons, I guess 😉 ). You can still find it in the internal full-blown-modeling environment in the extras menu though.
In Paul’s model class, a configuration object (an instance of /BOBF/IF_FRW_CONFIGURATION) is a member attribute. Paul refers to it being necessary in order to e. g. make the framework execute a validation class (we’ll know later what that is) by addressing the validation using its technical name (instead of instantiating the model class and calling the (public) interface methods). This is wrong.
The purpose of the object configuration mostly is to allow generic libraries and other reuse-features where you need RTTI. The configuration object provides a high-performance read-access on the model information. This includes the structural as well as the behavioral aspects, but it must never be used in order to invoke BO internal behavior (there is also no core service allowing this, for good reason and architecture).