XPages MVC: Experiment II, Part 2
Thu May 24 20:43:00 EDT 2012
- XPages MVC: Experiment I
- XPages MVC: Experiment II, Part 1
- XPages MVC: Experiment II, Part 2
- XPages MVC: Experiment II, Part 3
- XPages MVC: Experiment II, Part 4
Continuing on from my last post, I'd like to go over the collection and model structures I used in my guild-forums app.
I set up the data in the Notes DB in a very SQL-ish way (in part because I considered not using Domino initially). Each Form has a single equivalent view (for the most part), which is uncategorized and sorted by default by an ID column. There is one column for each column that I want to sort by and, in the case of simple documents, one for each applicable field, containing only the field name; additionally, I created $-named "utility" columns for when I wanted to sort by a given bit of computed data or have a second secondary sort column for a given column. For the most part, the data is normalized, but I had to give in to reality in a few situations, for things like sorting Topics by the their latest Post dates.
For collection classes in Java, each collection class file contains one public class for the "manager" and one list implementation class, both of which extend from Abstract versions, so the actual implementations may have very little code. For example, here is the "Groups" Java file (minus some Serialization details):
public class Groups extends AbstractCollectionManager<Group> {
protected AbstractDominoList<Group> createCollection() { return new GroupList(); }
}
class GroupList extends AbstractDominoList<Group> {
public static final String BY_ID = "$GroupID";
public static final String BY_NAME = "$ListName";
public static final String BY_CATEGORY = "ListCategory";
public static final String DEFAULT_SORT = GroupList.BY_ID;
protected String[] getColumnFields() {
return new String[] {
"id", "name", "category", "description"
};
}
protected String getViewName() { return "Player Groups"; }
protected String getDatabasePath() { return "wownames.nsf"; }
protected Group createObjectFromViewEntry(DominoEntryWrapper entry) throws Exception {
Group group = super.createObjectFromViewEntry(entry);
Document groupDoc = entry.getDocument();
group.setMemberNames((List>String<)groupDoc.getItemValue("Members"));
return group;
}
}
The actual heavy lifting is done by the parent class, letting the specific classes be composed almost entirely of descriptions of where to find the view, what columns map to model fields, and any extra code that is involved with that object mapping. Other classes are larger, but follow the same pattern. For example, here's a snippet from the manager for Posts:
public class Posts extends AbstractCollectionManager<Post> {
// [-snip-]
public List<Post> getPostsForTag(String tag) throws Exception {
return this.search("[Tags]=" + tag);
}
public List<Post> getPostsForUser(String user) throws Exception {
return this.getCollection(PostList.BY_CREATED_BY, user);
}
public List<Post> getScreenshotPosts() throws Exception {
return this.search("[ScreenshotURL] is present", PostList.BY_DATETIME_DESC);
}
}
The parent abstract class provides utility methods like getCollection
and search
that take appropriate arguments for sorting, searching (I love you, FTSearchSorted
), and keys.
As for the individual model objects, they start out as your typical bean: a bunch of fields with simple data types and straightforward getters and setters. Additionally, they contain "relational" methods to fetch associated other objects, such as the Forum that contains the Topic, or a list of the Posts within it. Finally, the model class is responsible for actually saving back to the Domino database, as well as taking care of any business rules, like making sure that the Topic document is updated with a new Post's date/time.
The result is that a new object type is easy (-ish) to set up, with all the code being written contributing specifically towards the task at hand. And, since the nuts and bolts of XPages are Lists and Maps, working with these objects is smooth, taking advantage of Expression Language's cleanliness and Server JavaScript's bean-notation support in the UI and Java's clunky-but-still-useful collections features.
Next time, I'll go over some of the specifics of how I deal with fetching collections and individual records, use reflection to save me some coding hassle, and other tidbits from the giant messes of code that make up my abstract classes. After that, I think I'll make a fourth post to detail some down sides to my approach as implemented.