wiki:WritingServiceApis

Overview

Service:
A service is the basic building block of a runtime instance.

A service will be deployed based on it's mandatory service descriptor (service.xml). Within the descriptor initialization parameters can be specified, as well as dependencies to other services (that have to be deployed first).

All classes located in the service's WEB-INF/classes folder and all jar-files located in the service's WEB-INF/lib folder are added to the runtime instance's classpath at deployment time.

A service can offer zero or more service APIs which can be obtained by other services via the runtime framework.

When a service does not offer a API at all, it is called a class-providing service. The service WebServiceSupport for example is such a class-providing service.

Service API:
Interfaces offered by a service to be used by other services and clients (also external non-java clients). A service can offer zero or more APIs.

NOTE: don't mix up service APIs with java interfaces.

A service API declaration consists of three parts:

  • the interface class. Methods the service API offers. A plain old java interface (POJI)
  • the implementation class implementing the interface class
  • the optional remote-client class: this class connects the service API to a service/client located outside the VM. By default a XML-RPC client is generated automatically in case a service requests an API from a service running on another VM.

Service Roles

When handling larger systems services turn out to have different roles. Some services become a kind of meta-services whose purpose is not to offer programmatic interfaces but for example to better decouple services or to help sharing classes between different runtime instances running on different VMs.

