IGUANA
Interactive Graphics for User ANAlysis - (Version 6.13.0.g4.81)
Guide > Developer > How to > Create a Service
 

Valid XHTML 1.0 Transitional
Valid CSS!
Browser compatibility
tested on:
Opera/9.10
Firefox/1.5.0.7
Safari/2.0.4
IE/6.0.2900



 Overview

IGUANA has application drivers and extensions that together form the full application. Services are special kinds of extensions that manage a particular aspect of the application state or functionality for their clients. They usually register themselves in the session object. For more details, please refer to the architectural description.
Services can be created in their own packages, or in existing packages as a part of a larger whole. This guide assumes the latter, hence we will not be creating a new package for it. The steps are as follows:
This receipe assumes that you are using the IGUANA C++ templates (automatically available in our emacs customisation).

 Design the service

The first step in developing a new service is of course selecting a design for it. The intended use of the service provides significant input to this step. The answers to the following questions will help to draft the design:
  • Is the service a means of centralising a bit of code, such as providing a point of communication or the management of a resource, or a point of extending the application?
  • Are there multiple alternative implementations of the service? Is the choice among the implementations made at compile-time or dynamically at run-time?
  • Is the service a means to combine -- as opposed to choose among -- a number of concrete implementations?
  • How should the service be initialised? On first use, or with a special initialisation context?
Most services ought to be designed to be objects, not global singletons. A service can be global if and only if it manages process-wide resources that cannot meaningfully be restricted to the scope of the client -- managers of shared libraries, signal handlers and such. All others should be objects attached with a session or other context object. This implies inheritance from IgExtension.
A service should be designed to be used directly via its class interface, not as a dynamically loaded extension. The service can of course delegate its internal implementation to dynamically loaded components as detailed below. There is however no point making the service itself a dynamically loaded extension: the client needs that specific service and the simplest way for it to use it is to use the class directly.
Now consider the questions above. The most important one is whether the service merely centralises the management of a resource, communication or some such concept, or is a point of application extension. The examples of the former would be services that manage the current selection, current window focus, menus, tool bars, how to run GUI event loops, and so forth. We will discuss these simpler designs first.

 Simple services

The simple service class design goes roughly as follows. Issues independent of creation are covered in the "Common desing patterns" section below.
  • The service class interface should provide the means for the clients to perform the necessary tasks. That this class will be an IGUANA service extension should not affect this part of the design at all.
  • The class should have a static instance method that clients use to get access to the service (more on this below).
  • The service constructor should be private so that clients use only the instance method to access the service.
  • The service should not exposed as an extension in the plug-in registry.

 Services with dynamic selection of implementation

This section describes the design of a little a bit more complex services: those that have multiple alternative and mutually exclusive implementations, of which one is selected at run-time. For these services you will need to provide two things: a class hierarchy and a mechanism to select the implementation.
The class hierarchy defines the abstract base class through which the clients will be able to use a number of concrete implementations. The base class should be rich enough that no client needs to cast it down to a specific implementation (if that seems necessary, the service should probably use a different design brake-down as described in the next section). This is merely ordinary use of abstract classes and nothing new.
The complexity is in how to select the concrete implementation. The simplest method is to instantiate an implementation somewhere in the code, for example in the driver. However, this is unlikely to work longer term, making it necessary to provide means to make the selection dynamically. We typically do this by the following (ab)use of the plug-in database as a registry and factory:
  • All concrete service implementations are registered as application extensions in the plug-in database, using a naming convention defined by the base service (typically a naming prefix). This implies the concrete implementations must be in a plug-in library -- even if all of them are in the same library as the base service itself.
  • The base service provides an instantite method that selects a particular implementation using the names of the extensions. It can also provide the list of the names of the available concrete implementations by querying the plug-in database for extensions whose names match the service-specific naming convention.
  • The client creates the concrete service instance with the instance method by passing it the name of the concrete instance it wants. The instance method uses the naming convention to translate the given name to an extension name, requests the plug-in manager to dynamically load and instantiate the extension by that name, casts the resulting pointer down to the abstract service base interface, and then registers the pointer into the session.
  • The client provides the name based on user selection (which can be restricted to the list of available choices provided the base service), from a configuration file, or some aspect of the running environment. A default choice can be given by using some naming heuristic as well (e.g. one extension can be registered twice, under its own name and as a synonym by a name such as "Default").
  • Subsequent uses of the service retrieve the pointer from the session.
[FIXME: The service interface should provide all the methods that are relevant to the service. Some of the methods can be virtual and even abstract if there are several concrete instances that can be used interchangeably. In that case all participating class should ensure they register the service with the same name into the session, and only the base class is used in clients to retrieve a pointer to the service instance.]
[FIXME: This approach can be augmented with the IgSession::remap facility that allows the selection of the session instance to be delayed until runtime; in this case only the base class has an instance method, which would use the IgSession dynamic extension creation mechanism (which is affected by remap) to select the particular instance to run. This assumes the concrete instances are registered in the plug-in database as extensions, presumably with some common naming scheme.]

 Extensible services

The final category of services are the most complex ones: those that are application extension points, usually mini-plug-in architectures of their own. These are services where it is not sufficient to merely select among a number of concrete service implementations, but one needs to make the service itself more modular. These services need to provide fairly large degree of configurability either to clients or other plug-ins in the system -- or both. This is the case in particular if the purpose is to allow a number of plug-ins to hook into some feature.
The service design will depend a little bit on what you will want to achieve; we will describe here two scenarios. The first is a service that is a simple extension point. It has a concrete service class that clients will use. Internally it also defines an abstract interface which plug-in extensions specific to this service can implement. Externally, to the clients of the service, these plug-ins need not be visible in any way.
The second scenario is where a service is extensible and these extensions must be made visible to the clients. This requires one or more abstract interfaces to be defined for the service components -- elements that the service clients will be able to use. The service should be in charge of orchestrating these components, and in particular for instantiating them, but otherwise the clients should use the abstract interfaces in addition to the service interface itself. In essence this is a design much like the IGUANA plug-in architecture itself -- the service becomes a task-specific mini-plug-in architecture of its own, exploiting the IGUANA plug-in manager as a registry and a factory for the internal components.
In both of the above cases the design steps are something like this:
  • The service has an instance method which the clients use to instantiate the service object into the session (see below for more detailed discussion). The service itself is typically not created dynamically, but used directly as in the simplest case.
  • The service defines one or more abstract interfaces for the internal components. The interfaces inherit from IgExtension so that they concrete implementations can be registered in the plug-in database. The service also defines a naming convention, typically a naming prefix, with which extensions are to be registered. Using this convention it can query the plug-in database for available extension components. In other words, the service can use the plug-in database as a simple registry.
  • Concrete implementations of those abstract component interfaces are implemented and registered as application extension plug-ins, using the service naming convention.
  • The service instantiates these components as implementation logic requires: all at once, on client request based on the component name, lazily on demand, and so forth. In general we encourage the use of lazy loading even though it implies a little more coding. When creating a component, the service asks the plug-in database to instantiate the extension, casts the resulting pointer down to the service component abstract interface to check that the extension has the proper form, and then uses that pointer internally. This makes use of the plug-in database as a factory.
  • Because the service components are created as application extensions, they will only get a session pointer as the constructor argument. Therefore they should be designed to do little or nothing in the constructor and instead define separate virtual method to bind the extension with the service object. (Similarly for a release method if the service will dynamically adjust which components it uses.) The service should invoke the bind method immediately after creating the component and having cast the pointer down from IgExtension to the service-specific class interface.
[FIXME: The final method, dynamic service extension is the least clumsy service method and our preferred mechanism for delegating the selection of service implementation until runtime. It is also the method to use in services that should automatically extend to new functionality based on capabilities available in the system. In this method the base service is a concrete object that delegates parts of its functionality to one or more surrogate objects. The surrogates are dynamically loaded extensions; the choice of which extensions to load is usually based on the user's choices, or the service may load all of them, or the choice can determined by some configuration setting in the environment. The base service usually defines one or more interfaces to which concrete extensions must conform, as well as a naming convention with which the extensions conforming to the service's interface register into the plug-in database. That is, the plug-in database is used as a registry of available functionality, and the service instantiates extensions from that registry and downcasts the resulting IgExtension pointers to its own interfaces. The service is in essence designed as a little mini-plug-in architecture of its own.]
[FIXME: Explain how to attach component-specific gui wizards or configurators for services that are themselves not intrinsically gui-related but need some configuration.]

 Common design patterns

In general, all services should be created via a class-static instance method. This method can create a global object, instantiate the object into the session, select an alternative implementation from the plug-in database, and so forth as discussed above. There can be either just one instance method that is used to both retrieve an existing service object and to create the instance, or two separate methods, one for retrieving and for initial construction.
The most significant issue in selecting how many and what kinds of instance methods to provide depends on the amount of context required to create the service. Services that need no additional context in constructor and which can be simply created on first use should certainly have just a single instance method. It should look up the service in the session, and if not found, create it, and then return a pointer to the service to the client. Except for global services, the methods should take as an argument a pointer to the session object.
If the service needs non-trivial constructor arguments, you should add another instance method that takes those arguments, creates the service, registers it with the session, and then returns the pointer to the caller. The reason for doing it this way instead of creating the service object directly with the new operator is to gain uniform coding pattern for all services, both those that are created dynamically or indirectly and those that are created directly. (Besides, it makes it possible to later change the design.) At any rate, it must be made clear who must create the service and when. Typically this will become the duty of the application driver or some higher-level service. The clients that just want to use of the service should never be required to be smart enough to create the service as well as using it -- this will just end up pushing lots of irrelevant high-level knowledge to very deep levels in the application.
Instead of having two separate instance methods, it is possible to combine the two if the extra constructor arguments can be given default arguments such as null pointers. However as this is somewhat confusing as to how it should be invoked, we recommend using two separate methods even though it is a little bit more coding.
[FIXME: inter-service dependencies]

 Define the service class

Let as assume that your service will be called IgFooService. Create interface/IgFooService.h in which you declare class IgFooService. Opening a new file with that name in emacs will automatically insert the header file and basic class structure if you are using our C++ customisation layer.
Following the advice from above and designing the most basic type of service, the class should look like as follows.
#ifndef IG_FOO_BAR_IG_FOO_SERVICE_H
# define IG_FOO_BAR_IG_FOO_SERVICE_H

//<<<<<< INCLUDES                                   >>>>>>

# include "Ig_Modules/IgFooBar/interface/config.h"
# include "Ig_Framework/IgObjectBrowser/interface/IgExtension.h"

//<<<<<< PUBLIC DEFINES                             >>>>>>
//<<<<<< PUBLIC CONSTANTS                           >>>>>>
//<<<<<< PUBLIC TYPES                               >>>>>>

class IgSession;

//<<<<<< PUBLIC VARIABLES                           >>>>>>
//<<<<<< PUBLIC FUNCTIONS                           >>>>>>
//<<<<<< CLASS DECLARATIONS                         >>>>>>

class IG_FOO_BAR_API IgFooService : public IgExtension
{
public:
    IgFooService *	instance (IgSession *session);

    // other service methods here

protected:
    IgFooService (IgSession *session);
    // implicit copy constructor
    // implicit assignment operator
    // implicit destructor

private:
    IgSession		*m_session;
};

//<<<<<< INLINE PUBLIC FUNCTIONS                    >>>>>>
//<<<<<< INLINE MEMBER FUNCTIONS                    >>>>>>

#endif // IG_FOO_BAR_IG_FOO_SERVICE_H
				

 Implement the service class

Now create src/IgFooService.cc to implement the service. Opening a new file with that name in emacs will automatically insert the basic structure if you are using our C++ customisation layer. Define the methods as shown below.
//<<<<<< INCLUDES                                   >>>>>>

#include "Ig_Modules/IgFooBar/interface/IgFooService.h"
#include <classlib/debug.h>

//<<<<<< PRIVATE DEFINES                            >>>>>>
//<<<<<< PRIVATE CONSTANTS                          >>>>>>
//<<<<<< PRIVATE TYPES                              >>>>>>
//<<<<<< PRIVATE VARIABLE DEFINITIONS               >>>>>>
//<<<<<< PUBLIC VARIABLE DEFINITIONS                >>>>>>
//<<<<<< CLASS STRUCTURE INITIALIZATION             >>>>>>
//<<<<<< PRIVATE FUNCTION DEFINITIONS               >>>>>>
//<<<<<< PUBLIC FUNCTION DEFINITIONS                >>>>>>
//<<<<<< MEMBER FUNCTION DEFINITIONS                >>>>>>

IgFooService *
IgFooService::instance (IgSession *session)
{
    ASSERT (session);
    
    IgFooService	*manager = 0;
    IgExtension		*ext;
    
    if ((ext = session->getExtension ("Runtime/Services/Foo", false)))
        VERIFY (manager = dynamic_cast<IgFooService *> (ext));
    else
    {
        manager = new IgFooService (session);
        session->setExtension ("Runtime/Services/Foo", manager);
    }
    return manager;
}

IgFooService::IgFooService (IgSession *session)
    : m_session (session)
{ ASSERT (m_session); }

IgFooService::∼IgFooService (void)
{
    ASSERT (m_session);
    ASSERT (this == m_session->getExtension
            ("Runtime/Services/Foo", false));
    m_session->setExtension ("Runtime/Services/Foo", 0);
}

// other methods here
				

Register the service class (optional)

If you are following one of the more complex scenarios described above, you will need to register the service or a component of it in the plug-in database as an application extension. Follow the instructions in plug-in creation guide. Then check that the extensions show up when you list the plug-in cache contents with iguana, like this:
$ eval `scram runtime -sh`
$ iguana --list
				
If these extensions are only for internal service use and are not supposed to be used directly by the users, please document that fact. Also choose a naming convention from which it is clear that the components are for internal implementation, not public GUI components for instance. (IGUANA applications will soon be able to let users request additional plug-ins to be loaded at run-time. The only information we have is the plug-in names.)