Comments were added after letting the initial document settle.

A Multiserver Architecture for Webware

I finally found a justification for the Webware package to exist. It can be the singleton service manager that allows other components to cooperate.



When Chuck said he thought Catalina was too bulky, I went back to look at ways to lighten it up. I discovered that there were three core features of Container that I liked:

The longer I follow webware-discuss, the more convinced I am that Webware very badly needs these features to be orthogonal.

The additional methods provided by org.apache.catalina.Container (get/set{Manager,Realm,Resources,Logger,Loader}) then began to look like warts placed there for convenience because there was no other good place to put them, just as WebKit.Application has become a place to hang everything that wants to be globally accessible. (especially since, in practice, a given container will often just delegate to the service provided by its parent -- overriding [Session]Manager or Logger is rare.)

Then I happened to look at Enhydra more closely. I realized that the web container part of an app server is a relatively small piece of the whole picture. Enhydra's startup class is called MultiServer. Aha! [Session]Manager, Realm, Resources, Loader, and Logger are services! All of those warty methods can be replaced by 3: getService(), addService(), and removeService(). This also opens things up to other services that might want to be associated with a container: e.g. CanKit. It also gives TaskKit a place to call home; since it is a global service, it can just register in one place (i.e. Webware.getKernel().addService("TaskKit",newTaskkitService)), and everyone who wants to use it just says (for example) Webware.findService("TaskKit"). Servlet factories can also register their availability, and a particular container can decide whether to use them or not. A client that does not care about the specific implementation can ask for a generic service: e.g. a container might say Webware.findServiceByClass("session") to get a registered instance of SessionSQLStore. [Impl. note: These Webware module functions would delegate to the singleton kernel returned by Webware.getKernel()].

Oops. There is a naming problem here. Enhydra calls anything that runs a service. Catalina uses "Service" to refer to the association of a root container with a set of Connectors (<=> a ZServer "module"). A catalina "Server" is the thing that sits around listening to a port waiting for a shutdown command. A Server controls zero or more Services. I am torn between the two. Maybe I could use the Webware term "Plugin" to mean an Enhydra Service. Then I am free to use the Catalina definition of Server and Service. hmmm.
Another reason I like the catalina name Service better than "Engine" is because I may not want to publish an Engine container at all.

After reading Chuck's Introduction to Webware for Python, I understand that his primary goal was to build only the web container. The rest of the application should stand on its own feet. I don't think the goals of this architecture conflict with that. The basic services are just that -- basic -- they fit well within the domain of a web container.

Indeed this architecture dramatically enhances the extensibilty (Chuck's second stated goal). I am no longer required to subclass Application or AppServer to change behavior -- extensions are added by composition rather than by inheritance, making it much easier to introduce a new service, or to use a Webware service in another application.

No, there is nothing earth-shatteringly new here -- these concepts are already present in Application and AppServer -- but this refactoring of responsibilities makes Webware much easier to customize. For example I can decide whether to load CanKit (I don't have CanContainer methods polluting Application, Request etc.) The code to load the SessionStore is no longer specific to that service; I might just as easily load HTML Tidy, XSL, LDAP, or a ZODB connection as services.

Another advantage of this approach is that individual services can be loaded, unloaded, or reloaded without bringing the whole server down. Services can be loaded lazily: if running from OneShot.cgi, it doesn't make sense to load PSP unless the request I am handling refers to a PSP page.

The most compelling example I can think of is adding multiple connectors. In the existing webware architecture, adding an FTP server means creating a new AppServer class, much like Geoff Talvola created AsyncThreadedHTTPServer. The class is the startup class, so it would take some major tweaking to get both an HTTP server and the standard "webkit protocol" server to work together (e.g. both would try to create an Application object [maybe Chuck never intended Application to be a strict singleton, but it does make sense to share it in this case]) Following the medusa pattern (a special case of the multiserver pattern) makes adding an FTP, BXXP, NNTP, or console (a.k.a. monitor in medusa speak) connector trivial. Then there really is a need for HTTPServlet to be a specialization of Servlet -- now we have FTPServlet and NTTPServlet to speak those protocols. ((WebDav|XMLRPC)Servlet extends HTTPServlet because (WebDav|XMLRPC) extends HTTP and there is no need for a different connector, but NNTP is a different animal.)


The server administrator gets some new tools. Instead of just AppServer.bat and, he now has:

start [service...]
status [service...]
stop [service...]
restart [service...]
service <service-name> <command> [parameters...]
[ more commands to allow adding/removing containers (contexts) to a running server ]

We can also make it easier to run multiple servers with different characteristics by moving the configuration and state out of the python packages.

    bin/  -- control and management scripts like the ones mentioned above
    lib/  -- contains python packages for common components
	    In the default configuration, this is the only directory that 
	    needs to be added to the global sys.path.
    config/ -- equivalent to the Unix /etc
	services/ -- like /etc/rc.d/init.d -- one directory for each installed service
	servers/ -- ?? common "named" server configurations ??
    var/  -- 
	tmp/  -- save large POST contents here by default
	cache/	-- generated PSP classes,
	data/	-- ??
	sessions/  -- *shared* session data files
	run/ -- a good place for .pid files and control sockets (e.g. address.txt)
    webapps/ -- Anyone have better name?
	    # WEB_INF and WEB_VAR are carefully excluded from the servable document tree
	    WEB_INF/ -- local config file
		lib/ -- private modules -- added to "local" sys.path
	    WEB_VAR/ -- local version of var (maybe WEB_DATA)
    doc/ -- ?? maybe each installed component could put a web app (expanded or in a .war) in this directory

You can probably tell that I hate mixed-case directory names. This particular layout is completely optional -- The multiserver architecture works just as well with the existing layout. However, the existing layout does not make a lot of sense if WebKit is a peer service rather than the driver.


In progress. Check back soon.