gdl
    title     = AMQ DRAFT
    subtitle  = The Module System
    product   = OpenAMQ
    author    = iMatix Corporation <amq@imatix.com>
    date      = 2004/10/10
    copyright = Copyright (c) 2004 JPMorgan
    version   = 0.1
end gdl

Cover
*****

State of this Document
======================

This document is a working draft.  Distribution of this document is
currently limited to iMatix and JPMorgan internal use.

This document describes a work in progress.

Copyright Notice
================

This document is copyright (c) 2004 JPMorgan Inc.

Authors
=======

This document was written by Pieter Hintjens <ph@imatix.com>.

Abstract
========

In order to reduce the complexity of the OpenAMQ server and thus reduce
the cost and effort of building, testing, maintaining, and improving it,
we have chosen a design that is almost entirely based on the concept of
plug-in "modules".  While the server architecture is well-defined, with
a specific core flow of data from client interface to destination through
various layers, the actual implementation of each layer is defined as an
abstract interface with an arbitrary implementation.  In this document
we define the general API that allows modules to register with the OpenAMQ
server kernel, and we sketch the specific APIs for each class of module.

Introduction
************

Problem Statement
=================

We need a generic way of linking arbitrary packages of C code with the
OpenAMQ server, along with standard semantics to allow such packages to
find and talk to each other.  At the same time, the run-time cost of
this abstraction must be as low as possible, ideally no more than the
normal cost of invoking functions.  At present we can assume that the
modules are linked at build-time; the issue of loading dynamic modules
can be handled at a later stage.

Argumentation
=============

We have a number of specific problems to solve:

1. Identifying the different modules in some way.
2. Telling the linker what modules to include in our executable.
3. Finding the correct module to use for a specific task.
4. Passing information to and from the module.
5. Abstracting the module API for all modules of a specific type.
6. Providing standardised ways of testing and qualifying modules.

Let us work through each of these problems...

We may create servers with many dozens of modules. To ensure that all
modules are clearly and unambiguously defined, we need naming standards.
While we can speak of "modules" in general, it is also clear that each
module belongs to a specific class, which circumscribes its functionality
if not its specific implementation.  We can decide to use the class name
as part of the module name.

To use the linker to embed modules in our server binary we must call one
of the functions in the module source file.  It makes sense to chose a
standard type of function for this, and we call this the "module main".
How do we list all the module mains we want to call?  At some point we
have to allow the developer to modify code to include new modules.  It is
not a good idea to modify core code, or even any code which forms part of
the formal server source package.  I.E. we cannot say "this header file
defines the modules to link, edit it to add your own modules", because as
soon as the developer updates to a new source release, there will be a
change conflict.  The best solution (the one we used in similar cases in
the past) is to list the required modules in the main source file.  This
file can be copied from templates, adapted and saved under a new name.
The advantage of this approach is that each distinct main file will also
correspond to a unique binary, which is exactly what we want.