Currently, service roles can be categorized as follows:

  • standard service
  • pure class-providing services: Their only purpose is to load java classes into a runtime instance.
  • interface(-providing) services: Load a bunch of java classes (typically POJIs and dependent classes). Allow to share these classes between VMs (runtime instances). Serves as join point between a service client and the service implementing the functionality. So to say, they are interfaces on service-level point of view. The runtime framework provides methods to obtain implementations for an interface service.
  • implementation(-providing) services: The service-level counterparts for java objects implementing java interfaces. Decomposing services into interface and implementation services is useful when running the services in a distributed, multi-VM scenario.
  • bus-defining services: Special type of an interface-service. Zero or more consumers (implementation services) are allowed to exist in the runtime instance. Calling methods of bus-interfaces invokes the corresponding methods of all implementation services (like sending an event but integrated into Mymory's method-oriented communication mechanism). Offered methods are not allowed to return any value.

The Service Descriptor

Location: <servicename>/WEB-INF/service.xml

The service descriptor contains a bunch of information used by the servicia framework to initialize and deploy the service and all the APIs it offers. The descriptor is an XML file. An example descriptor might look like this one:

<service type="XML-RPC">
    <description>Brief description of the service</description>

    <param name="aParameter" value="aValue" />    <!-- parameters can be passed to the service. -->
    <param name="anotherParameter" value="y"/>

	
    <depends-on name="SomeOtherService"/>         <!-- dependencies to other services can be stated. 
                                                       It is guaranteed that this service will be deployed after "SomeOtherService". -->
    <depends-on name="A" strong="true"/>
	                                          <!-- sometimes it's necessary that a service is deployed after a service "A" 
                                                      but before all services depending on service "A"-->


    <!-- Declaration of all offered APIs (zero or more)
         Recommendation for implementing and naming:
           * Implement each API in a separate class
           * Don't add 'Impl' to the implementation class name unless you are not interested in JMX support.
           * Name your API interface class <implementaion-name>Api
           * Name you JMX interface <implementation-name>MBean  
	-->	
	<api name="std-api">
		<interface-class>de.dfki.km.example.service.using_events.UsingEventsApi</interface-class>
		<implementation-class>de.dfki.km.example.service.using_events.UsingEvents</implementation-class>
	</api>

	<api name="another-api">
             <interface-class>de.dfki.km.workspace.examples.ExampleApi</interface-class>
             <implementation-class>de.dfki.km.workspace.examples.Example</implementation-class>
             <remote-client-class>de.dfki.km.workspace.examples.ExampleRemoteClient</remote-client-class>
	</api>
</service>

Init-parameters

Parameters can be passed to the service through the param element. These parameters are accessible via the ServiceContext object. See trunk/Software/ExampleService/src/service/de/dfki/km/example/service/access_servicecontext/AccessingServiceContext.java for an example.

Parameters are simple string key-value pairs.

Dependencies to other services

Sometime it is necessary to have one service deployed before another service is. For example, when a service offers APis to access a storage back end it is desirable to start this service before all other services using the backend.

Stating a depends-on in the service.xml of service StorageClientService

    ...
    <depends-on name="StorageService" />
    ...

will move StorageService ahead of StorageClientService in the startup sequence. Dependencies are transitive, that is, given a Service A depending on Service B and Service B depending on Service C leads to the conclusion that Service A also depends on Service C (implicitly). This might lead to circular dependency structures, which are not allowed in Servicia.

Strong dependencies

A second type of dependency arises when services take the interface/implementation roles (see service roles for details). Here a typical constellation is:

    interface-service   <----------+
      |                  <-----+    ClientService_1
      |                        ClientService_2
      implementation-service

We have an interface-service providing the interface classes (because the clients should not be bothered with all the dependencies of the implementation-service, the interface service is moved into an own service/project). ClientServices refer to the interface-service and are unaware (intentionally) of the implementation-service. In this case the implementation-service has a stronger dependency to the interface-service than the clients have to the interface-service, that is the implementation has to be deployed before the clients are.

Syntactically this is expressed in the implementation-service's service.xml as:

     <depends-on name="interface-service" string="true" />

This lead to a startup-sequence :

  • interface-service
  • implementation-service
  • ClientService_1
  • ClientService_2

Service APIs

Each api element has a name and it consists of three sub-elements:

  • interface-class: the java interface (POJI). This is the set of methods the service makes available to clients.
  • implementation-class: the implementation of the interface-class
  • remote-client-class (optional): when the service is invoked remotely this proxy will be used to pass method parameters to the service and return results to the client. It has to implement the interface-class. When omitted Servicia uses a generated proxy using XML-RPC to execute the remote calls http://delight.opendfki.de

Writing a Service API

Writing a service interface/implementation can be as easy as this:

package demo;
public interface ServiceApi
{
    public void sayHello();
}

package demo;
public class Service implements ServiceApi
{
    public void sayHello() { System.out.println( "hello" );  }
}

Then the service api desciptor would look like this:

<api name="std-api">
    <interface-class>demo.ServiceApi</interface-class>
    <implementation-class>demo.Service</implementation-class>
</api>

The service life-cycle

Additionally a service api implementation can support a life-cycle:

package demo;
import de.dfki.km.workspace.backbone.service.*;
public class Service implements ServiceApi, LifeCycle
{
    public void init( ServiceContext c ) throws Exception { /* called at service startup. */}
    public void destroy()                         { /* called at service shutdown. */ }
    public void sayHello() { System.out.println( "hello" );  }
}

For convenience an abstract service implementation is available, offering a bunch of useful methods (logging, accessing files, ...). Simply extend AbstractServiceImplementation.

public class Service 
                extends AnstractServiceImplementation
                implements ServiceApi
{
   ...
}

JMX

Some or all of the service methods can also be made callable through Java's JMX framework. Thus, it is possible to open up the JConsole tool, connect to a running runtime instance and call service methods.

To enable this feature an extra interface is needed (correctly spelled and in the same java package as the service implementation):

public interface ServiceMBean                /* <ImplementationClassname>MBean */
{
    public void methodAccessbileViaJmx();
}
public interface ServiceApi extends ServiceMBean { }

public class Service 
                implements ServiceApi
{
...
}

Accessing Service APIs

Now that we created some service APIs it's time to use them.

Using the Backbone

Usually services are called by other services from within the Servicia framework. The framework provides a core class to support the developer. This class is called Backbone. With the help of the Backbone services needed services can be obtained and method calls or messages can be sent to these services.

The Backbone offers the following communication options:

  • one-to-one, method-invocation

<I> I accessApiForClass( Class<? extends I> cls )
<I> I lazyAccessApiForClass( Class<? extends I> cls )

  • one-to-many, using a so called message-bus

<I> I publishTo( Class<? extends I> apiClass )

  • one-to-many, event-based, publish/subscribe

void raiseEvent( Event e )

Service invocations through XML-RPC

External components like Javascipts running in a browser also are able to call service API methods. Therefor the Servicia framework installs a XML-RPC request handler at http://<host>:<post>/RPC2 where port is the port of the runtime instance. XML-RPC requests are dispatched to the corresponding service APIs according to the XML-RPC method names, e.g., a request to method serviceMethod() of API api offered by service ServiceA can be made by using the XML-RPC handler id SerivceA_api and the XML-RPC method name serviceMethod.

Calling service methods using JConsole

When a service API definies a valid MBean interface, the methods of this MBean interface is accessible through the JConsole tool shipped with Java's JDK.

Simply connect to the running runtime instance and select the MBeans tab. Under the tree-entry workspace-service each enabled service API is listed and methods can be invoked by simply clicking on the method-buttons. NOTE: only some of the standard types and some container classes like Collections are supported. For more information see http://java.sun.com/javase/6/docs/technotes/guides/management/jconsole.html.

JConsole Screenshot

Last modified 10 years ago Last modified on 05/16/08 16:31:32

Attachments (1)

Download all attachments as: .zip