The Development Cycle

From GCube System
Revision as of 03:01, 12 April 2008 by Fabio.simeoni (Talk | contribs) (A PortType Interface)

Jump to: navigation, search

Let us go through a quick tour of development with SampleService.

My First Profile

A good starting point with service development is its configuration. And a good starting point when configuring a service is the definition of its profile. The role and structure of gCube service profiles is described in detail elsewhere. Here we just show one of the smallest and yet perfectly well-formed profile a service may have:

<?xml version="1.0" encoding="UTF-8"?>
<Resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<ID></ID>
	<Type>Service</Type>
	<Profile>
		<Description>A very simple gCube Service</Description>
		<Class>Samples</Class>
		<Name>SampleService</Name>
		<Version>1.0</Version>
		<Packages>
			<Main>
				<Name>Main</Name>
				<Version>1.0</Version>
				<Dependencies>
					<Dependency>
						<Service>
							<Class>Samples</Class>
							<Name>SampleService</Name>
						</Service>
						<Package>Stubs</Package>
						<Version>1.0</Version>
						<Scope level="GHN"/>
						<Optional>false</Optional>
					</Dependency>
				</Dependencies>
				<GARArchive>org.acme.sample.gar</GARArchive>
				<PortType>
					<Name>acme/sample/stateless</Name>
					<WSDL>...</WSDL>
				</PortType>
			</Main>
			<Software>
				<Description>Describes port-type stubs</Description>
				<Name>Stubs</Name>
				<Version>1.0</Version>
				<Files><File>org.acme.sample.stubs.jar</File></Files>
			</Software>
		</Packages>
	</Profile>
</Resource>

By necessity, this profile reveals things yet to come. For the time being, be happy with a conceptual summary:

  • SampleService is a a particular type of gCube Resource, one of type Service. Like all gCube Resources has an ID, which it receives when it is formally registered with the infrastructure. Until then, SampleService does not have a proper identifier. No worries though, a temporary one will be generated by gCF at runtime.
  • The profile of SampleService specifies its Name and specifies its membership to a Class of functionally related gCube services. Classes are not pre-defined, and here we settle on Samples. A free-form Description and a Version complement the preliminary description of SampleService.
  • SampleService is physically comprised of Packages. A Main package identifies the main artefact to emerge from the build of the service, a GARArchive, and provides information about the service port-types, including their WSDLs' (here omitted for mercy). The profile unveils our initial plans for a single stateless port-type, which we name by identifying the relative part of the URI at which it will be accessible on the gHN (more on this soon).
  • Another Software component identifies the secondary build artefact of the service, its own stubs. The Main package depends always upon its stubs and this dependency must be explicitated.

Note: If the notion of build artefact is not obvious to you, bide your time for a little longer or jump ahead.

Save the profile in a file called profile.xml and place it in the etc folder of the service implementation:

|-SampleService
|--etc
|---profile.xml
|--src
|--schema
|
|-Dependencies
|--SampleService

A Tiny JNDI

Next we move to the configuration of the code itself, for which we use JNDI technology. Again, the details and granularity of JNDI configuration are discussed elsewhere. Here we simply notice that, while the JNDI configuration might contain any information which the code needs to consult at runtime, the gCF raises precise requirements against the existence and shape of some of that information. In particular, the configuration should at least contain the following information:

<?xml version="1.0" encoding="UTF-8"?>
<jndiConfig xmlns="http://wsrf.globus.org/jndi/config">

	<service name="acme/sample">
		<environment  name="configDir" value="@config.dir@"  type="java.lang.String" override="false" />
	</service>

</jndiConfig>

Essentially, the configuration must have a service section dedicated to information conceptually associated with the entire implementation, rather than more specific components yet to come. Furthermore, the service section must:

  • specify the name of the service. This is not particularly constrained but must distinguish SampleService from any other which may be deployed on the same gHN. As we already do with Java and URI namespaces, we can approach uniqueness through a hierarchical structure, here of /-separated strings. In our case, the name acme/sample will suffice as a unique identifier.
  • include an environment element which specifies the absolute path to the configuration folder of SampleService. Contrary to expectations, this is not our etc folder under the service location. It is in fact another configuration folder allocated to SampleService at the point of service deployment. This will become clearer later. For the time being, we use the wildcard @config.dir@ to abstract over the precise location of this yet-to-be folder, and leave to gCore the task of resolving it at the point of deployment.

Note: The wildcard @config.dir is surely convenient at this stage. It is in fact necessary for any real gCube service. This is because the service may be dynamically deployed on any gHN and the absolute path of the configuration folder depends on the file system of that gHN. During service development, that location is an absolute unknown.

So far, there is little in our configuration which is specific to SampleService. Indeed, the JNDI above can be considered as boilerplate for any gCube service. We will see shortly how to inject more information into the JNDI configuration, but until that moment this minimal configuration will suffice.

Save the configuration in a file called deploy-jndi-config.xml and place it in the etc folder of the service implementation:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|--src
|--schema
|
|-Dependencies
|--SampleService


A PortType Interface

Enough configuration for now. Let us move to the definition of the interface for the first port-type we plan to add to SampleService. The general expectation is that all your interfaces will have this high-level structure:

