wiki:GalaxySoa

http://svn.rocketboots.com/os/galaxy/trunk/com/rocketboots/galaxy/galaxy.png

Galaxy (Experimental)

Galaxy does for components what Application.cfc does for directory trees, and more...

What is It?

By extending com.rocketboots.galaxy.Service you can create a ColdFusion component with the following behaviours:

  • All calls to the component take place with the component's own private application, session, and client scopes (this is at the class level, not the instance level - multiple instances of a component in a single ColdFusion server instance share the same scope).

  • Application attributes can be specified in the this scope, just like Application.cfc:
    passed through to cfapplication
    		
    	clientManagement
    	clientStorage
    	loginStorage
    	name 			Defaults to component name or superclass component name if endPoint attribute set 
    				on component - probably what you want to happen normally (read about 'endPoint' below)
    	scriptProtect
    	sessionManagement
    	setClientCookies
    	setDomainCookies
    		
    service specific
    		
    	synchronised		Default true
    	instances		Default 1
    	tickInterval		Default 1000 ms
    	version			Set to suit your own configuration management system, for example "$Id:$" to use 
    				with Subversion keyword expansion. 
    

  • Callbacks are added, similar to those in Application.cfc:
    onServiceConfigure()				Allows service properties to be set before the service is started
    onServiceStart()				Allows service properties to be validated (perhaps by a base class)
    						when the service starts
    onRequestStart(method, arguments)		Allows common pre-processing of method calls
    onRequest(method, arguments)			Allows dynamic handling of calls, like ColdFusion's onMissingMethod()
    onRequestEnd(method, arguments, result)		Allows common post-processing of method calls, result argument optional. 
    						If the handler returns a value it replaces the original result. 
    onServiceTick()					Allows background tasks to continue in a dedicated service thread
    onServicePause()				Allows specific actions to be taken when the service is paused
    onServiceDestroy()				Allows cleanup tasks before the service application context is deleted
    onServiceError(error, method, arguments)	Allows common processing of un-caught exceptions
    
  • If this.synchronised is true, all calls to the service will be single-threaded using an exclusive lock. This allows thread-safe access to all shared scope variables within the service application context.

  • If using this.synchronised creates a potential performance bottle-neck, set this.instances to a number > 1 and Service.cfc will create multiple applications to run the service, and round-robin calls between them.

  • If it exists the onServiceTick() handler to be called repeatedly in a separate thread while the service is running, with a sleep interval of this.tickInterval milliseconds between calls. Note that if two or more services are originally created in a single page request, their onServiceTick() handlers will not run concurrently but wait for a shared lock. This is due to a limitation in the way the current application context is shared between threads generated in the same request, but it also limits the amount of background processing that can occur concurrently which is probably a good thing.
  • A remote send() method allows web service, remoting and http (wddx/json) clients to call the service with all the benefits the class gives to local callers (unfortunately you can't call the methods directly because it is apparently impossible for !Service to intercept remote calls with onMissingMethod()).

  • By passing a list of wsdl urls to init() the component acts as a remote proxy to a remote instance of the component, allowing normal method calls to be transparently converted to send() calls for remote ColdFusion clients. If more than one wsdl is listed the component will round-robin calls to the web services, providing simple load-balancing. If the version properties of the local and remote services do not match send() will throw an error. TODO: Failover and sharing server status with other services and ColdFusion instances.

  • Adding endPoint="true" to the cfcomponent tag means that the service will use the immediate superclass name for it's application context (otherwise it would use it's own component name by default, creating a separate context which is probably not what you wanted). This is a useful shorthand for when you want to define a service in a package outside the webroot and then expose it to remote clients by placing a subclass somewhere under the web root:
      	  file {webroot}/.../SampleService.cfc:
      	
      		<cfcomponent extends="com.sampleco.services.SampleService" endPoint="true"></cfcomponent>
    

  • A com.rocketboots.galaxy.ServiceManager instance will be created, accessible through server.serviceManager to allow monitoring and centralised control of all services in a ColdFusion server instance. It has the following methods:
    TODO: copy details here from method comments in ServiceManager.cfc
    
    getServiceStatus() 
    pause([criteria])
    continue([criteria])
    kill([criteria])
    bulkStatusSet(criteria, status)
    getServiceStats([criteria])
    

Usage

You can think of your service subclass as a 'gateway' or facade to a ColdFusion application that can only be reached by calling a method on the service - template paths and form/url scopes are replaced by a method name and arguments. You can keep instances of the service in persistent scopes in client applications just as you would any other cfc instance, but they also work (like Application.cfc) in transient one-off remote requests or even cfinvokes (shudder).

You can create as many instances of the service as you like in your code and they will all point to the same service in a single ColdFusion server instance - the service is tied to the cfc class, not the cfc instance.

Like Application.cfc you should not store your service state in the 'this' or 'variables' scope, but instead use the application (and perhaps client/session, although that is rather un-service-like) scopes. You can have as many components as you like inside the service - it is just a normal ColdFusion application - and you should be able to use your favourite dependency-injection-autowire framework without issue (not tested).

When you create a new service consider putting the bulk of the functionality in a base class, extend it and override the onServiceConfigure() method to provide your own settings for a particular use of the service. For instance you might have a com.sampleco.security.SecurityService to centrally manage authentication, authorisation and roles for a collaborating group of services. For a client project you subclass it as com.sampleclient.sampleapp.services.SecurityService and specify datasources/ other options in onServiceConfigure(). Ideally the subclass would be in your cf mappings / package hierarchy outside the web root and to expose it you would create another subclass in com.sampleclient.sampleapp.vhosts.main.SecurityService (webroot pointing to main) with endPoint="true". BTW you don't have to structure your packages like this, but it's how we do it.

For parameters and return values for service methods, remember the service may be remote and the client may not be ColdFusion or Flash, so arguments should be passed by value and complex types should be avoided (todo: ColdFusion to ColdFusion type-preserving remoting requests would be nice though).

This package brings ColdFusion's RAD development philosophy to Service-Oriented-Architectures (SOA). It lets you replace monolithic single applications containing many components with suites of co-operating, potentially distributed services, as long as you're clear about the dependencies between services and stick to them. The individual applications/services are smaller, simpler, more likely to be re-used and easier to develop with all the elbow-room granted by having your own thread-safe shared scopes and 'tick' thread to work in.

Road Map

Recently completed features:

  1. Improved multi-server management console to give better visibility for debugging and concept demonstration (alpha 0.7)
  2. Report onServiceTick() (and other callbacks/methods) statistics separately from outside requests (alpha 0.7)
  3. Fail-over between service instances (alpha 0.8)

Here are the features I'm currently working on:

  1. A proper demonstration application with some useful core services e.g. message queue, user directory
  2. Fail-over across servers
  3. More efficient use of threads, esp for people on ColdFusion Standard.
  4. Sharing of service status information across servers
  5. Additional handler methods called on the proxy/client side when working with remote services
  6. Remote "auto-wire" service discovery with "buddy" servers
  7. Plugin/Extension? Point mechanism for ServiceManager
  8. Unit tests

With all these complete we can move to beta status if people are interested in seeing a production-ready version.

Mailing List

 http://groups.google.com/group/galaxy-dev

Bug Reporting

Unfortunately our ticket system here is constantly being spammed, if you send bug details to robin (at) rocketboots.com I will add them to the log.

Download

Release http://svn.rocketboots.com/os/galaxy/tags/alpha_0.9
Bleeding Edge http://svn.rocketboots.com/os/galaxy/trunk

Attachments