To do module lookup at runtime ("what module do I have to call to process
this piece of data"), we need to do two things.  First, we need a standard
way for modules to register themselves.  Secondly, we need a standard way
to use this information to choose the appropriate modules when needed.
It would be possibly to short-cut this so that we can refer to modules by
name, but this does not allow the flexibilty we need.  A module must be
totally anonymous (from the point of view of the code that uses it), and
only known by the way it has registered itself to handle different kinds
of work.  We will call these the "bindings" that a module makes.

Passing information to and from modules happens via normal C function
calls, through what we call the "module methods".  Methods are just a name
for formalised function calls that follow the standard naming style used by
iCL.  There are, generally, two kinds of work.  There is work that takes a
short finite time, and there is work that takes an undetermined time, often
because it depends on events coming from outside the system.  We do not
want module methods to 'block' while waiting for work to be done.  Rather,
we want such methods to return at once, and do the waiting in some way that
does not block the rest of the server.

We have discussed module classes: these are the distinct types of module
that we need to make our server work.  Each class has a specific set of
data (its properties) and functions (its methods).  From the point of view
of the server and code that uses modules, how these classes are implemented
is not significant, so long as they conform.  From the point of view of the
developer it is desirable to use a framework that provides the "conformity"
automatically.  We have such a framework, iCL.

Lastly, it is vitally important that every module can be tested in a solid
manner.  Since we know the module's API we should be able to define a full
framework for testing each module, quite independently from its operation
within the server.  This has several advantages: it is faster for the
developer, since problems can be found immediately rather than after a
complex test involving servers, clients, and test data.  It is better for
the quality of the product, since modules can be "certified" by their
success in passing a series of tests.

Basic Proposal
==============

We propose a simple framework for developers in which they can write and
test server modules of different kinds.  This framework consists of a set
of naming conventions, a toolkit (a set of iCL classes), and standard
models for building modules.

Design Proposal
***************

Definitions and References
==========================

See AMQ_RFC008 for terminology, and Appendix 1 for a definition of the iCL
language.

Objectives
==========

We aim to make the process of writing, testing, and using server modules
as cheap and safe as possible.  We assume that modules will be statically
linked into the server binary.

Architecture
============

Our solution is based on these design elements:

1. A standard naming and typing convention for modules.
2. A standard API for registering modules.
3. A standard API for locating a module.
4. A set of standard APIs for talking to modules.
5. A standard toolkit for abstracting modules APIs.
6. A standard toolkit for testing and validating modules.

Proof and Demonstration
=======================

The architecture and design described in this document is the basis for
the current implementation of the OpenAMQ server.

Detailed Proposal
=================

Naming and Typing
-----------------

We define modules as having:

- A class identifier
- A public name
- A version number

The class identifier is one of the standard module classes that OpenAMQ
supports.  The module classes are defined in AMQ_RFC010 and are encoded in
amq_module.icl.

The module name is a text string that is unique within the class.  The
version number is a text string that documents the version number of
the module.  At present this is not used for any kind of checking or
selection, but it may be used for error solving.

The source code for an iCL class definition that defines a module class
is always in a file called amq_cccc.icl where 'cccc' is the name of the
class.  For example:

    amq_driver.icl
    amq_terminal.icl

The source code for a module will be in a single file (which may include
other files if necessary for its internal organisation), which will be
named amq_cccc_mmmm.xxx where 'cccc' is the class name, 'mmmm' is the
module name, and 'xxx' is the extension of the source file.  For example:

    amq_terminal_http.smt
    amq_terminal_amqp.smt
    amq_driver_filequeue.icl
    amq_parser_amqxml.icl
    amq_dispatcher_fast.icl

Module Registration
-------------------

Telling the linker what modules to use means using some public identifier
defined by that module.  We standardise this as follows: all modules will
have a function called "modulename_main" where "modulename" is the prefix
common to all functions in that module.

Here is a main program that registers two modules and then runs the server
core (thus launching the OpenAMQ server):

    /*  Example OpenAMQ server main program  */
    int amq_terminal_abc_main (void);
    int amq_driver_xyz_main   (void);
    int
    amq_user_modules (void)
    {
        if (amq_terminal_abc_main ())
            return (1);
        if (amq_driver_xyz_main ())
            return (1);
        return (0);
    }
    int
    main (int argc, char *argv [])
    {
        return (amq_core (argc, argv));
    }

The amq_core() module starts the server and when it is ready to register
user-specified modules, it calls the amq_user_modules() function, which
must be present in the main program (or included in the binary through
some other route).

The _main function is defined in each module.  This is a typical example:

    amq_driver_t
        *this_driver;
    this_driver = amq_driver_new (MY_NAME, MY_VERSION);
    if (this_driver) {
        this_module->constructor   = &create_channel_thread;
        this_module->new_event     = new_event;
        this_module->post_event    = post_event;
        this_module->request_event = request_event;
        this_module->destroy_event = destroy_event;

        /*  Bind to the paths that we manage                                 */
        /*  These would come from the server configuration                   */
        amq_driver_bind (this_driver, ...);
        return (0);
    }

Note that the _main function returns 0 to signal success.  If any module
fails when registering, the server will halt.

Locating a Module
-----------------

We do not look for modules by name, since this would presuppose a static
configuration that we want to avoid. Rather, each module exports the
list of services that it provides, and we provide a centralised way of
finding a module associated with a service.

This is done simply by using a hash table (an array indexed by string
keys) where we store the bindings each module makes. The bind method
registers a new binding between a module and a key value. The lookup
method finds the module for a key, if any. This is a general solution
that handles all the module classes we have.

For example:

- Drivers bind on URL paths.  Terminals perform lookups on paths to find
  the drivers they need to talk with to handle work on those paths.
- Parsers bind on MIME types.  Dispatchers perform lookups on message MIME
  types to find the parser responsible for handling each message.

Obviously we do not need to perform a lookup each time we need a module;
the _lookup method returns a static module reference that can be stored
and reused.

Standard Module APIs
--------------------

Module APIs depend on the module class implementation.  We do not define a
single standard for method names, argments, or implementation except that
methods are synchronous and return immediately.  Any asynchronous work
that a method does must be queued and handled asynchronously, e.g. by a
thread in an SMT agent.

Our standard way of building an asynchronous API between two modules is:

- The calling module supplies its own reference when invoking an
  asynchronous method.
- The called module queues the method arguments and returns immediately.
- When the called module has finished processing the method, it invokes
  the calling module (doing a so-called "callback") with one of a set of
  standard methods.
- Success or failure is signalled by invoking different methods, rather
  than by providing a return code. When invoking an error method, the
  called module (now doing the callback) provides a reason code.

The implementation of each module API will be the subject of a separate
AMQ RFC document.  At present time, these AMQ RFCs cover module APIs:

AMQ_RFC005:
    The terminal-driver API.

Module API Abstraction
----------------------

We use iCL to abstract the module APIs.  Each module class is implemented
by a corresponding iCL class.  These classes are listed and explained in
AMQ_RFC010.

To create a new module of a particular class, the developer may use iCL as
a framework, but this is not essential.

There are two ways to abstract the module API for any given module class.
The first way is to derive an iCL class and write the module methods in
the iCL framework.  This gives a new class which will be instantiated as
a single object.

An alternative is to use method references in the module object. The
developer creates a module object of the required class and initialises
the method references to point to the correct function for each method.

We will provide examples of both models or standardise on a specific model
when we have implemented this.

Testing and Validating Modules
------------------------------

The module class will provide a test handler that performs full validation
of the module's functionality.  We expect these test handlers to be quite
large, and will be developed and extended over time.

Alternatives
============

We do not propose any alternatives at this moment.

Security Considerations
=======================

This proposal does not have any specific security considerations.

Appendices
**********

Appendix 1 - The iCL Language
=============================

[See icl_doc.txt]

Comments on this Document
*************************

Date, name
==========

No comments at present.