<?xml version="1.0" encoding="UTF-8"?>
<definitions 
               xmlns:tns="..YOUR NAMESPACE.." targetNamespace="...YOUR NAMESPACE..." name="..SOME_NAME... "
               xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >

	<types>
	   <xsd:schema targetNamespace=".....">
			<!-- REQUEST & RESPONSE TYPE DEFINITIONS -->
	    </xsd:schema>
	</types>
	
	<!-- MESSAGE DEFINITIONS  -->

	<portType name="...PORT_TYPE NAME...portType">
		<!--OPERATIONS DEFINITIONS -->
	</portType>

</definitions>

All pretty standard WSDL, of course. And yet notice what follows:

  • we dedicate the WSDL to a single port-type (even though multiple port-types are admitted by the WSDL specs). This simplifies our building configuration and suggests than any form of sharing across port-types (type definition, messages, faults, etc.) be achieved through imports/includes.
  • the name of the WSDL definitions does not play much of a role during development. Use anything that looks appropriate. Given that there is a one-to-one correspondence between WSDLs and port-types use, any informal name you may wish to give to the port-type.
  • the name of the portType also refers to the port-type, though this has a very important role during the build of the service. In particular, it must end in portType.

The WSDL of our first port-type illustrate these conventions:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns:tns="http://acme.org/sample" name="Stateless" targetNamespace="http://acme.org/sample"
    xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"  >

	<types>
		<xsd:schema targetNamespace="http://acme.org/sample">

			<xsd:element name="about" type="xsd:string"/>
			<xsd:element name="aboutResponse" type="xsd:string"/>

		</xsd:schema>
	</types>

	<message name="aboutInputMessage">
		<part name="request" element="tns:about"/>
	</message>
	<message name="aboutOutputMessage">
		<part name="response" element="tns:aboutResponse"/>
	</message>

	<portType name="StatelessPortType">
		    
             <operation name="about">
			<input message="tns:aboutInputMessage"/>
			<output message="tns:aboutOutputMessage"/>			
		</operation>

	</portType>
</definitions>

As you can see:

  • we have used the namespace http://acme.org/sample for our port-type.
  • we have settled on the name Stateless for it, and used it twice. The first time to name the whole interface definition and a second time in the name StatelessPortType of the portType definition.
  • we have defined a single operation about for Stateless, which takes and returns a string. The idea is to expect the name of the caller as input and produce some information about the port-type as output.

Hard to imagine a simpler port-type, really. Save the interface in a file Stateless" under the schema folder of the service, as agreed:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|
|--src
|
|--schema
|---Stateless.wsdl
|
|-Dependencies
|--SampleService

A Simple Implementation

So far, the development process has been dominated by simple but numerous conventions upon the use of not necessarily simple but equally numerous formats. Frankly, all very unexciting and error-prone. Welcome (or welcome back) to the grim reality of web service development. gCF cannot take much of this pain away, unfortunately. In fact, using gCF has only added to those conventions without showing no clear benefit yet. Delving into the implementation of SampleService hopefully will give you the first glimpse of rewards to come.

A natural place to start the implementation is right at its centre, with a service context. Among other things, a service context manages configuration, scoping, security, and lifetime on behalf of the rest of the code. In gCF, you can have your own wih a few lines of code:

package org.acme.sample;
import ...

public class ServiceContext extends GCUBEServiceContext {
		
	/** {@inheritDoc} */
	protected String getJNDIName() {
                  return "acme/sample";
        }
	
}

As you can see, it is enough to:

  • inherit your service context from GCUBEServiceContext.
  • let the inherited code know how to act on your behalf by implementing getJNDIName() and returning the name of the JNDI section dedicated to the service ( remember?).

Note: This is a first instance of the template pattern: you complete the interface by exposing service-specific information which the inherited code uses to customise the work it carries out for you. It is simple, and it's used a lot within gCF.

The rest of the implementation could use the ServiceContext by instantiating it. For efficiency and exchange of data, however, it is more appealing to force the use of a single ServiceContext throughout the implementation. A simple way of doing this is as follows:

/** Single context instance, created eagerly */
private static ServiceContext singleton = new ServiceContext();
		
/** Prevents accidental creation of more instances */
private ServiceContext(){};

/** Returns cached instance */
public static ServiceContext getContext() {return singleton;}
  • a single context object is created the first time the class is loaded (singleton field declaration).
  • no other such object can be created by clients (private constructor).
  • the object is available on demand (getContext()).

Note: This is an instance of the singleton pattern and gCF cannot 'force' it as a contract upon your code without taking away too much of its simplicity. It just recommends it.

We will go back to the service context in the rest of the Primer, as its feature will be needed. Even If we didn't, it would already carry out important work during service startup, as you will see later on. More information on the role and features of a service context in gCF is here.


Next, the implementation of the port-type interface. This is also straightforward:

package org.acme.sample.stateless;
import ...

public class Stateless extends GCUBEStartupPortType {

	/** {@inheritDoc} */
	protected GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();}
	
	public String about(String name) {		
		return ("Hello " + name + ", you have invoked service ")+this.getServiceContext().getName() + " ("+this.getServiceContext().getServiceClass() + ")";				
	}
}

but do please note:

  • the implementation extends GCUBEStartupPortType, which initialises the service context when the port-type is acivated in turn at container startup. One service port-type must always assume the role of 'fire-starter', in case you have many. We will refer to it as the startup port-type from now onwards.
  • the name and class of the service as defined in the service profile can be obtained from the service context, as can any piece of information in the profile if it proves useful for dynamic introspection.

By now the service location should look as follows:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|
|--schema
|---Stateless.wsdl
|
|-Dependencies
|--SampleService

Building & Deploying

Logging it

A Test Client