Difference between revisions of "Refining the implementation"

From GCube System
Jump to: navigation, search
(New page: Now that we have exemplified the tasks which comprise the development cycle of a gCube service, we can progressively inject more structure and functionality into the implementation of <co...)
 
Line 1: Line 1:
 
 
Now that we have exemplified the tasks which comprise the development cycle of a gCube service, we can progressively inject more structure and functionality into the implementation of <code>SampleService</code>. We do so incrementally, of course, tackling first a number of issues which are common to all service implementations and yet do not imply big changes to the internal structure of our <code>SampleService</code>.
 
Now that we have exemplified the tasks which comprise the development cycle of a gCube service, we can progressively inject more structure and functionality into the implementation of <code>SampleService</code>. We do so incrementally, of course, tackling first a number of issues which are common to all service implementations and yet do not imply big changes to the internal structure of our <code>SampleService</code>.
 +
 +
== A Faultful PortType ==
 +
 +
Things not always go according to plans. Unpredictable circumstances at runtime may cause deviations form the control flow we expect within our service implementation. Some of these deviations are the result of genuine programming errors which we hope to capture as early as possible during testing. Other are more ''exceptional'' and we cannot blame our code for them, rather a malformed client request or simply a local or remote environment which does not satisfy normal expectations. When this happens and there is a client waiting for a response, all we can do is return a ''fault'' which explains the problem and ''may'' induce a client to react in some useful way.
 +
 +
=== Faults & Exceptions ===
 +
 +
Simply put, a fault is an exception which escapes the service implementation. More precisely, it wraps such an exception in a way which is suitable for it to travel over the network back to clients. By default, gCore like all SOAP engines wraps exceptions which escape the service boundary in a generic ''SOAP fault''. Admittedly, there is no much a client can do with a generic fault, beside desisting gracefully from what it was trying to achieve by calling our service. However, we can define our own fault types as specialisations of the generic one.
 +
 +
gCF comes equipped with three pre-defined fault specialisations. These capture prototypical fault causes, still very generic but potentially informative enough for intelligent clients to react more usefully than by gracefully giving up:
 +
 +
* '''GCUBERetrySameFault''', which basically means: ''couldn't do it now, but try again later I might make it then''.
 +
* '''GCUBERetryEquivalentFault''' which basically means: ''couldn't do it myself, but other Running Instances they might''.
 +
* '''GCUBEUnrecoverableFault''', which basically means: ''couldn't do it now, won't do it later, and there is no point asking someone else. Just forget about it''.
 +
 +
Clearly, a client presented with either one of the first two faults can try to recover from the setback, while a client presented with last type of fault knows that there is no point to insist, neither later nor elsewhere. Of course, when or which of these faults should be returned depends on the precise semantics of our service.
 +
 +
So, how about our <code>SampleService</code>? Well, at the moment our <code>Stateless</code> port-type can hardly go wrong, because if its single <code>about()</code> method completes successfully once, it is very likely that it will complete successfully all the time. At the very best, we could play safe and declare that we might return a <code>GCUBEUnrecoverableFault</code> in the assumption that if something goes wrong than there aren't many chances it will ever go right. In fact, a modicum of testing could immediately eliminate this possibility: either the service does not activate properly within the gHN or, if it does, it will behave correctly.
 +
 +
So, to illustrate fully the case of  'faultful' port-types, we are going to introduce artificially the possibility of a wider range of faults into the interface and implementation of the <code>about()</code> method.
 +
 +
Before we do that, however, a final observation. Faults are 'heavyweight' exceptions and ought to be created and raised and caught only at the ‘edge’ of a service implementations, i.e. in the methods directly invoked on the service and in those which directly call the methods of other services. Accordingly, gCF mirrors gCube faults with equivalent but 'lightweight' exception to raise ''inside'' the service implementations:
 +
 +
* '''GCUBERetrySameException''' ~ GCUBERetrySameFault
 +
* '''GCUBERetryEquivalentException''' ~ GCUBERetryEquivalentFault
 +
