Dienstag, 4. Dezember 2007

Ajax in Action II

In Ajax in Action Teil I habe ich die Problematik des Seitenaufbaus mit Spring MVC + JSP's angesprochen.

Hier soll das Problem und der Lösungsansatz genauer aufgezeigt werden.

Problem:

Mit JSP 1.2 gib es kein Komponenten-Model im GUI, was sich auch mit Spring MVC nicht lösen lässt. Grundsätzlich besteht ein Seite immer aus mehreren, logisch unabhängigen, Modulen.
Beispiele: User-Informationen, Navigation, Warenkorb und ein Produktdetail mit Add to cart Funktion.














Natürlich kann man eine solche Seite in Fragmenten zerlegen, z.B. mit Apache Tiles. Das löst jedoch nicht das Problem, sondern schaft lediglich eine Möglichkeit der Wiederverwendung auf HTML Ebene. Problematisch ist nämlich, wie man das Model (JavaBean) baut und wann und wie man es an die View bindet.

Nehmen wir an, es gibt ein UserBean:

public class UserBean implements Serializable {
private String name,lastname,email;
...
}

und ein CartBean:

public class CartBean implements Serializable {
private long subTotal;
private Collection cartItems;
...
}

und ein ProductDetailBean:

public class ProductBean implements Serializable {
private String id, name, code, description;
...

Dazu gibt es eine Tiles2 View-Defintion:

<definition name="productDetail" extends="publicPage/template">
<put-attribute name="content" value="/WEB-INF/views/productdetail.jsp" />
<put-attribute name="pagetitle" value="Product Detail" type="string" />
</definition>

Nun die eigentliche Frage: Wie sieht der Controller aus und wie das ViewBean?
Da wir kein submit nach MVC auslösen wollen gibt es einen simplen AbstractController:

public class ProductDetailController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response) throws Exception
Map refData = new HashMap();
// call service to get the model object
refData.put("command", service.getProductDetail());
return new ModelAndView(getFormView()).addAllObjects(refData);
}

Bisher haben wir folgendes: Ein Controller, der ein Model lädt und an die View "productDetail" übergibt. Die View wird per Tiles zusammengebaut. Jedes Tile in Form einer JSP hat nun Zugriff auf unser Model unter dem Namen "command". Demnach muss unser Controller ein Model liefern, dass allen Tiles Fragementen gerecht wird.

Wie sieht nun das Model aus?

1. Lösung:

Man baut ein Model pro View in Verbindung mit einem Controller - ProductDetailViewBean + ProductDetailController für "productDetail" View. Das ProductDetailViewBean ist ein Composite aus folgenden Objekten: UserBean, ProductDetailBean und CartBean. In den jeweiligen Tiles wird wie folgt zugriffen:

Guten Tag ${command.userBean.name} + ${command.userBean.lastname}!
Sie haben ${command.cartBean.items.count} Produkte im Warenkorb.

Nachteile:
  • Der Controller muss unter Umständen mehrere Serviceaufrufe tätigen, bis er das ViewBean korrekt erzeugt hat.
  • Der Controller + ViewBean kennen den Context ihrer Existenz in der View.
  • Der GUI Entwickler kann auf jedem Tile in jedem JSP direkt alle Attribute des command Objects zugreifen.
  • Eingeschränkt wiederverwendbar.

2. Lösung:

Man gibt der View "productDetail" nur das ProductDetailBean als command Object zurück und alle anderen referencing data unter einem anderen Key, z.B.

refData.put("user",userBean).


Nachteile:
  • Der Controller ebenso mehrere Serviceaufrufe tätigen.
  • Der Controller kennt den Context und muss alle notwendigen Beans als referencing data bereitstellen.

3. Lösung:

Der Controller liefert nur das command Object ProductDetailBean an die View zurück. Die anderen Komponenten, z.B. User-Informationen, Warenkorb etc. laden sich selbst mittels AJAX/DWR nach.

Diese Lösung entkoppelt den ProductDetailController, sodass dieser keine contextspezifischen Beans in die View reichen muss.

In diesem Fall ist Lösung 3 zu empfehlen.

Keine Kommentare: