This page last changed on Oct 20, 2006 by kpvdr.

Back to Multiple AMQP Version Support

Approach to adding support for multiple AMQP versions into the broker

I had intended to follow approach 2 below, however after some brainstorming, a better and more efficient approach has been proposed. The following is a summary of the possible courses of action.

Kim van der Riet


1. Present status: No AMQP version discrimination.

ADVANTAGES:

a. Nothing changes in the code, and everyone keeps doing what they are already doing...

DISADVANTAGES:

a. There is no easy way of hosting more than one version of AMQP at the same time in the broker.
b. The code that touches the version-specific generated method body classes is scattered all over the place. This makes it difficult to maintain the code if there is a change to the AMQP protocol. In order to help conceive the scope of possible protocol changes (outside of any policy the AMQP WG may have in this regard) consider the following possible change types/scopes:

  1. A method call (class, method, fields, types) does not change, but the use or purpose of a field changes;
  2. A method call changes the type of one or more fields;
  3. A method call changes the number of fields (including by removing a field from one version to the next);
  4. A method call changes the order of the fields;
  5. A totally new method call is added;
  6. A method call is removed;
  7. A class is added;
  8. A class is removed;
  9. Any of 5-9 above in such a way as to alter the class/method numbers of unchanged elements (e.g. a class is removed, so class Basic changes from 60 to 50)

2. Initial approach to solving problem: Namespace discrimination.

a. In this approach, each version of the protocol is generated as before, but into a namespace unique to that version.
b. The ProtocolSession objects are made version-aware, and carry the major and minor version numbers of the current session from the ProtocolInitiation objects used to start the session.
c. The points in the code where version-specific method body objects and/or calls are made are moved into a single layer - the method handlers. Static function calls are added where necessary to instantiate and/or process these objects. Since the method handlers are version-aware, they would contain the logic to select the correct namespace.
d. The generated code would contain additional methods which allow the fields within any method body to be accessed or set using a parametrized function call. This would aid the coding of the version-sensitive logic used in the method handlers.

      
    +--------+       +---------+       +------------+       +---------+
    | Broker |       |  Method |       | Generated  |       | Encoder |
    | code   | <---> | Handler | <---> | MethodBody | <---> | Decoder |
    |        |       | classes |       |  classes   |       | classes |
    +--------+       +---------+       +------------+       +---------+
       1                1 set         n sets, 1/version    1 or possibly more

ADVANTAGES:

a. Allows multiple AMQP versions to be used simultaneously in the broker.
b. Isolates the scope of version-specific code to the method handlers, where hand-coded logic specific to the needs of individual AMQP versions may reside. This aids maintainability in the event of additional versions being added/versions being changed.
c. Forces the code to choose a specific AMQP version to use the MethodBody classes.
d. Works using existing code-generation system (XSLT).

DISADVANTAGES

a. Code duplication: Since in most AMQP version changes, 90+% of the specification remains unchanged, the generated classes will contain mostly exact duplicates from version to version.
b. The ideal system of version fallback (in which default behavior is to use the latest class and to hand-code logic only where differences exist) does not operate. Each version must be handled separately whether identical or not.
c. The code using the parametrized function calls is more complex and not as easy to understand as direct calls.

3. Revised approach: Intelligent generation

a. In this approach, the limitations of the existing XSLT code generation are removed. We assume that we can compare one version of the AMQP specification to another, and generate code for the latest version and code to handle only the differences between the latest and each earlier version. To achieve this, we conceptually refactor the XML specification file so that the version information is contained inside the elements instead of outside them. Then each version of the spec is added cumulatively to create a complete map of the specification. For example, assume the following simple example:

AMQP 1.0:
Basic.Consume(Ticket ticket, Queue queue, ConsumerTag consumer_tag, NoLocal no_local, NoAck no_ack, bool exclusive, bool nowait);
AMQP 1.2:
Basic.Consume(Ticket ticket, Queue queue, ConsumerTag consumer_tag, int no_ack, bool exclusive, bool nowait, NoLocal no_local, int priority);

