In light of the increasing amount of services, I make the following
proposal for service redesign.  Hopefully this starts to give people an
idea of what I have in mind for Turbine 1.0 final, or even 2.0.  This is
fairly radical departure from where we are now, so be prepared to
stretch your concepts of what services are now.

Purpose: 
* To increase the portability of services
* To decrease the specificity of services
* To allow additional processing in services without clients needing to
change code (e.g. XML-RPC)
* To stretch the concept of a service from what it is today to a more
appropriate, OO model.

Assumptions:
* Less casting at the service level is preferable (in other words, I
would rather cast the result of a method on a generic service, than cast
an entire service implementation and not case the result.  Examples will
follow)
* Services will move beyond simple JDBC, JNDI types to more complex
schedulers, XML-RPC handlers, etc.

Implementation (High-Level):

Redefinition of the Service interface:

public interface Service {

  public void setName(String name);

  public String getName();

  public void init(Object ob);

  public Object get();

  public boolean execute() throws ServiceException;

  public void destroy();

}

That's it.  Nothing else ever needed.  Here's the way it works.

  /**
   * This will allow the name of a Service <b>instance<b> to be set.
   *   These names should distinguish an instance across the
   *   complete namespace of services in any given JVM.  In other
   *   words, it is the programmer's, or the service loader's,
   *   responsibility to assign these unique names across instances
   *
   * @param name - String name of service
   */
  public void setName(String name);

  // getName is same concepts.  Do I need to spell it out?

  /**
   * This method allows a single object parameter to be handed
   *   to a Service.  Because different services will handle
   *   different objects, this is kept intentionally vague.
   * Within this method, the <code>instanceof</code> Java
   *   construct should be used to ensure the correct type
   *   of object is passed in for a specific service.  If so
   *   the object should be saved to an appropriate member
   *   variable and the protected member variable/flag
   *   isReady should be set to true.  This will later be
   *   used by the <code>output()</code> and
   *   <code>execute()</code> methods to see if it is OK to
   *   output/execute the Service.  No exception should 
   *   ever result here; if the object is not the right
   *   type, just don't set the flag to true.  This could
   *   be called multiple times, so don't kill the process
   *   on incorrect parameters.
   *
   * @param ob - Object parameter for Service to use
   */
  public void init(Object ob);

  /**
   * This is the executable portion of a service.  For a scheduler,
   *   this would start the event timer; for a parser, it would begin
   *   parsing of, say, a ClassMap ;-)  You get the idea...
   * The first thing this method must always do is check the isReady
   *   flag (@see init()); if it is not true, the
   *   <code>ServiceException</code> should be thrown signifying things
   *   aren't ready to go.  If this particular service implementation
   *   does not need any object paramters, <i>still check the flag!</i>
   *   The >code>init()</code> method in that instance will simply set
   *   the flag to true.  This makes these portable and the
implementation
   *   very straightforward.  This should return true if everything 
   *   executed, else false.  Note that a false return signifies that
the
   *   <b>execution</b> failed, not that the intended result was not
   *   reached.  For example, a LoginService that finds out a user's
   *   credentials are incorrect would return true because it completed
   *   its task, even though the outcome wasn't what the user wanted.
   *
   * @return boolean - true if all OK, else false
   * @throws ServiceException - if not ready to execute, or errors
occur.
   */
  public boolean execute() throws ServiceException;

  /**
   * This method completes the lifecycle.  When a service is shut down,
   *   trashed, etc., this handles closing any open resources,
   *   doing basic due diligence.
   */
  public void destroy();

Implementation (Lower-level):

So you're thinking, what all this vague terminology worth?  What's the
big deal?  Let's look at how things happen now:

Connection con =
((ConnectionService)TurbineServiceManager.getInstance().getService("connection")).getConnection();

This sucks.  The problem is that as soon as you cast the Service to
ConnectionService, you have lost all flexibility.  You have lost the
ability to do really nice, "sneaky" things like proxying, XML-RPC,
advanced processing, etc., with other services.  You have lost the
ability to chain services (Which is OK, and not at all analagous to
servlet chaining, which is mucho bad-o).  What this proposal recommends:

Connection con =
(Connection)(TurbineServiceManager.getInstance().getService("connection").get());

Notice the shift in how casting occurs?  It occurs at the returned
object level, not at the service level.  In other words:

Service service =
TurbineServiceManager.getInstance().getService("connection");
Connection con = (Connection)service.get();

So why the big deal?  Why should you have to use less "accurate" method
names (get() vs. getConnection())?  Let's look at another example to
see:

Service service =
TurbineServiceManager.getInstance().getService("scheduler");
// Set up a new event and start the process of execution (timer)
service.init(myNewEvent);
service.execute();

No big deal, you still don't see, right?  But what if the scheduler is
distributed, and on  another machine?  Uh.. oh... but no big deal,
because the client doesn't request an implementation of a _sepcific_
service, they request an implementation that conforms to the generic
_service_contract_!!  This means that we can return to them an
XMLRPCService class, which implements Service, and give it the
information (in TurbineServiceManager) to know how to send the type of
XML-RPC that the Scheduler Engine, on another box understands.  So when
you call execute, you aren't _really_ invoking execute() on
SchedulerService, but rather on XMLRPCService, that constructs an
XML-RPC call, sends it across the wire, and invokes execute() on
SchedulerService somewhere ... all unknown to the client because he is
using a generic Service interface, not casting to a sepcific Service
implementation.

So this is beatiful for us ;-)  We have TurbineServiceManager read in a
services.properties file (yes, seperate, because it may be on machines
that TurbineResources.properties is not), and at service request
(getService(serviceName)), determine whether to return the actual class
for a given service, or the XMLRPCClass, or a ProxyClass, or an
SSLRequest class, or any number of intermediary services that add
advanced pre- and post- processing to service behavior.  All using our
nice simple little properties file ;-)

Examples:

Using SchedulerService across the wire via XML-RPC...
Getting a URL that is SSL protected with SSLService... (lame, but an
example still)
Changing a data store from a database to LDAP without the client
knowing... (no casting to JNDIService vs. ConnectionService!)
<scott-pay-attention-here>
Getting a DOM layer over a SAXparser service for handling class maps if
one is needed.
</scott-pay-attention-here>

So what do you all think?

-Brett


------------------------------------------------------------
To subscribe:        [EMAIL PROTECTED]
To unsubscribe:      [EMAIL PROTECTED]
Problems?:           [EMAIL PROTECTED]

Reply via email to