qpid-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Alan Conway <acon...@redhat.com>
Subject Re: [c++]: Sketch of a high-level, protocol independent API
Date Wed, 15 Apr 2009 13:20:38 GMT
My first impression is that there are too many steps and too many
objects involved in sending/receiving messages. 

Essentially there are 3 operations:
- send a message (to somewhere)
- pull a message (from somewhere)
- register a listener to have messages pushed (from somewhere)

We need to define "message" and "somewhere" in a portable way. Your
proposal addresses the "somewhere" part with sources, sinks, producers,
receivers and listeners. I think we can compress this into fewer classes
but I don't have a proposal I'm happy with yet. 

We also need to cover creating wiring, threading model, flow control,
and accepting/acquiring messages (browsing) etc. I think we can evolve
rather than replace the existing API in these areas.

I'll keep mulling it over this week and we can sit down next week. I'll
try to make a start at cleaning up work at cleaning up the mechanics as
per my proposal - did you have any comments on it? Here it is again:


===

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:dev-subscribe@qpid.apache.org


Mime
View raw message