We need to clean up our public C++ API so that we can:
- identify when we might be making incompatible changes that will break existing user code.
 - reduce our implementation exposure to incompatible changes.
Here are some thoughts in that direction, feedback appreciated:


* Public API design principles

** Rationale
Any change to code that can be seen by the user's compiler can potentially break compatibility.

There are two levels of compatibility:
- Binary compatible: user binaries built against old headers/libs can be linked with new libs and will work. - Source compatible: user source code can be re-compiled without change and still work.

Future-proofing the API means:
- clearly identifying public header files - those that are installed and can be seen by user's compiler.
- minimizing the amount of code in public header files.
- hiding implementation as much as possible in public header files.

** Design Principles

Public header files live under cpp/include, private header files live under 
cpp/src
- Only headers in cpp/include are installed and visible to the user's compiler.


QPID_*_EXTERN declaration are required on:
 - all member functions intended to be called by users.
- all member functions used by another qpid library (e.g. common lib functions used by client/broker libs) See http://stevehuston.wordpress.com/2009/03/12/lessons-learned-converting-apache-qpid-to-build-dlls-on-windows/ for some discussion on this subject.

Public classes should fall into one of the following 3 categories:

Handle: handle to refcounted object (e.g. Connection, Session)
- pure pointer to impl (PIMPL) idiom: no data except impl pointer, no virtual functions, no inlines.
 - qpid::client::Handle provides common base class
 - defined in client lib, namespace ::qpid::client

Interface: abstract base class, intended for user to subclass (e.g. 
MessageListener)
 - defined in client lib, namespace ::qpid::client

Value: data type with value semantics (e.g. std::string, FieldTable)
 - defined in common lib, namespace ::qpid
 - imported into ::qpid::client namespace with using statements.
 - more on value types below, this is the area that needs most work.

Note type system is defined in common lib, ::qpid namespace so it can be shared by client & broker code. It is imported into ::qpid::client namespace so a typical client need only use the qpid::client namespace.


No boost headers included in public .h files: I think this is
feasible. It would the boost-devel requirement for clients and avoid
incompatibilities due to boost version changes.


* Value Types Proposal

Value types have the most exposed implementation so need to be kept as simple and clean as possible.
They should:
 - represent strictly the *users* view of the data type: no encode/decode 
functions
 - have value-semantics - no virtuals, normal copy semantics etc.
- not be tied to a specific AMQP protocol version - this is the C++ view of the type.

Integral types: typedef int8_t Int8; typedef uint16_6 Uint16; ... etc.

String types: typedef std::string String;

Additional classes: class SequenceNumber; class SequenceSet; class Url etc.

Discriminated union type: needed by FieldTable and Url. Currently Url
uses a boost::variant which is not extensible, and FieldTable uses
FieldValue which has a somewhat ad-hoc interface.

Propose replacing both with class qpid::Any, modelled after boost::any
with some additional features described below.  This provides a
standard-ish (boost::any is proposed for a future C++ standard) API
that allows us to store *any* C++ type without modifications.

FieldTable: propose replacing with
  typedef std::map<std::string, Any> Map;
Keep deprecated framing::FieldTable for compatibility.

This allows code like:

  map["foo"] = std::string("abc"); // Creates an any containing std::string

  std::string* s = anyCast<std::string>(&map["bar"]);
  if (s) { do string stuff }

  uint16_t i = anyCast<uint16_t>(&map["x"]); // throw exception if not a 
uint16_t

as well as all the standard map iterator stuff.


** Relating C++ types to protocol types.

There is not an exact 1-1 mapping between protocol types and the basic
value types described above. E.g. the protocol's str8, str16, and
str32 all map to std::string.

Where we need a 1-1 mapping (e.g. in a Map) we use **type wrappers** e.g.

namespace qpid::amqp_0_10 {
struct String8 { String value }; // ctors & conversions to & from String omitted
  struct String16 { String value  };
  struct Vbin32 { String value };
  etc.
};

So to insert a value in a map that I want encoded as a string with an 8 bit 
length:

 map["foo"] = amqp::0_10::String8("bar");

An Any decoded off the wire will always use the wrapped form of a type. e.g.

 if (anyCast<amqp0_10::String8>(&any)) { ... }
 else if (anyCast<amqp0_10::String16>(any)) { ... }
 ...


To allow the user to avoid these protocol specifics we provide anyConvert():

  if (anyConvert<String>(&any)) { ... }

This will work if the any contains any type that can be converted to string.

We use **type traits** to associate protocol-specific type codes with C++ types.

 TypeCode<T>::value == type code for type T.

There is a default mapping for unwrapped types with multiple mappings, e.g.

 TypeCode<std::String>::value == typecode for String32

Finally we add one more function to get the type code from an any:

 anyTypeCode(any) returns the type code for the any.

To support code like:
 switch(anyTypeCode(any)) {
   case STR8_TYPE: ...
   case UINT16_TYPE: ...
 }
}

QUESTION: all of the above should probably be in a
qpid::amqp_<version> namespace?  Can we rely on the 0-10 codes not to
change meaning in future versions? 1.0 type system is simpler but is
it backwards compatible?


---------------------------------------------------------------------
Apache Qpid - AMQP Messaging Implementation
Project:      http://qpid.apache.org
Use/Interact: mailto:[email protected]

Reply via email to