* '''GCUBEUnrecoverableException''' ~ GCUBEUnrecoverableFault
 +
 +
gCube exceptions and gCube faults are freely convertible. Typically, a gCube exception raised within the service should be converted into a gCube fault before it escapes the service. Similary, a gCube fault received from interacting with another service ought to be converted into a gCube exception before it perculates up backwords the call stack.
 +
 +
=== A Faultful Interface ===
 +
 +
First of all, let us declare our intentions in the port-type interface: 
 +
 +
<pre>
 +
<?xml version="1.0" encoding="UTF-8"?>
 +
<definitions name="Stateless" targetNamespace="http://acme.org/sample" xmlns:tns="http://acme.org/sample"
 +
  xmlns="http://schemas.xmlsoap.org/wsdl/"  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 +
        xmlns:corefaults="http://gcube-system.org/namespaces/common/core/faults">
 +
   
 +
    <import namespace="http://gcube-system.org/namespaces/common/core/faults" location="../gcube/common/core/faults/GCUBEFaults.wsdl"/>
 +
   
 +
<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"/>
 +
<fault name="unrecoverablefault" message="corefaults:GCUBEFaultMessage"/>
 +
<fault name="retrysamefault" message="corefaults:GCUBERetrySameFaultMessage"/>
 +
<fault name="retryequivalentfault" message="corefaults:GCUBERetryEquivalentFaultMessage" />
 +
        </operation>
 +
 +
</portType>
 +
 +
</definitions>
 +
</pre>
 +
 +
 +
The thing to notice here is that we import pre-defined fault messages from a standard gCore WSDL:
 +
 +
<pre><import namespace="http://gcube-system.org/namespaces/common/core/faults" location="../gcube/common/core/faults/GCUBEFaults.wsdl"/></pre>
 +
 +
and then use them in the declartion of the <code>about()</code> operation:
 +
 +
<pre>
 +
<operation name="about">
 +
<input message="tns:aboutInputMessage"/>
 +
<output message="tns:aboutOutputMessage"/>
 +
<fault name="unrecoverablefault" message="corefaults:GCUBEFaultMessage"/>
 +
<fault name="retrysamefault" message="corefaults:GCUBERetrySameFaultMessage"/>
 +
<fault name="retryequivalentfault" message="corefaults:GCUBERetryEquivalentFaultMessage" />
 +
        </operation>
 +
</pre>
 +
 +
