It rarely happens that I’m really looking forward to a release of a book. The announcement of “ABAP to the Future“ by Paul Hardy was such an occasion. Reading the TOC, I was 100% positive that Paul picked the right topics which were needed in order to fulfil the promises of the title.
Particularly I was excited about the BOPF chapter: I have given a couple of trainings, been to TechEd with the topic, promoted it to management as well as fellows and all the time, the same questions are being raised: “Why have I not heard about it before?”, “Is there an official training at SAP?” and “Is there a book about it?”. While I still can’t answer the first one, the answer to the second one is “yes, WDEBOF at least gives a rough overview”. I always answered “not yet” to the book-question and was excited to be able to answer it with “yes, and the title of the book is very appealing to management as well: ABAP to the future”.
I finally started to read it and have to admit that I don’t agree to some things I read and expected some more architectural depth. Sharing this opinion with fellows, I was encouraged to write my comments on SCN so that we can discuss them.
Preface: This is my opinion based upon my experience and architectural as well as programming style. @Paul: You have done a great job collecting and preparing those topics. Thanks for including BOPF in it. I hope you don’t mind my criticism and the style I chose. I would love to read your comments on my writing and discuss on the each and every aspect. Often, there’s no “right” solution, so I guess that other readers joining the discussion would also benefit from it.
Instead of picking all the small pieces which I would like to comment on, I chose to write a partially alternative version. This is of course more challenging and exposes my poor writing (I can’t keep up with Pauls entertainment for sure, I’m German and Germans tend to stick to facts instead of entertaining the reader, which is a pity...), but I hope that third persons can understand it more easily. Still I will not repeat parts which I agree with. You’ll have to read the chapters in Pauls book yourself and create your own picture which suits your experience and architectural style. Anyway, it’s worth buying the book
So here we go:
8.1 Defining a Business Object
A BOPF model is a representation of a real-world object. With a very low representational gap, the model which is designed in a dedicated modeling environment in the ABAP backend (in fact, there are multiple designtimes based upon the same model, we’ll come to that later). This so-called “outside-in” approach doesn’t only make it easier to consume the model, but also to discuss as a developer with domain-experts with the same vocabulary. But be careful: The business object is agnostic to the consumer (e. g. doesn’t know anything about the UI). Deriving a model from a UI can render your model inflexible or in-appropriate for other consumers. Modeling a business object is very similar to modeling a UML class diagram: You think about how the things work and use your brain and methodology in order to derive classifiers (classes) and behavior (methods) – if you don’t have a methodology which helps you to decide what makes an entity, I highly recommend reading Craig Larman.
8.1.1 Creating the Object
Before we head to the system, let’s make a plan. How does a monster look like? A monster is composed of many parts (UML: classes) which are interconnected (UML: by associations). Those which are connected with a dependency that the parts can’t exist without the monster itself (UML: composition associations) will form our monster business object. An analysis of our usecases has resulted in the monster itself needing a name and a creator. It has a head, it can even have multiple heads. About the legs and arms we might bit a bit uncertain: When is an arm an arm, what makes the difference to a leg? We might decide for an entity “extremity” instead. All these questions might not occur when deriving the monster from a UI. We might instead have come up with a model where we’ve got only one head (as an attribute of the monster) and six attributes leg1..6. Now let’s move to the BO-Builder. I recommend to directly utilize the BOPF Expert tool (transaction BOBX) unless you’re got EhP7, SP8 and are happy to be able to utilize the BOPF in Eclipse plug-in.
All entities of a model are technically encoded as GUIDs. When programming with BOPF, we technically pass the GUID in order to address an aspect of a business object which we’re talking to. This is necessary, as the command pattern and the service layer pattern are fundamentals of the BOPF architecture (we’ll talk about this in more detail later). Just one word upfront: In the beginning it might feel a bit cumbersome, after but you get used to it very easily as the pattern is ubiquitous. In order to make the code more readable (and writable) for the human, BOPF generates a so-called constant-interface which has a human-readable constant containing the GUID. During debugging however, you are confronted with the GUIDs. Hint: There are various ways in order to translate the GUID back to the model-entity. The easiest one is to press control+F in the BOPF designtime and paste the GUID. The UI will navigate to the corresponding model-entity (at least in the SAP-GUI-based tools). When debugging, you might not want to leave your debugging environment and can utilize the debugger script /BOBF/TOOL_DEBUGGER_SCRIPT.
8.1.2 Creating the root node
The wizard guides you through the creation, you basically have to select proper names. The so-called root node is the topmost entity upon which existence all other nodes depend on (as Paul wrote comparable to the top level of an XML document). I recommend you not to choose any other name than ROOT, as the ROOT kind of represents the object itself. Naming it like the object (e. g. MONSTER) is redundant (as all node-names are unique only in the context of the BO) and giving it a name (e. g. HEADER) makes it more difficult to identify as top-level node. BOPF also creates associations for all lower level nodes to the top-level-node which are called “TO_ROOT”, so it would be nice in my opinion if TO_ROOT also found the ROOT node.
The structure of each node contains two major includes: One for the persistent structure and one for the transient structure. The persistent structure will be included into the database table which BOPF will generate for you. The Transient structure contains information which can be derived at runtime. It’s a bit tricky to distinguish between transient attributes and UI-control-information. I recommend to include attributes into the transient structure which are relevant to the business (and don’t think about the UI). For a monster, it is necessary how many heads it’s got, as it can bite once with each head (assuming only one mouth per head). Thus, we should have an attribute “number of heads” on our ROOT node. However, this can be easily derived by counting the number of HEAD-instances for each monster. It doesn’t need to be persisted, but it could be also stored if updated after each head-creation or deletion. Whether an attribute is transient or persistent is transparent to the consumer, as both structures are included into the “combined node structure” which is being used for consumption. You can even move attributes between the two structures without disruption. Information which is solely necessary for the consumption by a human (via a user interface) like language-dependent texts to a coded value should neverbe part of the model, but should be added on an architectural layer closer to the UI (I call this layer upon which the UI-controller is based upon “service adaptation”, but this aspect is not part of this chapter). My recommendation: If you can derive an attribute but want to search (the database) for it or if the derivation is very expensive, include it in the persistent structure. Else, give the transient structure a try.
The names for the runtime structures will be proposed by some funky algorithm which resides in BOPF. I recommend to just keep the prefixes and suffixes as they are (and only change the semantical part if necessary), as it is very beneficial if the naming is the same within your development team. And there’s always a laze team member which just goes for the proposal If you’ve got a deviating naming convention in your company, feel free to extend the BOPF code which does the proposal. Doing this will help you tremendously to learn how BOPF itself works – and might open your eyes a wide.
8.1.3 Creating the subnodes
Our monster has got heads and extremities. For the sake of simplicity, we’ll just ignore legs and arms now and simply create an entity to store information about the monster’s various heads. As the heads depend on the existence of the monster itself, this will be a subnode to the ROOT node (in contrast to “FINGER” which depends on the existence of the node “EXTREMITY” and would be a subnode of the subnode, if we modelled it). Also note that the node-name is in singular! How many heads a monster may have is a property of the association between ROOT and HEAD (its cardinality) and might even change as the system evolves (usually from one to many ). Also, we don’t prefix the node-name with the business object name itself, as the node is anyway context-dependent on the BO.
As I wrote above, a BOPF Business Object comprises all the entities (as nodes) which are connected by compositions. As we create the subnode, BOPF implicitly also creates a composition association along with it. All associations in BOPF are directional. The composition, which usually has the same name as the target node leads from ROOT to HEAD. Another association which leads from HEAD to ROOT is also created implicitly: it is called “TO_PARENT”. This association exists for all the subnodes of our model, just like “TO_ROOT” which always associates the ROOT node from the subnode. While TO_PARENT and TO_ROOT always have a cardinality of 1, the compositions’ cardinalities have to be modeled.
Note that – just like in the UML – nodes and associations are different entities of the metamodel. In the constant-interface, you can also experience this easily: There are separate constants for zif_monster=>sc_node-head and zif_monster=>sc_association-root-head. Also, there can be multiple associations between the same nodes: E. g. we can imagine one head being a preferred one (which takes most of the tasks). This dedicated head would still be a head (and not a separate node in the model), but could be represented by an own association from ROOT to HEAD: “PREFERRED_HEAD”. There are various types of associations, including associations to nodes of other business objects. We’ll only confront our brains with compositions now, all the other associations can be used in the same way.
In UML, Nodes are classifiers (such as classes in ABAP OO) and as such, they are also the anchor for behavior: While object oriented languages differentiate behavior based on their visibility (private, protected, public methods), BOPF also differentiates semantically (Determinations, actions, validations, queries). What this all is and how this relates to known concepts, we’ll look at in a practical example.
8.2 Using BOPF in order to write a DYNPRO-style program
Also in the ancient DYNPRO-times, it was (theoretically) possible to implement a MVC-pattern. The DYNPRO-Events PBO and PAI could sever as entry point for a controller which then delegates the actual logic to a model. However, there are only a few samples I know where this has been done consequently (you might debug a PBO and PAI of the ABAP workbench (SE80) or the BOPF builder (BOBX) in order to get an impression how this could be done.
Please do note that there in my opinion, there is no “BOPF equivalent of a PAI”, as the PAI is part of the UI or controller layer (even I can argue with myself what it’s exactly part of), but it’s surely not meant to be part of the model which is where BOPF resides. In this chapter, we will consume BOPF (from the UI layer) as well as provide business logic (such as checks on the input’s sanity). The interfaces and patterns interfaces for service consumption and provisioning look very similar, which is one of the strengths of the patterns used.
8.2.1 Where is my model class?
Short answer: 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 representing the instance of my real-world-object. Without knowing it, I implied a 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 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, 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 evoking behavior (executing an action in BOPF language). Reading an entity via a service layer looks something like this:
monster = monster_manager->retrieve( node = HEAD key = 4711).
As the signature only alows the framework to know at runtime which entity (node data) shall be transported, all the data transported can’t be typed. 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)).
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 make it more easy 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.
Finally: The object configuration object
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).
--> The next post covers the basic consumption of a BO using CRUD services