В чистом виде механизмы расширяемых типов и наследования расширением решают проблему корректного разбора старым вспомогательным кодом нового сериализованного представления. Например, пусть в первой версии протокола описан тип заказа покупки:
{type amount_data_t {extensible} ... } {type merchant_data_t {extensible} ... } {type goods_t {extensible} {subclassing_by_extension} ... } {type order_t {extensible} || Описание суммы, валюты, типа платежной системы и т.д. {attr m_amount_data {of amount_data_t}} || Описание этого заказа в магазине. {attr m_merchant_data {of merchant_data_t}} || Список входящих в заказ товаров. {attr m_goods {stl-list} {of {extension_of} goods_t}} }
Объекты типа order_t передаются между магазином и платежной системой обслуживающего этот магазин банка.
Данный протокол может расширяться обеими сторонами. Так, магазин может вводит новых потомков для типа goods_t, т.к. описание покупаемого автомобиля может существенно отличаться от описания холодильника. Банк может расширять типы amount_data_t и merchant_data_t для новых типов магазинов и платежных систем, поддерживая при этом совместимость с магазанами, работающими со старыми спецификациями типов amount_data_t и merchant_data_t. Поскольку в таких расширениях задействованы всего две взаимодействующие стороны (магазин и его банк), то механизмы раширяемых типов и расширяющего наследования прекрасно справятся с подобными задачами.
Но что, если между магазином и банком появляются еще несколько слоев? Например, банк, который выпустил (эммитировал) платежную карту, с помощью которой осуществляется покупка. Этот банк проводит аутентификацию плательщика и подтверждает возможность проведения платежа. Или автоматическая система, которая пытается обнаружить случаи мошеничества на основании статистических методов и списков номеров дискредитированых платежных карт. Или автоматическая система анализа заказов магазина, которая на основании order_t::m_goods пытается спрогнозировать изменение покупательского спроса.
Во всех этих случаях появляется третья сторона, которая выполняет роль ретранслятора между магазином и его банком. При этом ретранслятору не обязательно знать последнюю версию спецификации протокола для выполнения своих прикладных действий. Но, если ретранслятор не знает про расширения типов и при десериализации извлекает только известные ему составляющие типов, то как ретранслятор передаст точные исходные образы объектов при повторной сериализации?
Примитивным решением являлось бы сохранение точной двоичной копии входящих данных, десериализация в известные ретранслятору типы и их обработка, после чего повторная сериализация не выполняется, а ретранслируется сохраненная ранее копия. Такое решение требует, чтобы прикладная логика ретранслятора имела возможность получить доступ к нераспакованному, двоичному представлению входных данных. Что может быть вообще не возможно, если прикладная часть ретранслятора получает уже десериализованные объекты от транспортной части, а транспортная часть занимается десериализацией/сериализацией даже не имея представления о прикладном назначении обрабатываемого трафика. Такая ситуация, например, происходит при работе через такие высокоуровневые протоколы, как SOAP или CORBA.
Второе решение заключается в том, чтобы при десериализации не "выбрасывать" неизвестные расширения объектов, а специальным образом сохранять их в десериализованном объекте. А при повторной сериализации помещать их в выходное двоичное представление. Именно такое решение реализовано в ObjESSty версии 1.2.0.
Например, пусть есть две версии спецификации прикладного протокола: v1 и v2. И две взаимодействующие стороны (A, B), между которыми оказался ретранслятор R. При этом A и B используют спецификацию v2, а R -- v1. Благодоря поддержке неизвестных расширений R получает от A запросы в версии v2, но работает только с частью запросов, которая описывается версией v1. Но после повторной десериализации B получает от R ретранслированные запросы в версии v2! Таким образом, A и B вообще не имеют представления о том, что их трафик кем-то ретранслируется.
Для простых расширяемых типов поддержка неизвестных расширений тривиальна. А для наследования расширением в базовом классе oess_1::stdsn::serializable_t сохраняется не только образ сериализованных неизвестных атрибутов и типов, но и информация о том, какой реально тип был сериализован (т.н. subclassing extension path). Например, если при сериализации на стороне A был сериализован тип passenger_car_t, производный от car_t, производный от goods_t (т.е. subclassing extension path == {passenger_car_t, car_t, goods_t}), а сторона R знает только про тип goods_t, то на стороне R будет десериализована только составляющая типа goods_t оригинального объекта. Но subclassing extension path оригинального объекта будет сохранен как часть неизвестного расширения. Поэтому, при повторной сериализации на стороне R subclassing extension path будет восстановлен в своем исходном значении (т.е. {passenger_car_t, car_t, goods_t}). И сторона B десериализует именно объект типа passenger_car_t.
Поскольку неизвестные расширения сохраняются в базовом типе oess_1::stdsn::serializable_t, то производным от него прикладным типам не нужно заботится о том, чтобы копировать неизвестные раширения в конструкторах и операторах копирования. Для этого достаточно задействовать оператор копирования из базового типа. Например:
class goods_t : public oess_1::stdsn::serializable_t { typedef oess_1::stdsn::serializable_t base_type_t; OESS_SERIALIZER( goods_t ) public : goods_t() {} goods_t( const goods_t & o ) : base_type_t( o ) { ... } goods_t & operator=( const goods_t & o ) { base_type_t::operator=( o ); ... return *this; } ... };
Если же сохранение неизвестных расширений и их последующая сериализация не желательно, то можно воспользоваться методом oess_1::stdsn::serializable_t::oess_drop_unknown_extensions(), который "выбрасывае" все неизвестные расширения из объекта. При этом теряются как расширения простых расширяемых типов, так и расширения типов в механизме наследования расширением (включая и subclassing extension path).
Благодоря механизму неизвестных расширений можно реализовывать промежуточные прикладные системы, которые обрабатыват "то, не знаю что". Например, пусть есть система, состоящая из магазина
, который продает услуги производителя
; банка-эквайера, который обслуживает магазин; банка-эммитента, который выпускае платежную карту. В центре этой системы находится платежный шлюз, который:
При этом платежный шлюз для обращения к производителю оперирует объектом order_t::m_goods, для обращения к банку-эмитенту -- объектами order_t::m_amount_data и order_t::m_merchant_data, а для обращения к банку-эквайеру -- объектом order_t::m_amount_data.
Примечательно, что платежный шлюз может знать только об одном типе из семейства типов goods_t -- базовом типе goods_t. О конкретных типах товаров должны знать только магазин и производитель. Более того, разные магазины и производители могут оперировать разными ветвями иерархии goods_t. Например, торгующие автомобильями магазины могут использовать свои унаследованные от goods_t типы (car_t, passenger_car_t, truck_t, racing_car_t и т.д.). А фото-магазины свои: photo_cam_t, digital_photo_cam_t, film_photo_cam_t, film35mm_photo_cam_t. А магазины, торгующие иформационными услугами -- свои (magazine_t, video_on_demand_t, ...). Все эти типы могут создаваться намного позже того, как будет реализован и запущен в реальную эксплуатацию платежный шлюз. При этом конкретные типы, описывающие реальный товар, могут быть достаточно сложными. Например, тип passenger_car_t может включать в себя описание десятков параметров: начиная от цвета кузова, типа и цвета обивки салона, заканчивая подробным описанием каждого из колес (включая запасное), а так же пакет дополнительных услуг (страхование, установку противоугонной системы, время и место доставки автомобиля заказчику, ...).
Более того, после запуска в экплуатацию платежного шлюза могут быть расширены даже такие типы, как merchant_data_t и amount_data_t. Например, в какой-нибудь очень независимой стране может использоваться локализованная версия платежной системы Visa. Карты этой системы требуют такой же аутентификации, как и остальные карты системы Visa. Различия могут заключаться в том, что при платеже этой картой внутри данной независимой страны магазин должен сообщить эквайеру дополнительную информацию, которая передается в новой версии типа amount_data_t. Платежный шлюз оказывается работоспособным и в этом случае, т.к. он, как и банк-эммитент, работает только с необходимой им частью объекта amount_data_t. А магазин и банк-эквайер -- с полной версией amount_data_t.