Musing About Multi-App Structure
Tue Apr 28 12:25:39 EDT 2015
One of the projects I'm working on is going to involve laying the foundations for a suite of related XPages applications, and so I've been musing about the best way to accomplish that. I'll also want to do something similar down the line for OpenNTF, so this is a useful track for consideration. Traditionally, Notes and XPages applications are fairly siloed: though they share authentication information and some configuration via groups, integration between them is fairly ad-hoc.
I'm not sure what the final structure will involve, but for now I'll assume it will use my framework or something similar. Though my Framework provides a lot of the structure that will be needed in this sort of project, it doesn't answer the inter-app question. In each app, responsibilities are clearly defined, model objects follow a consistent structure (and can point to other data sources), and putting all the undergirding code in the plugin makes for very lean in-app codebases.
There are a couple main points of integration I can think of: permissions, findability (i.e. what's the URL to link from App A to App B), and business objects/models. The first way is best covered the traditional way - just make groups and use that in the ACLs - so I'm not going to sweat that one too much. Theoretically, it could be useful to have a companion app to manage ACLs in a friendly way, but that's not needed at this point. That leaves the others, which are related but have their distinct needs. I've been thinking of a couple potential setups.
One Big Honkin' App
I could just throw everything into one app. This would have some immediate benefits: everything is already "shared" by virtue of being in the same place already, and it would make it very easy to move the app "bundle" around and to have multiple distinct instances on a server. Additionally, caching opportunities would abound, so this could make for some heavy performance boosts - and I don't know of any particular performance down sides of having a lot of XPage code in one NSF. It would make it easier to handle common UI elements, but, since my development style already favors using stock or ExtLib controls paired with renderers, the need for reusable custom controls is lower than usual.
The down sides are fairly obvious, though. Putting everything in one NSF makes for a difficult-to-navigate codebase long-term, and with a suite of applications that will likely have multiple developers, that's a recipe for a miserable experience. Additionally, the XPages/NSF dev environment doesn't provide any affordances for compartmentalizing design elements - all XPages are just going to sit in a single flat list. There's the IBM-style convention of stuff like "admin_home.xsp", "prople_list.xsp", which I adopt for most apps, but that would fall on its face when you start getting to "app1_admin_home.xsp". This would also make it difficult to develop a new companion app down the line, adding to the compartmentalizing woes.
So... I don't think I want to do that.
Business Logic in a Plugin
I could go the almost-opposite route and move most of the business logic into an OSGi plugin, and then the individual apps would contain almost exclusively UI - XPages that refer to these plugin-side model objects and deal just with the mechanics of displaying the data and interacting with the user.
This would have some nice advantages. The XPages apps themselves would be extremely slim and I could move what common custom controls do exist into the plugin. That would remove a chunk of the worries about bundling absolutely everything into one place, and the split of "logic" and "UI" is a classic and important one, and would scale nicely in a situation where developers are split into front-end and back-end. It would also retain the cache and share-config benefits of the "one big app" approach.
However, this would retain the problem of monolithic business logic, solving very few of the issues of having multiple developers working on separate modules, or wanting to add new ones to an existing basis. Additionally, the process of developing each module would be a huge PITA: changes would involve parallel modifications to the plugin and the NSF, and the experience of rapid development in an OSGi plugin is not great. This would also wander into the multi-tenancy problem, where the centralized code would have to know about distinct instances of the app group deployed on the server. This is a problem that will show up in most configurations, but I don't think this option brings enough to the table to compensate.
"Normal" separation
I could also go the "normal" route and have the apps know about each other in a fairly ad-hoc way. This wouldn't be too bad, overall - the apps could find each other via in-app configuration documents (or, most likely, a more-centralized variant on that approach).
The primary benefit here is that developing each app would be very easy, since it would be the same as any other Framework-based app. And, while the apps wouldn't be truly sharing code, they would be structured similarly, lowering the hurdle of working on multiple ones. Modularity and splitting responsibilities between multiple developers would be fairly straightforward.
The severe down side, though, is the question of shared model logic. If one app has a notion of "Person" that is a shared concept across all apps, how do the others know about it? I could copy the Java classes around from app to app - they'd all point to the same data - and make sure they're in sync either via templates or manually, but that is awful. This seat-of-the-pants sort of integration wouldn't go far enough.
Services and Coordinator
This approach would be similar to the "normal" separation, but I would develop a way for apps to pull in the business logic from other ones. For example, if an app wants to use the same "Person" class that another app defined, I could write a "coordinator" plugin that would find the class in the appropriate app and load it for this one. This is actually similar to something my Framework already does: its REST services consist of a JAX-RS/Wink servlet in the OSGi plugin which loads model objects from inside the NSF without having to have in-depth knowledge of the class.
This is made relatively simple by the fact that my model objects and collections implement standard interfaces - DataObject
and List
respectively, plus more-specific variants for additional capabilities - and they enforce their behavior and constraints through the standardized methods. So if, for example, the Person
object has a getManager
method that returns the person's manager as an object, calling getValue("manager")
would use this method and return the right thing. The "consumer" code wouldn't have the benefit of seeing the actual methods on the class and so would have to know what to call the fields by convention, but honestly I've found this convention-based route to be entirely practicable.
The other side of this would be the "coordinator" plugin, which would handle knowledge of how to access other parts of the suite, so the individual apps would request of it "I would like the Person manager from the StaffAdmin app", or something to that effect. This would have to include some way to solve the multi-tenant and database-location problem, which is a largely-unavoidable issue. Once this plugin is in place, it would presumably require little ongoing work, with primary development happening in the NSFs.
As the explanation-heavy and down-side-light nature of this section implies, this is the sort of approach I'm leaning towards.
Black Magic
There are also other approaches and techniques I've been thinking about - such as trying to figure out how to load XPages from one DB in a central one so they act like a single pool, coming up with some sort of abstract model-definition format and generating app UIs from that, or just write the whole thing in plugins. I probably won't go down those sorts of routes - this is supposed to be maintainable by Domino developers - but it's always good to think about the more-out-there options, so I don't cut off future possibilities.
David Leedy - Tue Apr 28 14:46:33 EDT 2015
"Additionally, the process of developing each module would be a huge PITA: changes would involve parallel modifications to the plugin and the NSF, and the experience of rapid development in an OSGi plugin is not great. "
I'm not sure I totally agree with you about this. I think it depends on your approach to the Library. We have a big library for a lot of our internal business logic. But when needed we'll go into the local app and extend/override code when needed. This gives up back the "RAD" flexbility that you might lose a little bit from going with the libraries. Actually our thoughts typically are that all code should go through the local extended classes first and only push up to the library what is more "rock solid".
Just my quick thought...
Paul Withers - Tue Apr 28 20:41:47 EDT 2015
Another downside of the plugin approach is that once live, any changes/ fixes to the plugin need an http restart to take effect. If that's combined with a change to an NSF, it makes things a bit more complex.
I'm not sure if JRebel could be hooked into Domino OSGi or "tell http xsp refresh" might allow the plugin to be reloaded without a full http restart.
Martin Rolph - Fri May 01 16:24:53 EDT 2015
I use a more old school approach to share common code between related xpages applications, including business logic/models and common controls.
In the middle is a common code nsf which contains the master version. The nsf is working application in it's own right with test/demo pages where you can experiment with the controls and functions e.g. getting model objects via DAO's etc. That allows you to keep the RAD as if you want to extend the business logic you still work in 'an' application but just not a specific one.
The real user-facing applications then inherit the common code using good old fashion template inheritance set on the individual design elements (including java classes!). So not the whole db, just the code which is common. That allows you to pull in changes quickly just by refreshing the db and with no http restart.
Not very modern but still works great for our multi developer projects.