Skip to main content

Project in Review - Part 2: What worked

Here are some of the techniques I adopted that turned out great. I made no claim to innovation or superiority.

What worked?


PhaseListener:

Having a PhaseListener to let me know where I was in the JSF lifecycle was extremely helpful. I used this information constantly to determine the state of various objects at any given time. In fact, as much as I know it's bad technique to troubleshoot via print statements, I found it invaluable to be able to observe the exact order of state changes. That being said, it's important to note that THE CONSOLE LIES! If you see something very unusual happening, like the lifecycle restarting multiple times or events just never finishing - look at the console log. My experience was that what appeared to be going on in the log was not necessarily what was going on, but if it looked crazy it was an indication that something wasn't right.

I don't care if you're trying to go pure Java as I did, or pure SSJS - a PhaseListener is a single, standalone class that can be turned on and off with a simple config change and it provides invaluable feedback.

Services

I think the specific definition of a service nebulous, but once I absorbed the concept of a service object, some things came into focus that were lacking clarity before. Do I have a need to provide a non-static link in my XPage? It gets implemented in my LinkService. Prior to that, I tried packing things like that into the model or controller bean. Which violates the single-use principle and led to some abominable results when I tried to get out of it by writing more code. Do I need to compute the styleClass of a component (because I need to set it to 'active' or not based on what the back-end knows about the UI state)? UIService. Do I need to dynamically build a PLUploader script so that files get uploaded to the right directory? UploadService.

I need to refine my technique, but it freed me from some self-imposed shackles.

getBean()

It's really handy having a static method that returns the current managed bean. I could have just used the JSF variable resolver (and in fact that is how it's implemented) but by having the bean retrieve itself you don't have to worry about a typo getting the wrong (or no) bean. It also means that instead of having to infer the Object retrieved, you know exactly what it is. This is a minor convenience, but also a minor effort.

REST service for typeahead

Initially, I created an XAgentController that provided a typeahead service. But the easiest code to maintain is code that was never written. I scrapped it and dropped a REST Service control onto a service XPage and never looked back. That was the right move.

Filename obfuscation - maybe?

One thing I did with my files is create a bi-directional Map (a pair of Map where the key/value pair is reversed in the second map, so using either value returns its match) to replace file names on the website with a random string. I initially did this for security reasons so that my FileService couldn't return a file whose name was guessed. It turned out even better than hoped because I store that table in the Session scope, which means the codes aren't persistent and are specific to each user. That means you can't bookmark or share a link, but that isn't how this particular application is used. What remains to be seen is whether this will bloat the session object and cause memory issues.

RequestParameterMapService

Maybe there is a better way to handle this, but this technique proved very handy for a particular need. My use-case is probably specific enough to not be of interest, but I needed access to the submitted values before the Invoke Application phase, where they were used to compute some values for the response. So I created an object whose job it is to parse out all the values submitted on the request - not just the URL parameters. It wasn't difficult to implement, but until this solution came to me, I was really stumped for how to tackle this challenge without making a terrible UI.

Passing an object, not parameters

One of the things I picked up on late in the game is a technique of passing an object to a custom control. This was extremely useful for editing data - even entire documents - within a modal. This is something I know others are doing, but it didn't really take hold with me until I realized I was fighting against my managed beans. I'll do a separate write-up of this that goes into more detail.

Bootstrap

Many things about Bootstrap worked very well.
  • I don't have an eye for design. Bootstrap looks great.
  • About 2 months into the development process, I was told "Too much white-space. We need more data on the screen." I went to my html css entry and added 'font-size: calc(16px * .8);' Instantly fixed the problem. Bootstrap 4 uses REM for most measurements, so my text and white-space stayed proportionate, and I got 25% more information on the screen.
  • The UI responsiveness was vastly superior to AJAX.

GetMap

I've blogged about this before. It doesn't accomplish anything that couldn't be done with SSJS, but it does help to keep all of the logic in Java. And it helps considerably with maintenance and refactoring. I've made a bigger deal out of it than it actually is, but it is also dead-simple to implement, so why not?

Pure Java

So I set out on the project to code entirely in Java. I wanted stronger typing. I wanted compile-time checking. I wanted to create a single model for logging and error handling. And it took some effort to learn or develop some of my techniques, but they were extremely successful. The biggest challenge is how easy getComponent() is in SSJS vs. the same functionality in Java. This can be solved by using the 'binding' attribute of components, but it can be a challenge in re-usable components or components within a repeat control. The binding must be unique or you will have troubles, so you can't hard-code it into the component. I didn't have any luck in making the binding computed. But in the end, I was able to create my entire application without a single line of SSJS. It may have required more time, but I think the result is much more readable, maintainable, and less fragile.

Also, it would take an inconvenient amount of room to explain, but there were a couple of instances where a significant refactoring had to take place. My initial security model, for example, had holes all over the place. I wrote and debugged an entirely new model in two days. I also had a significant problem with my core data model in that I made some assumptions that only failed after months of development when some complex elements were developed enough to test. That took a lot longer than 2 days, but way less than it could have if I'd been less of an encapsulation freak.

Separating application from data, and keeping the old data schema

Normally when you rebuild and application, you wind up having to do a data migration, which comes with it's own set of headaches.
  • Handling data which does not fit the new schema
  • You either fragment your data, or everyone is required to cut over at the same time
  • If any bugs or missing functionality makes it through testing, you are faced with a roll-back, possibly having to migrate new data to the old schema, and then having to schedule a new maintenance window and re-migrate data after the bug is fixed.
Now I'm not going to pretend maintaining compatibility doesn't come with it's own set of costs.
  • I had to duplicate some data fields where I had to establish a convention that the old, ad-hoc design didn't match.
  • I missed some opportunities for improvement. There is still text data being stored in a rich-text field (which created issues displaying it in the view).
  • Existing bugs in the old system could play havoc in the new one.
Ultimately, I'm putting this one in the 'win' column - at least for this project.

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