This is an introduction for an idea of mine I’m calling Lift Delegation, a feature that I’m hoping to implement in the Lift Framework that would allow Lift applications to be implemented in a master/delegate model with a small amount of state and content from the master made available to the delegate application. So, what does that mean?
Loosely speaking, this feature has a few principal goals:
- Apply the microservices model, which has a has a lot of benefits in the backend world, to building a UI application as a route to break up large, complex applications into smaller applications that represent a subset of the functionality the application provides.
- Allow for enough sharing of state and content from the master application to present this master/delegate application in such a way that the user is completely unaware of this architecture behind the scenes, which will promote a better user experience than existing alternatives (iframes, SSO redirects to other URLS, etc).
- Still permit the delegate application to maintain some state of its own, and allow it to use Lift’s AJAX and Comet support transparently.
- Enable delegate applications, representing individual functional groups, to be released separately from the master application.
So, that’s a lot of jargon all at once isn’t it? If you’re already on the “this is awesome” train skip down to the specification section. If not, let me take a minute to explain the cases where I think this would be useful.
The Lay of the Land
I’ve noticed a lot of cases where there are web applications that are in actuality application systems or a system of multiple user-facing applications that are functionally distinct but strongly related by a usage context. The classic example of these are personal health insurance portals like those provided to customers by companies like United Healthcare and Blue Cross Blue Shield. If you visit such a portal you’ll first land on a login page that has its own url. After logging in you’ll be redirected through a series of redirects that land you at your portal homepage which oftentimes has a distinct domain from the login system. Then, if you click the links to get pricing information on perscriptions you’ll typically be bounced to yet another domain that is run by the prescription insurance provider (often not the same company as your Health Insurance proper).
These are several different applications that the insurance companies rightly determined are closely enough related by their usage context that transitioning between them should be as seamless as possible. They are, as such, an application system.
I typically see these application systems take one of four forms:
- As in the health insurance example above, they will take the form of multiple applications with completely distinct implementations that use Single Sign-On to make transitioning from one application in the system to another as easy as possible for the user.
- In other cases these applications are only distinct on their backend and there is a single unifying UI application that’s using some sort of API (REST, SOAP, etc) on the backend to pull information from the distinct applications.
- In rare cases these days you’ll see applications in a larger system wrap other applications in an HTML iframe in order to try and present it in a unified manner to the user.
- Perhaps more common than we’d like to admit: the non-application-system application system. Or rather, a massive application that should probably be a modular system of smaller applications but isn’t.
There are, of course, problems with all of the above. Both items (1) and (3) above often tend to provide a substandard user experience. Visual style differences are often jarring. Flows that require the user to jump from one application to another are often not optimized for quick access, sometimes resulting in going through ten pages or more to get to a receipt for a recenty prescription refill, for example. There are some cases where these options may not be avoidable, such as when a single company doesn’t have control over all the applications in the application system, but this arrangement is far from optimal.
Most engineering teams know that bouncing from one application to another is a pain, so it’s not that uncommon for them to opt for taking road (4) and building a single, larger application that just does everything their user needs to do. When companies take this route they’re not entirely misguided. Using a single application certainly makes sense if your application system is still evolving and the individual modules are not yet well specified enough or complex enough to warrant the configuration overhead involved in a different arrangement. It’s also much easier to ensure implementation, security, and design uniformity across a single application. However, if your application continues to grow in complexity, you may find that you have issues with changes in one module having unintentional side effects in other modules, that it’s an issue to have all the various modules tied to the same release cycle, or any number of other problems that come up with a large, monolithic application.
Finally, (3) is perhaps the closest to my ideal solution that I’ve seen in the wild to date. In this one, the actual backends are separate and there’s a single application that is unifying the backends and appearing to be one application to the user. The problem here is that you’ve completely decoupled your implementation of the display of your application in a user’s browser from your implementation of the application’s guts at a repository level. I don’t think that this decision is inherently bad and makes sense in many cases, but also comes with its own set of challenges. For example, at times the loose coupling between the backend and the UI may not be a good thing because they are not, in fact loosely coupled. We oftentimes think of coupling as purely a code organization help, but if the truth of your application is that it’s likely by changing a backend component you’ll need to change your UI you’ve still got tight coupling. Your code and project organization just no longer reflects that and you have to deal with that truth as you roll out new functionality. Those things have to stay synchronized and that can involve extra human awareness on an operations level.
So having taken stock of some existing examples of this, what, exactly, am I proposing as an alternative?
HTTP servers have, for a long time, had the concept of a reverse proxy, which retrieves resources on behalf of a client from one or more backend servers. My suggestion is essentally to move that concept to the web application layer itself. However, a pure reverse proxy wouldn’t exactly suffice here for a few reasons.
First, Lift is stateful. With the way we use that state security is a bit easier for us. We can give the client much less information than we might otherwise be required to in order to maintain information about who the client is and what they’re doing. It stands to reason, then, that if you have a larger system of applications that all integrate seamlessly that there might be some state that you want shared between the master application, the one that’s at a public url that the user can interact with, and the delegate application, the backend that the master interacts with on the user’s behalf. Comets, AJAX handlers, and everything else that depends on state to function correctly should be usable from the delegate application as if it was a standalone Lift application interacting with a user.
For the reasons above, I think that this type of reverse proxy application system is best implemented by allowing two-way communication between the master and the delegate application. All of these abstracted over the concepts that already exist in Lift so that, ideally, the only places where delegation matters to the user code in the delegate application is when they’re authoring their Boot.scala file that sets up the
LiftRules for their application.
In the next section, I’m going to go into exactly how I see this working out on an implementation level.
I think that such a delegation architecture, as mentioned above, could be implemented with a few specific changes to the internal workings of Lift. Specifically, I think we’d be looking at the following API changes to the framework:
- Impelementation of
()=>Box[Map[String, String]]that produces a map with the short names of delegates as the key and the hostname and port that those delegates can be accessed from as the value. If
Empty, the default value, delegation is disabled. This will be defined in the master application.
- Implementation of
Box[String]contains the hostname/port of the master application. This will be defined in the delegate application.
- Implementation of
Box[String]contains the name this delegate will be referenced from according to the master. This will be defined in the delegate application.
- Implementation of a new
Delegatedparam for SiteMap’s menu entries. Semantically, this would tell the sitemap that everything under a particular URL is delegated to a different delegate Lift application and provide the identifier so that the actual URL can be pulled from
- Changes to
Locto provide a
delegatedResponsemethod. During a normal request the
processRequestmethod would invoke this method before invoking
earlyResponse. If the
Locin question has
Delegatedattached to it, it would result in the Framework issuing an identical request to the delegate application. It would return the response from the delegate to the user once the delegate finishes doing its thing.
- Changes to the
ajax_requesturl generation code such that if the Comet or AJAX request was created from within a delegate application, its URL will take the format
(comet/ajax)_request/[delegatename]/[identifier]. The master application would continue to use
(comet/ajax)_request/[identifier]for its comets and ajax elements.
- Changes to the comet and ajax handling to correctly delegate those requests to the correct application.
- Impelementation of a
MasterSessionVarthat behaves identically to a normal
SessionVarexcepting that its value can be retrieved by a delegate using a
DelegateSessionVarof the same name. Whatever is stored in this Var must be JSON serializable. You may be required to provide a
Formatsobject lift-json can use to write it out to the wire. The
DelegateSessionVarwill probably exhibit the same lifetime as a
RequestVarso that it doesn’t constantly have to go fetch the value.
- Changes to the
lift:embedsnippet provided with Lift to allow it to look in the master’s webapp directory for templates.
Most of this is fairly easy to do with simple HTTP request proxying. I think that (7) could be tricky with regard to long polls not occupying threads. With regard to (8) and (9) I think these will best be serviced by an internal REST API that the master and delegate use to talk to each other. The endpoints will be exposed on the master application and probably look something like this:
- GET /delegation-api/session-var/[identifier] – This will be used by the delegate application to read the value a
- GET /delegation-api/template/[path/to/template] – When a delegate application can’t find a template it wants within itself, it will make this request to the master in the hopes of finding the template it wants. The result should have already been processed through the rendering system of the master – so all snippets should be executed against it before returning it to the delegate.
I expect there will be some good and bad things about this architecture. It will certainly increase application complexity a bit. But I think that for applications that need it, this architecture will be able to provide a superior user experience to the existing alternatives.
There are a few things that I still haven’t quite figured out. Including:
- How to have a delegate application to know which session it needs to reach into on the master when retrieving a session var.
- How to handle the fact that the container the delegate is running in will want to issue its own JSESSIONID cookie. Maybe the code in the master that proxies the request to the delegate handles this?
- How this will impact performance and occupy threads.
So this is my official request for comments on the above. I’m interested in hearing what the Lift community (or the tech community in general) thinks of the proposal I’ve made. I fully acknowledge that it’s far from done, but I hope that it’s something that people are interested in seeing done.
If you’re interested in contributing feedback, please contribute on the Lift mailing list thread. That’s where we do all our discussion and, as such, comments on this post are disabled.
Thanks for reading and let me know what you think!