on 05-07-2007 2:12 AM
As a developer coming from an OO/java background, I recently started to study and use the Java Web Dynpro framework for creating enterprise portal applications.
Up to this point, I've developped 2 or 3 WDP projects - and in so doing, I've tried to reconciliate my java-influenced development methods with the SAP way of doing things. I'd say for the most part it was rather painless. I did, however, find a serious problem as far as I'm concerned in the way SAP has promoted the use of the java bean model importer.
<a href="https://www.sdn.sap.com/irj/sdn/weblogs?blog=/pub/u/251697223">david Beisert</a> created this tool and presented it to the SDN community in 2004 in his <a href="/people/david.beisert/blog/2004/10/26/webdynpro-importing-java-classes-as-model The same year (don't know if it was before or after), SAP published '<a href="https://www.sdn.sap.com/irj/servlet/prt/portal/prtroot/docs/library/uuid/1f5f3366-0401-0010-d6b0-e85a49e93a5c">Using EJBs in Web Dynpro Applications</a>'. Both of these works presented simplified examples of invoking remote functions on EJB backends (an add() function in the case of David Beisert's example, and a calculateBonus() function in the case of the SAP publication). Accordingly, they both recommended the use of the Command Bean pattern as an implementation strategy for their respective examples. Which I don't totally disagree with, in these particular circumstances. A simple execute() method is perfectly suitable if one needs to EXECUTE a remote function call - whether it be a calculate() method invoked on a EJB Session Bean or an RFC call made to some remote ABAP system.
Problem is, not everything in life is a function call ! To me, it makes very little sense to model everything as a command if it doesn't match your business model. The needs of your application should dictate the architecture of your model and not the other way around.
This unjustifiable fixation on the Command Bean pattern is probably to blame for the fact that very little up to this point seems to have been written on the subject of the power of the binding mecanism as a most powerful tool in the arsenal of the Web Dynpro developer.
What's this ?
Binding can make it possible to abstract away most of the nitty gritty context node navigation and manipulation logic and replace it with more intuitive and more developer-friendly model manipulation logic.
There was a time when programs that needed persistence were peppered with database calls and resultset manipulation logic. Hardly anyone codes like that anymore.. and with good reason. The abstraction power of Object Oriented technologies have made it possible to devise human friendly models that make it possible for developers to concentrate on business logic, and not have to waste time dealing with the low-level idiosyncrasies of database programming. Whether it be EJBs, JDO, Hibernate... whatever the flavour... most serious projects today utilize some sort of persistence framework and have little place for hand-coding database access logic.
I feel that the WD javabean model offers the same kind of abstraction possibilities to the Web Dynpro developer. If you see to it that your WD Context and javabean model(s) mirror each other adequately, the power of binding will make it possible for you to implement most of your processing directly on the model - while behind the scenes, your context and UI Elements stay magically synchronized with your user's actions:
+-------------+ +-------------------+ +--------------+ +------------+
| Model |<-bound-| Component Context |<-mapped-| View Context |<-bound-| UI Element |
+-------------+ +-------------------+ +--------------+ +------------+
o Context Root o Context Root
| |
ShoppingCartBean <---- +-o ShoppingCart Node <------ +-o ShoppingCart Node
{ | |
Collection items <---- +-o CartItems Node <--------- +-o CartItems Node <-- ItemsTable
{ | |
String code; <-------- +- Code <-------------------- +- Code <----------- CodeTextView
String descrip; <----- +- Description <------------- +- Description <---- DescTextView
}
}
Let's examine an example of this concept. I propose a simple but illustrative example consisting of a shopping cart application that presents the user with a collection of catalog items, and a shopping cart in which catalog items may arbitrarily be added and/or removed.
The Component and View contexts will be structured as follows:
o Context Root
|
+--o ProductCatalog (cardinality=1..1, singleton=true)
| |
| +--o CatalogItems (cardinality=0..n, singleton=true)
| |
| +-- Code
| +-- Description
|
+--o ShoppingCart (cardinality=1..1, singleton=true)
|
+--o ShoppingCartItems (cardinality=0..n, singleton=true)
|
+-- Code
+-- Description
Let's examine how a conventional Command Bean implementation of this component could be coded. Later on, I'll present a more object-oriented model-based approach. We can then compare the differences.
public class ProductCatalogCommandBean
{
// collection of catalog items
Collection items = new ArrayList();
public void execute_getItems()
{
// initialize catalog items collection
items = new ProductCatalogBusinessDelegate().getItems();
}
}
This command bean will serve as a model to which the ProductCatalog node will be bound. This happens in the supply function for that node in the component controller:
public supplyProductCatalog(IProductCatalogNode node, ...)
{
// create model
model = new ProductCatalogCommandBean();
// load items collection
model.execute_getItems();
// bind node to model
node.bind(model);
}
No supply function is needed for the ShoppingCart node, since it is empty in its initial state. Its contents will only change based on the user adding to or removing items from the cart. These operations are implemented by the following two event handlers in the view controller:
public void onActionAddItemsToCart()
{
// loop through catalog items
for (int i = 0; i < wdContext.nodeCatalogItems().size(); i++)
{
// current catalog item selected ?
if (wdContext.nodeCatalogItems().isMultiSelected(i))
{
// get current selected catalog item
ICatalogItemsElement catalogItem = wdContext.nodeCatalogItems().getElementAt(i);
// create new element for ShoppingCartItem node
IShoppingCartItemsElement cartItem = wdContext.createShoppingCartItemsElement();
// initialize cart item with catalog item
cartItem.setCode (catalogItem.getCode());
cartItem.setDescription(catalogItem.getDescription());
// add item to shopping cart
wdContext.nodeShoppingCartItems().addElement(cartItem);
}
}
}
public void onActionRemoveItemsFromCart()
{
// loop through cart items
for (int i = 0; i < wdContext.nodeShoppingCartItems().size();)
{
// current shopping cart item selected ?
if (wdContext.nodeShoppingCartItems().isMultiSelected(i))
{
// get current selected item
IShoppingCartItemsElement item = wdContext.nodeShoppingCartItems().getElementAt(i);
// remove item from collection
wdContext.nodeShoppingCartItems().removeElement(item);
}
else
// process next element
i++;
}
}
From what I understand, I believe this is the typical way SAP recommends using Command Beans as a model in order to implement this type of simple component.
Let's see how the two same event handlers could be written with a more comprehensive object model at its disposal. One whose role is not limited to data access, but also capable of adequately presenting and manipulating the data that it encapsulates. (The actual code for these model beans will follow)
// I like to declare shortcut aliases for convenience...
private ProductCatalogBean catalog;
private ShoppingCartBean cart;
// and initialize them in the wdDoInit() method...
public wdDoInit(...)
{
if (firstTime)
{
catalog = wdContext.currentNodeProductCatalog().modelObject();
cart = wdContext.currentNodeShoppingCart ().modelObject();
}
}
Now the code for the event handlers:
public void onActionAddItemsToCart()
{
// add selected catalog items to shopping cart items collection
cart.addItems(catalog.getSelectedItems());
}
public void onActionRemoveItemsFromCart()
{
// remove selected shopping cart items from their collection
cart.removeItems(cart.getSelectedItems());
}
I feel these two lines of code are cleaner and easier to maintain than the two previous context-manipulation-ridden versions that accompany the command bean version.
Here's where the models are bound to their respective context nodes, in the Component Controller.
public supplyProductCatalogNode(IProductCatalogNode node, ...)
{
node.bind(new ProductCatalogBean(wdContext.getContext()));
}
public supplyShoppingCartNode(IShoppingCartNode node, ...)
{
node.bind(new ShoppingCartBean(wdContext.getContext()));
}
Notice that a context is provided in the constructors of both models (a generic context of type IWDContext). We saw earlier that our model needs to be able to respond to such requests as: catalog.getSelectedItem(). The user doesn't interact directly with the model, but with the Web Dynpro UI Elements. They in turn update the context... which is where our model will fetch the information it requires to do its job.
Also note that a model is provided for the shopping cart here, even though it has no need to access or execute anything on the back-end. Again, the model here is not being used as a command bean, but rather as a classic object model. We simply take advantage of the power of binding to make ourselves a clean and simple little helper that will update for us all the relevant context structures behind the scenes when we tell it to.
Here are the ShoppingCartBean and ProductCatalogBean classes (I've omitted a few getter/setter methods in order to reduce unnecessary clutter):
public class ShoppingCartBean
{
Collection items = new ArrayList();
IWDNode itemsNode;
public ShoppingCartBean(IWDContext context)
{
// initialize shortcut alias for ShoppingCartItems node
itemsNode = context.getRootNode()
.getChildNode("ShoppingCart", 0)
.getChildNode("ShoppingCartItems", 0);
}
public void addItems(Collection items)
{
this.items.addAll(items);
}
public void removeItems(Collection items)
{
this.items.removeAll(items);
}
public Collection getSelectedItems()
{
return ItemDTO.getSelectedItems(itemsNode);
}
}
public class ProductCatalogBean
{
Collection items;
IWDNode itemsNode;
public ProductCatalogBean(IWDContext context)
{
// fetch catalog content from back-end
items = new ProductCatalogBusinessDelegate().getItems();
// initialize shortcut alias for CatalogItems node
itemsNode = context.getRootNode()
.getChildNode("ProductCatalog", 0)
.getChildNode("CatalogItems", 0);
}
public Collection getSelectedItems()
{
return ItemDTO.getSelectedItems(itemsNode);
}
}
Notice that both classes delegate their getSelectedItems() implementation to a common version that's been placed in the ItemDTO class. It seems like a good place to put this type generic ItemDTO-related utility.
This DTO class could also have been used by the Command Bean version of the event handlers.. would reduce somewhat the number of loops. At any rate, the ItemDTO class shouldn't be viewed as an "overhead" to the model-based version, since it usually will have been created in the J2EE layer,for the marshalling of EJB data (see <a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/TransferObject.html">Data Transfer Object Pattern</a>). We just take advantage of what's there, and extend it to our benefit for packaging some common ItemDTO-related code we require.
// DTO made available by the EJB layer
import com.mycompany.shoppingcart.dto.ItemDTO;
public class ItemDTO extends com.mycompany.shoppingcart.dto.ItemDTO
{
String code;
String description;
public ItemDTO()
{
}
public ItemDTO(String code, String description)
{
this.code = code;
this.description = description;
}
//
// returns ItemDTOs collection of currently selected node elements
//
public static Collection getSelectedItems(IWDNode node)
{
// create collection to be returned
Collection selectedItems = new ArrayList();
// loop through item node elements
for (i = 0; i < node.size(); i++)
{
// current item element selected ?
if (node.isMultiSelected(i))
{
// fetch selected item
IWDNodeElement item = node.getElementAt(i);
// transform item node element into ItemDTO
ItemDTO itemDTO = new ItemDTO(item.getAttributeAsText("Code"),
item.getAttributeAsText("Description"));
// add selected item to the selectedItems collection
selectedItems.add(itemDTO);
}
}
return selectedItems;
}
}
Notice that the getSelectedItem() method is the only place in our model where context node navigation and manipulation actually takes place. It's unavoidable here, given that we need to query these structures in order to correctly react to user actions. But where possible, the business logic - like adding items and removing items from the cart - has been implemented by standard java constructs instead of by manipulating context nodes and attributes.
To me, using a java bean model as an abstraction for the Context is much like using EJBs as abstractions of database tables and columns:
abstracts away
EJB model --------------> database tables & columns
abstracts away
WDP javabean model --------------> context nodes & attributes
Except that a javabean model (residing in the same JVM) is much more lightweight and easy to code an maintain than an EJB...
Before concluding, it might be worth pointing out that this alternative vision of the Web Dynpro Model in no way limits the possibility of implementing a Command Bean - if that happens to suit your business needs. You will of course always be able to implement an execute() method in your WDP Model if and when you feel the need to do so. Except that now, by breaking free of the mandatory Command Bean directive, you are allowed the freedom to ditch the execute() method if you don't need such a thing... and instead, replace it with a few well-chosen operations like getItems(), addItems(), removeItems(), getSelectedItems()... which, as we've just seen can add significant value to the javabean model made available to your WDP component.
Comments would be appreciated on this issue (if anyone has had the time/courage/patience to read this far...;). Am I alone here intrigued by the potential of this (up until now) scarcely mentionned design strategy ?
Romeo Guastaferri
Hi Romeo,
thanks for sharing this with the community. I am little bit surprised that the command pattern was understood as the only way on how to use the Javabean model in conjunction with EJBs. The command pattern blog of mine was just a very simplified example of how a functional call can be translated to a Java Bean model. Actually it was to show how the paradigm of a model works. I personally use a similar approach to yours. It seldomly makes sense to map an EJB method one to one to a model, but the javabean model must be driven by the Userinterface and represents a bridge between the business service layer and the ui. I personally even think that often it does not make sense to map RFC function like they are to the Web Dynpro Context. Most often you end up writing ZBAPIs that return structures like they are used in the UI. But if you use a java bean model as a layer in between your service layer, you are more flexible in evolving the application. Anyways design patterns for the java bean model need to be discussed more on SDN as they really add very valuable possibilities you would never have when working with value nodes alone. With the Javabean model we are back in the real OO world where things like inheritance work, things that are really not too well supported by the native WD features. I encapsulate every context of mine as javabeans. This has nothing to do with EJBs (which I am personally not a fan of) but only with the fact that I want to work with the power of the OO world.
rgds
David
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
85 | |
10 | |
10 | |
10 | |
7 | |
6 | |
6 | |
5 | |
4 | |
4 |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.