Toolbox Services API

$Id: services.xml,v 1.16 2012/08/01 06:52:52 hannes Exp $

Copyright © 2006 - 2012 Hannes Holtzhausen

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.


Table of Contents

1. Introduction
2. What's in a Service
2.1. Working with Services
2.2. Configuration
2.3. Dependencies
2.4. Interceptors
3. Developing Services
4. Why Services

1. Introduction

This document will describe the functionality and purpose of the Toolbox Services API, as it is implemented in the toolbox.services package. The general idea is to have an API and mechanism for configuring, assembling and accessing sets of services (Java Objects) within a single JVM.

2. What's in a Service

Services are simply POJO's that are managed by a ServiceRegistry instance. The ServiceRegistry ensures that the services are instantiated and configured as needed.

Services will, in most cases, require infrastructure resources and configuration settings. The Services API provides a facility to manage the configuration data required by your services. The Services API also provides interfaces and classes that enable you to develop infrastructure components for your services. In addition to all the infrastructure and configuration facilities provided, service implementations can also collaborate with each other, and other objects, via dependency injection.

Applications will be developed on top of the Services API. Application code will gain access to ServiceRegistry instances via the static ServiceRegistryFactory class.

2.1. Working with Services

The Services API aims to make coding, using the API, as simple as possible. Because all services are simple objects and the ServiceRegistry handles instantiation and configuration of the objects, there is no need for complex code to handle configuration parsing and object assembly.

       //First obtain a reference to a ServiceRegistry
       ServiceRegistry reg = ServiceRegistryFactory.getRegistry("myservices");

       //Now obtain the Service from the ServiceRegistry
       MyService svc = (MyService)reg.getService("MyServiceId",MyService.class); 
       //Perform Service related operations.
       svc.doStuff();
       

The internal machinery of the Services API ensures that ServiceRegistries and Services are created and configured timeously and safely. All that is left for the application developer is to make use of the created and configured Services. More detailed information on the service interfaces and classes are available in the API documetation.

2.2. Configuration

Detailed information on the configuration of the services API can be found in the Configuration Reference and ServiceRegistry DTD Reference.

2.2.1. ServiceRegistry

Services must be configured within a ServiceRegistry to be accessed and used successfully. The following is an example of a ServiceRegistry configuration:

<?xml version="1.0"?>
<ServiceRegistry name="myservices">
  <Properties>
    <Property name="default_home">/opt/applications/myservices</Property>
  </Properties>

  <Environments home="$[default_home]/etc">
    <Environment name="Env1" class="env.impl.classname">
      <Config relative="true">/env1_env.xml</Config>
    </Environment>
  </Environments>

  <Services home="$[default_home]/etc/services">
    <Service name="S1" env="Env1" singleton="false">
      <Config relative="true">/s1_service.xml</Config> 

      <Dependencies>
        <Dependency type="pojo" useServiceClassLoader="true">
          <Property name="class">HelloWorld</Property>
          <Property name="serviceMethod">setHelloWorld</Property>
        </Dependency>
      </Dependencies>

      <Interceptors>
        <Interceptor class="toolbox.services.LoggingInterceptor">
          <Property name="javaLogger">myservices</Property>
          <Property name="successLevel">INFO</Property>
          <Property name="exceptionLevel">WARNING</Property>
        </Interceptor>
      </Interceptors>
    </Service>
  </Services>
</ServiceRegistry>
          

2.2.2. ServiceRegistryFactory

The ServiceRegistryFactory must be configured with a Java properties file containing the names and configuration of all the supported ServiceRegistries. The properties file is configured using the Java System Property 'toolbox.services.ServiceRegistryFactory.config' and must contain properties in the following format.

            # <registry name>=<config file> 
            myservices=/opt/applications/myservices/etc/services.xml
          

2.2.3. Service

Service instances can be configured using free form XML documents. Services can access the configuration properties in one of two ways: 1) As XML properties or; 2) As normal Java properties.

This XML document:

<?xml version="1.0"?> <MyService> <config> <value1>one</value1> <value2>two</value2> </config> </MyService>

Would be translated into the following Java properties:

config.value1=one config.value2=two

A Service can also be configured using custom configuration objects that are injected into the Service using the dependency injection functionality of the ServiceRegistry.

2.3. Dependencies

In most cases the objects within an application have dependencies on other objects. For example: Objects that must interact with a database have a dependency on a DataSource object. Assembling all the objects within an application is a repetitive task.

The Services API defines a mechanism for injecting dependencies into service objects. The dependencies are configured in the service registry configuration file. The Services API currently supports three types of dependencies:

  • pojo - A Plain Old Java Object dependency

  • service - A dependency on another service.

  • factory - A dependency on an object that must be created using a custom ObjectFactory.

2.4. Interceptors

Interceptors provide a very convenient way to add functionality to service objects. An interceptor can intercept a method invocation on a service object and perform custom operations before and after the service invocation. This functionality can be very useful for aspects that are required by all objects in an application. For example: Debug logging can be implemented as an interceptor rather than adding debug code to every object within an application individually.

Interceptors are configured in the service registry configuration file for each service. The Services API provides a single interceptor at this point; a logging interceptor that logs each method entry and exit. The interceptor is implemented in the toolbox.services.LoggingInterceptor class.

NOTE: Services must implement a defined interface for the interceptor functionality to work.

3. Developing Services

Service implementations should be stateless components with well defined interfaces that describe the functions they can perform. Making Service implementations stateless makes them highly scalable and thread safe, which also makes them ideal candidates for forming the building blocks of enterprise applications. There are a number of forms a Service can take:

  • A simple Plain Old Java Object.

  • An implementation of the toolbox.services.Service interface.

  • A subclass of one the API provided convenience base classes.

The Services API provides the following Service implementations as a convenience:
  • toolbox.services.BaseService that provides methods for accessing Service configuration.

  • toolbox.services.dao.DaoService which is derived from the BaseService and additionally provides methods for interacting with Database systems.

  • toolbox.services.ldap.LDAPService which is derived from the BaseService and additionally provides methods for interacting with LDAP directories.

  • toolbox.services.mail.MailService which is derived from the BaseService and additionally provides methods for interacting with Internet Mail Systems.

  • toolbox.services.jms.JmsService which is derived from the BaseService and additionally provides methods for interacting with JMS Brokers.

4. Why Services

Having well defined Services can encourage reuse and encapsulation. Consumers of Services need not be concerned with how the Service achieves it's functionality. It would be possible to expose common functionalities as a set of Services that all applications can use to achieve certain common goals. Services can also collaborate with each other to deliver functionality.

Imagine a scenario where applications must be delivered in an environment where access to Database, LDAP and Messaging systems are required. In most cases each application would address each of these systems individually. Instead, it could be beneficial to develop a set of Services that can be reused by all applications to interact with these systems through a well defined set of interfaces addressing the business requirement rather than the technical complexities in accessing these systems.