Thursday, June 28, 2007

How to call domain object methods from JSP

It seems like the earlier post about super() spawned at least a little (offline) discussion, so here's another comparison of some different ways of approaching a problem. People should definitely feel free to post some comments , lend your opinions, offer up code snippets, etc. Ok, so here's the deal. We have a middleware domain object that has a method with arguments, and we essentially want to call that method from a JSP page. To make this concrete, let's suppose we have some software supporting a blog. Here's the middleware for a blog post:

public class Post {
 public String getTopic() { ... }
 public List<Comment> getCommentsByRange(int first, int max) { ... }
}
So, we let's say we have a web page where we want to show, for example, the first eight comments under the post itself. Probably the JSP page will want to, somehow, essentially call post.getCommentsByRange(0,8).
Approach A: Call from controller, add to model. Our controller will look like this (assuming Spring MVC).
public class PostController implements Controller {
 public ModelAndView handleRequest(...) {
   Map model = new HashMap();
   String postId = request.getParameter("postId");
   Post post = postDao.getPost(postId);
   List<Comment> comments = post.getCommentsByRange(0, 8);
   model.put("post", post);
   model.put("comments", comments);
   return new ModelAndView(..., model);
 }
}
And our jsp page will contain the following snippet:
<c:foreach items="${model['comments']}" var="comment">
  <!-- display a Comment in here -->
</c:forEach>
Pros:
  • No extra classes or taglibs needed.
Cons:
  • Presentation/business logic appears in the controller, which is arguably not its job.
  • Controller gets really messy the more items like this have to get added to the model.

Approach B: Create a data transfer object. This creates a special class whose methods take no arguments:
public class PostDTO {
  private Post post;
  private List<Comment> comments;
  public PostDTO(Post post, int start, int max) {
    this.post = post;
    this.comments = post.getCommentsByRange(start,max);
  }
  public getTopic() { return post.getTopic(); }
  public getComments() { return comments; }
}
Now the controller looks like as below. Notice that there's only one object put into the model, so the controller is very simple.
public class PostController implements Controller {
 public ModelAndView handleRequest(...) {
   Map model = new HashMap();
   String postId = request.getParameter("postId");
   Post post = postDao.getPost(postId);
   PostDTO postDTO = new PostDTO(post, 0, 8);
   model.put("postDTO", postDTO);
   return new ModelAndView(..., model);
 }
}
Finally, the JSP snippet looks like:
<c:foreach items="${model['postDTO'].comments}" var="comment">
  <!-- display a Comment in here -->
</c:forEach>
Pros:
  • Standard JSP.
  • Controller stays simple, and does not contain presentation logic.
Cons:
  • We have to create this extra DTO class which doesn't do anything particularly interesting.

Approach C: Use a taglib I'm not going to show the code here (want to keep to my self-imposed time limit), but essentially, we create a taglib that knows how to call the getCommentsByRange() method of a post. The JSP would look like:
<c:set var="comments" value=""/>
<mytags:postComments post="${model['post']}" start="0" max="8" outvar="comments"/>
<c:foreach items="comments" var="comment">
  <!-- display a Comment in here -->
</c:forEach>
Pros:
  • Controller stays simple, and does not contain presentation logic.
Cons:
  • We have to create a taglib which pretty much exists just to allow the jsp to invoke a method on the domain object.
Exercise for the Reader. Which approach would you use, and why? Does it depend on the situation?

2 comments:

forum widget said...

Thank you so much for this post :) I followed the second approach notes - and it really helped me. I really appreciate the effort you put in order to help others...

Yury Mikhailenko said...

Neither way looks right for me. Is there anything else left?