would be refactored to something like (abbreviated):
<AMQP>
  <class name="Basic>
    <version major="1" minor="0" num="60"/>
    <version major="1" minor="2" num="50"/>
    <method name="Consume">
      <version major="1" minor="0" num="20"/>
      <version major="1" minor="2" num="20"/>
      <field name="ticket" type="Ticket">
        <version major="1" minor="0" num="0"/>
        <version major="1" minor="2" num="0"/>
      </field>
      <field name="queue" type="Queue">
        <version major="1" minor="0" num="1"/>
        <version major="1" minor="2" num="1"/>
      </field>
      <field name="consumer_tag" type="ConsumerTag">
        <version major="1" minor="0" num="2"/>
        <version major="1" minor="2" num="2"/>
      </field>
      <field name="no_local" type="NoLocal">
        <version major="1" minor="0" num="3"/>
        <version major="1" minor="2" num="6"/>
      </field>
      <field name="no_ack" type="NoAck">
        <version major="1" minor="0" num="4"/>
      </field>
      <field name="no_ack" type="int">
        <version major="1" minor="2" num="3"/>
      </field>
      <field name="exclusive" type="bool">
        <version major="1" minor="0" num="5"/>
        <version major="1" minor="2" num="4"/>
      </field>
      <field name="nowait" type="bool">
        <version major="1" minor="0" num="6"/>
        <version major="1" minor="2" num="5"/>
      </field>
      <field name="priority" type="int">
        <version major="1" minor="2" num="7"/>
      </field>
    </method>
  </class>
</AMQP>

This would result in the generation of only a single MethodBody class, and since all instances of this class are version-aware, each knows how to handle calls for a given version (pseudo-java):

class BasicConsumeBody extends AMQPMethodBody
{
    public int _major, _minor;
    public BasicConsumeBody(int major, int minor) {_major = major; _minor = minor;}
    public int getAMQPClass(){if (major == 1 && minor == 0) return 60; else return 50;} // the class number has changed!
    public int getAMQPMethod(){return 20;}

    public Ticket getTicket() {...}
    public Queue getQueue() {...}
    public ConsumerTag getConsumerTag() {...}
    public NoLocal getNoLocal() {....}
    public T getNoAck(Class<T>) {if (major == 1 && minor == 0)...} // NoAck changes type
    public boolean getExclusive() {...}
    public boolean getNoWait() {...}
    public int getPriority() throws Exception {if (major != 1 || minor != 2) throw...} // Priority field is in 1.2 only
    // also for setXXX() methods...
}

Static functions would require major and minor to be passed to be able to operate.
a.#2 As before, the ProtocolSession objects are made version-aware, and carry the major and minor version numbers of the current session from the ProtocolInitiation objects used to start the session.
b. As before, the points in the code where version-specific method body objects and/or calls are made are moved into a single layer - the method handlers.
c. If different versions of a particular method have additional and/or different parameters, the generated class may be thought of as a cumulative collection of all these parameters/types. Since the object is AMQP version-aware, it is possible for the handlers to know which fields to use and/or how to initialize/handle the unused fields for any call which addresses less than all the available fields. Possible courses of action include ignoring the unused field, or initializing it with a meaningful default. Making the field members of the class public would afford this flexibility, and the get/set methods could simply be viewed as convenience methods. Where the type of a field changes, the different fields would require some kind of name-mangling to indicate their type.

ADVANTAGES

a. Only a single class for each method body
b. Single get/set method for each field
c. Common code is simple and remains unchanged from version to version
d. Where a field type changes, a Class<T> type method is used; this will break all areas in the code which used to use this method, and conveniently indicate where hand-coded logic is required.
e. Where there is a field that is unique to a subset of versions, auto-generated code will throw an exception if it is invoked from a session of the wrong version. The addition of the exception will break the compilation (i.e. must be caught), and will indicate places in the code where hand-coded logic will be required.
f. The existing handler code can remain substantially unchanged.

DISADVANTAGES

a. Complexity...
b. This generation is beyond the capabilities of XSLT (or would be inordinately difficult), and we would need to switch to either Python (or Jython) or a purpose-written Java generator to do the job.
c. It is not clear from the interface what field is valid in what version. There has been some discussion on name-mangling to solve this, but the general dislike for name-mangling in general and the complexities it brings seem to outweigh any advantages...

Document generated by Confluence on Apr 22, 2008 02:47