Skip to main content

Project in Review - Part 1: Lessons learned

At this point, I think it's fair to call my project a success. So while everything is fresh in my mind, I want to get down things I felt worked very well for me, and things I felt did not, and lessons learned along the way. This is mainly for myself, but if anyone else learns anything, so much the better.

Lessons learned the hard way


MVC:

So trying to adopt MVC was a challenge. It was the first time I'd done it, though I'd read enough I was familiar with the ideas, and I had many mis-starts along the way - some of which got corrected and some didn't. In fact, I could write a whole series of blog posts on lessons learned here - and most of them come from a lot of reading on Java. But if I had to refine it to a single idea, it's "keep your concept clear". I spent a lot of time trying to shoehorn everything into my model classes and I wasn't really clear what belonged in the controller except as something between the model and the front end - which seemed largely unnecessary since I was wiring my model beans directly to my UI components. Should I render in read mode or edit mode? Ask the model what mode it's in. Can the user modify a particular field? Ask the model what the rules are. This led to some painful refactoring, because the simplest answers turned out to be wrong.
  • Model is state data. Anything that is unrelated to accessing or modifying state does NOT belong in the model.
  • Controller is the XPage controller. There is not a 1-to-1 relationship between controllers and models unless every model is associated with one and only one XPage.
  • There should generally be a 1-to-1 relationship between controllers and XPages. In my case I have a default fall-back controller (from which the other controllers extend and which provides common methods), so if an XPage needs no specific functionality, a controller isn't needed.
  • If you have something that is sort of like a controller, in that it provides UI functionality, but is tied directly to a model, what you have is neither a controller nor a model, so don't try to put that functionality in either. Down the road when you need to use that functionality in a different context, you will almost certainly regret having it tied to one or the other, and it will lead to copy-paste code or some sort of abomination where classes are being invoked for things other than their reason for existing.

Understand the existing objects

What is DominoDocument and how is it different from Document? DominoRichTextItem vs. RichTextItem? I found it confusing and just ignored it. It turns out that DominoDocument is a serializable representation of a Document with the DataAccess, handling of RichText fields, automatically handling the conversion between scalar values and multi-values - basically all the hard parts of serializing Document data. I wasted a fair bit of time on my Model objects when I could probably have just used or extended this one.

Understand what you are borrowing

I created a lot of work for myself by copying code from others that just did everything automatically, and I discovered weeks later that it didn't exactly do what I needed it to do automatically. And I had to write a lot of confusing and potentially fragile code to shoehorn my needs into a structure that just wasn't what I needed.

Packages

Okay, I confess. My packages are a mess. I think I have too many of them for my relatively flat hierarchy, but at the same time I'm not sure how to manage it any better. I have 2 high-level hierarchies: <domain>.common and <domain>.<project> and from there it's a muddled mess. I have a xxx.yyy.data package where all of my data beans are. Except for User data, because that consists of a User interface, AbstractUser with common User methods, UserBean for referencing user data, and CurrentUser, which extends UserBean with some context-specific stuff. Maybe I should have created xxx.yyy.data.user? But then does that mean I should create sub packages for the rest of my data beans even though they are single classes instead of just one? Or was it a mistake to split off the user stuff - except I rather like having all 4 user classes tied up with a bow.

I have a package where all my ActionListeners reside. That makes them very convenient to include in my XML because the hierarchy is always the same. Yet conceptually they are extremely unrelated. Two are for logging user actions, a couple others are for keeping the UI state in sync between the browser and the back-end. Ultimately I realize there is no rule for this - it's a combination of an art and largely irrelevant. But if there were a rule, it would be pick something and then stay consistent.

Identity and Keywords

In short, I advocate using enums everywhere more than one class needs to be able to get an object by key. Within a class, I normally use a private static if it's just a single value, like a reference to requestScope.getValue(CONSTANT_KEYWORD). I'll create a private enum if there are several values related to an identity.

There is a lot of coordination that needs to take place between a bunch of layers. For one field, I might need to know a simple id for XML, the JSF id for javascript, a key-word for identifying it in an EventHandler, keys for other elements that need to inform or be informed about the field, perhaps what scope it is stored in, and the key for reading/writing to that scope. An enum lets you centralize all of that information in one place instead of hard-coding strings all over the place and then not being sure necessarily which string is used where. It also gives you instant feedback if a bad key gets used somewhere, instead of just returning null or the wrong value (particularly when you can't know for at compile time whether null is due to a bad key or because that key hasn't had anything put in it yet).

Incidentally, I found it very appealing to have a Scope enum. It allowed me to use Scope.VIEW.get(), which I find to be easier to read. There is no magic to it: there is a getMap function that invokes the VariableResolver. Then, I wrote function wrappers for all of the Map functions, allowing me to treat my enums as Maps. I did make the decision not to implement the Map interface. That might have been handy, but I instead opted to explicitly make my scope maps Map<String,Object> instead of Map<Object,Object>. That saved me a bunch of unchecked cast warnings.

Keep methods to a single screen of code

This is not a hard rule, but a guideline. As a hard rule, it might encourage packing too much into a single line of code, hurting readability. But as a generality, it is much easier to read and follow code if you don't have to do a lot of scrolling and hopping around to figure out what is going on. Name your functions something that is self-documenting. Toss a couple lines of Javadoc explanation if anything seems remotely confusing.

Javadoc NOW, not when you have time

I wrote my code to be self-documenting. Which means I used very descriptive variable and function names so it is extremely obvious what is what. So naturally I ran into several cases where the code I wrote didn't give me the result I was expecting. You don't have to write up paragraphs or meticulously document every @param, and @Exception - just a sentence or two about expected inputs, case-sensitivity, caching, and whether a null might be returned. A prime example would be the Enums I was just talking about. If I have a method public EnumObject getByKey(String key), I need to add some Javadoc saying if the key is case-sensitive or not. I need to specify if it throws an error if there is no match, or returns a null. Because unless you do it exactly the same every time, in 3 months, you aren't going to remember a small detail like that, and it could cost you time and hair-pulling.

Comments

Popular posts from this blog

Pass data between XPages and existing LS scripts

I'm working on modernizing a fairly hefty application with a lot of existing script libraries which we want to leverage within the XPages environment. Here is a technique that works very well. First, create an in-memory document in SSJS. We can set any input values needed for the back end. Then we pass that document to a LS Agent which can work it's magic using the values set in SSJS and use the same document to return values back to the XPage. Here is how it works in detail:

Quick tip: Convert a number to String in EL

I just had a need to do this and a Google search didn't immediately turn up a solution. So I thought for a couple of minutes and came up with this: value="0#{numberVar}" This takes advantage of the way Java auto-converts objects to strings when doing a concatenation. So if your number is 13, Java EL turns this into new String("0"+13), which becomes "013". You can then strip off the leading zero or just parse the string back into a number.

Project in Review - Part 3: What didn't work

Of course, not everything was an unmitigated success. I tried many things that didn't work out. Much of which I've removed and forgotten about, but a few things remain - either scarred into my psyche or woven too deeply to fix. What didn't work Storing my entire configuration in application.properties Using properties files is great. It let me get configuration out of a profile document and into something much easier to edit - particularly configuration that users will never see or maintain (and thus there is no need for an interface for). But I took it too far. The paths to the other databases are there, and that's good. But view aliases are also there, and that was a mistake. I already have a ViewDefinition enum that describes each view and all the information I need to know about it. I could have set view names there, but instead I'm reading them from the application config. I can change where a view is pointing without having to go into my code. Except of co