'''Note:''' The <code>location</code> makes sense with respect to the temporary <code>build</code> [[#Building & Deploying Stubs|used by our buildfile]] and will succeed because the buildfile will copy all the the pre-defined gCore WSDL in that folder at the point of building. You do not need to worry about it, just make sure you are copying this correctly.
 +
 +
That's all for the interface of <code>Stateless</code>. This is the right time to [[#Building & Deploying Stubs|rebuild the stubs]] though.
 +
 +
=== A Faultful Implementation ===
 +
 +
Now the implementation of the port-type and the simulation of failure we promised:
 +
 +
<pre>
 +
<pre>
 +
package org.acme.sample.stateless;
 +
import ...
 +
 +
public class Stateless extends GCUBEStartupPortType {
 +
 +
/** {@inheritDoc} */
 +
protected GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();}
 +
 +
protected static void simulateRequestProcessing() throws Exception {
 +
if (Math.random()<.40) { //simulating an error
 +
switch ((int) (Math.random()*4+1)) {//randomly choosing error type
 +
case 1 : throw new GCUBEUnrecoverableException("just give up");
 +
case 2 : throw new GCUBERetryEquivalentException("maybe someone else?");
 +
case 3: throw new GCUBERetrySameException("maybe in a bit?");
 +
case 4: throw new Exception("some problem with unclear semantics");
 +
}}}
 +
 +
public String about(String name) throws GCUBEFault,GCUBERetryEquivalentFault,GCUBERetrySameFault,GCUBEUnrecoverableFault {
 +
 +
              try {
 +
      simulateRequestProcessing();
 +
                      return ("Hello " + name + ", you have invoked service ")+this.getServiceContext().getName() +
 +
                                                            " ("+this.getServiceContext().getServiceClass() + ")";
 +
}
 +
catch(GCUBEException e) {throw e.toFault();}
 +
catch(Exception e) {sctx.getDefaultException("Problem of unknown semantics", e).toFault();}
 +
}
 +
}
 +
</pre>
 +
 +
A few things to comment upon here:
 +
 +
* the method <code>simulatRequestProcessing()</code> emulates an arbitrarily complex process which may fail in more than one different way. In a real scenario, the process would be likely distributed among many implementation components and would probably reach very 'deeply' within the stack trace. Here we use a 'flat' method in which we throw a die and simulate a failure in 40% of the outcomes. When we do so, we throw another 4-faced die to simulate the different types of failures which may occur in a gCube Service: the three types of gCube exceptions gCube plus a fourth type of generic failure. After all, not all methods which observe failures may know the semantics of gCube exceptions.

Revision as of 18:30, 15 April 2008

Now that we have exemplified the tasks which comprise the development cycle of a gCube service, we can progressively inject more structure and functionality into the implementation of SampleService. We do so incrementally, of course, tackling first a number of issues which are common to all service implementations and yet do not imply big changes to the internal structure of our SampleService.

A Faultful PortType

Things not always go according to plans. Unpredictable circumstances at runtime may cause deviations form the control flow we expect within our service implementation. Some of these deviations are the result of genuine programming errors which we hope to capture as early as possible during testing. Other are more exceptional and we cannot blame our code for them, rather a malformed client request or simply a local or remote environment which does not satisfy normal expectations. When this happens and there is a client waiting for a response, all we can do is return a fault which explains the problem and may induce a client to react in some useful way.

Faults & Exceptions

Simply put, a fault is an exception which escapes the service implementation. More precisely, it wraps such an exception in a way which is suitable for it to travel over the network back to clients. By default, gCore like all SOAP engines wraps exceptions which escape the service boundary in a generic SOAP fault. Admittedly, there is no much a client can do with a generic fault, beside desisting gracefully from what it was trying to achieve by calling our service. However, we can define our own fault types as specialisations of the generic one.

gCF comes equipped with three pre-defined fault specialisations. These capture prototypical fault causes, still very generic but potentially informative enough for intelligent clients to react more usefully than by gracefully giving up:

  • GCUBERetrySameFault, which basically means: couldn't do it now, but try again later I might make it then.
  • GCUBERetryEquivalentFault which basically means: couldn't do it myself, but other Running Instances they might.
  • GCUBEUnrecoverableFault, which basically means: couldn't do it now, won't do it later, and there is no point asking someone else. Just forget about it.

Clearly, a client presented with either one of the first two faults can try to recover from the setback, while a client presented with last type of fault knows that there is no point to insist, neither later nor elsewhere. Of course, when or which of these faults should be returned depends on the precise semantics of our service.

So, how about our SampleService? Well, at the moment our Stateless port-type can hardly go wrong, because if its single about() method completes successfully once, it is very likely that it will complete successfully all the time. At the very best, we could play safe and declare that we might return a GCUBEUnrecoverableFault in the assumption that if something goes wrong than there aren't many chances it will ever go right. In fact, a modicum of testing could immediately eliminate this possibility: either the service does not activate properly within the gHN or, if it does, it will behave correctly.

So, to illustrate fully the case of 'faultful' port-types, we are going to introduce artificially the possibility of a wider range of faults into the interface and implementation of the about() method.

Before we do that, however, a final observation. Faults are 'heavyweight' exceptions and ought to be created and raised and caught only at the ‘edge’ of a service implementations, i.e. in the methods directly invoked on the service and in those which directly call the methods of other services. Accordingly, gCF mirrors gCube faults with equivalent but 'lightweight' exception to raise inside the service implementations:

  • GCUBERetrySameException ~ GCUBERetrySameFault
  • GCUBERetryEquivalentException ~ GCUBERetryEquivalentFault
  • GCUBEUnrecoverableException ~ GCUBEUnrecoverableFault

gCube exceptions and gCube faults are freely convertible. Typically, a gCube exception raised within the service should be converted into a gCube fault before it escapes the service. Similary, a gCube fault received from interacting with another service ought to be converted into a gCube exception before it perculates up backwords the call stack.

A Faultful Interface

First of all, let us declare our intentions in the port-type interface:

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="Stateless" targetNamespace="http://acme.org/sample" xmlns:tns="http://acme.org/sample"
  	xmlns="http://schemas.xmlsoap.org/wsdl/"  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:corefaults="http://gcube-system.org/namespaces/common/core/faults">
    
    <import namespace="http://gcube-system.org/namespaces/common/core/faults" location="../gcube/common/core/faults/GCUBEFaults.wsdl"/>
    
 	<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"/>
			<fault name="unrecoverablefault" message="corefaults:GCUBEFaultMessage"/>
			<fault name="retrysamefault" message="corefaults:GCUBERetrySameFaultMessage"/>
			<fault name="retryequivalentfault" message="corefaults:GCUBERetryEquivalentFaultMessage" />
        	</operation>
	
	</portType>

</definitions>


The thing to notice here is that we import pre-defined fault messages from a standard gCore WSDL:

<import namespace="http://gcube-system.org/namespaces/common/core/faults" location="../gcube/common/core/faults/GCUBEFaults.wsdl"/>

and then use them in the declartion of the about() operation:

	<operation name="about">
			<input message="tns:aboutInputMessage"/>
			<output message="tns:aboutOutputMessage"/>
			<fault name="unrecoverablefault" message="corefaults:GCUBEFaultMessage"/>
			<fault name="retrysamefault" message="corefaults:GCUBERetrySameFaultMessage"/>
			<fault name="retryequivalentfault" message="corefaults:GCUBERetryEquivalentFaultMessage" />
        	</operation>

Note: The location makes sense with respect to the temporary build used by our buildfile and will succeed because the buildfile will copy all the the pre-defined gCore WSDL in that folder at the point of building. You do not need to worry about it, just make sure you are copying this correctly.

That's all for the interface of Stateless. This is the right time to rebuild the stubs though.

A Faultful Implementation

Now the implementation of the port-type and the simulation of failure we promised:

<pre>
package org.acme.sample.stateless;
import ...

public class Stateless extends GCUBEStartupPortType {

	/** {@inheritDoc} */
	protected GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();}

	protected static void simulateRequestProcessing() throws Exception {
		if (Math.random()<.40) { //simulating an error
			switch ((int) (Math.random()*4+1)) {//randomly choosing error type
				case 1 : throw new GCUBEUnrecoverableException("just give up");
				case 2 : throw new GCUBERetryEquivalentException("maybe someone else?");
				case 3: throw new GCUBERetrySameException("maybe in a bit?");
				case 4: throw new Exception("some problem with unclear semantics");
	}}}
	
	public String about(String name) throws GCUBEFault,GCUBERetryEquivalentFault,GCUBERetrySameFault,GCUBEUnrecoverableFault {		

              try {
		      simulateRequestProcessing();
                      return ("Hello " + name + ", you have invoked service ")+this.getServiceContext().getName() + 
                                                             " ("+this.getServiceContext().getServiceClass() + ")";
		}
		catch(GCUBEException e) {throw e.toFault();}
		catch(Exception e) {sctx.getDefaultException("Problem of unknown semantics", e).toFault();}
	}
}

A few things to comment upon here:

  • the method simulatRequestProcessing() emulates an arbitrarily complex process which may fail in more than one different way. In a real scenario, the process would be likely distributed among many implementation components and would probably reach very 'deeply' within the stack trace. Here we use a 'flat' method in which we throw a die and simulate a failure in 40% of the outcomes. When we do so, we throw another 4-faced die to simulate the different types of failures which may occur in a gCube Service: the three types of gCube exceptions gCube plus a fourth type of generic failure. After all, not all methods which observe failures may know the semantics of gCube exceptions.