/*!
\page oess_1_2_0__extensible_types oess_1.2.0. Расширяемые типы

\section oess_1_2_0__extensible_types__version_problem Проблема версий

Стандартный механизм сериализации oess_1::stdsn имеет два
основных применения: долговременное хранение информации в oess_1::db
и для реализации обмена данными между несколькими процессами.
В обоих случаях возникает проблема, известная как "<b>проблема второй
версии</b>". Заключается она в том, что с течением времени спецификация
сериализуемого типа нуждается в изменении, требуется выпустить
"вторую" версию спецификации.

Применительно к долговременному хранению объектов существует
специальный термин: <i>эволюция схемы данных</i>. Он означает, что с
течением времени некоторые атрибуты хранимого объекта могут
быть изъяты, переименнованы или добавлены.
Некоторые атрибуты могут изменить
свой тип (например, с unsigned char на unsigned short,
с int на float и т.д.). Задачей базы данных является обеспечение
наиболее безболезненой эволюции схемы данных. При этом база данных
либо обеспечивает автоматическую (в крайнем случае автоматизированую)
модификацию уже сохраненных данных, либо выполняет преобразование
из старого формата в новый "на лету" в момент
обращения к данным прикладных приложений.

Применительно к межпроцессовому взаимодействию проблема
второй версии носит несколько иной характер. Как правило, существующий
список атрибутов, их типы и размерности не изменяются. Поскольку
это <i>интерфейс</i>, который был специфицирован и поддержан
всеми взаимодействующими сторонами. Модификация интерфейса может
потребовать изменение его реализации на каждой из взаимодействующих
сторон. Что не всегда возможно. Если бы это было легко осуществимо, то
проблемы второй версии бы не существовало -- просто все стороны
одновременно перешли на использование нового интерфейса.
Проблема второй версии в межпроцессовом взаимодействии как раз
возникает, когда несколько из взаимодействующих сторон <b>не могут</b>
перейти на использование новой версии интерфейса.
Применение средств преобразования данных из одного формата
в другой "на лету" так же может оказаться неприемлимым, например, из-за
необходимых для этой операции вычислительных ресурсов.

Поэтому, при использовании сериализованных объектов для межпроцессового
взаимодействия, наиболее распространненой практикой является расширение
списка атрибутов, без изменения имен, типов и размерностей старых
атрибутов. В этом случае возникает задача поддержки
одновременного взаимодействия
нескольких процессов, каждый из которых использует разные версии
интерфейсов. Например, клиент 1 работает с версией #1, клиент 2 работает
с версией #3, а сервер с версией #2.

Очевидны две проблемы:
- каждая из сторон должна знать, на какую функциональность она может
расчитывать. Например, если клиент с более новой версией интерфейса
подключается к серверу со старой версией интерфейса, то клиент должен
понять, что от этого сервера безполезно ожидать новой функциональности.
Одним из способов определения объема функциональности, на которую
могут расчитывать обе стороны является процедура <i>handshake</i>,
в процессе которой обе стороны сообщают друг другу номера версий
поддерживаемых ими протоколов. Такой механизм предполагает, что
сообщения процедуры <i>handshake</i> являются частью прикладного
коммуникационного протокола, и он не имеет непосредственного отношения
к проблемам сериализации данных;
- каждой из сторон должно быть гарантировано, что она корректно получит
любое сообщение, к какой бы версии интерфейса оно не принадлежало
(естественно, речь идет о сообщениях, присутвующих во всех версиях
интерфейса).
Например, есть сообщение \c A, которое в версии #1 имеет атрибуты
\a a, \a b, \a c. В версии #2 добавляются атрибуты \a d, \a e. А в версии
#3 добавляются атрибуты \a f, \a g. Если клиент с версией #3 отправляет
сообщение \c A серверу с версией #2, то сервер должен получить
сообщение с корректными значениями атрибутов \a a, \a b, \a c,
\a d, \a e. Если же клиент с версией #1 отсылает сообщение клиенту с
версией #3, то клиент с версией #3 должен получить сообщение с
корректными значениями атрибутов \a a, \a b, \a c и с выставленными
значениями по-умолчанию для атрибутов \a d, \a e, \a f, \a g.

Как раз проблема гарантированного разбра сериализованного объекта
безотносительно к тому, был ли он сериализован более старшей версией
или нет, непосредственно касается oess_1::stdsn.

Ниже обсуждаются некоторые возможные способы решения данной
проблемы, после чего описывается реализованный в oess v.1.2.0
механизм <i>расширяемых (extensible)</i> типов.

\subsection oess_1_2_0__extensible_types__version_problem__ent_serializer Сериализаторы сущностей

Любой способ поддержки расширений спецификации сериализуемого
типа сводится к тому, что в двоичное представление объекта помещаются
специальные маркеры, по которым становится возможным определить,
что же было сериализовано. Основных способов организации таких
маркеров два: на основе тегов и на основе порядка следования атрибутов
в спецификации типа.

Ярким примером подхода на основе тегов являются
способы сериализации ASN1 BER (Abstract Syntax Notation One
Basic Encoding Rules), использующий механизм TLV (Tag, Length, Value)
и сериализация с
использованием какого-либо из языков разметки (XML, cls_2). В таком
подходе каждому атрибуту предшествует тег, по которому определяется
сериализованный атрибут. Поэтому не важно, в каком порядке атрибуты
будут сериализованы. Так же не важно, все ли атрибуты будут сериализованы
(т.к. легко определяются какие атрибуты присутствуют). Не критично и
наличие неизвестных атрибутов, которые просто игнорируются.

Примером использования порядка следования атрибутов является
ASN1 PER (Packed Encoding Rules). В нем сериализацией/десериализацией
занимается вспомогательный код, сгенерированный на основе спецификации
сериализуемого типа. Данный код упаковывает/распаковывает атрибуты
объекта в том порядке, в котором они указаны в спецификации. В некоторых
случаях в сериализованное представление помещаются специальные
маркеры, указывающие присуствие/отсутствие значения конкретного атрибута.
Такой подход становится зависимым от спецификации, но обеспечивает
более высокую эффективность, как по скорости, так и по объемам
необходимой памяти и генерируемого сериализуемого представления.

Сериализация в ObjESSty, с теоритической точки зрения, не завязана
непосредственно ни на один из данных подходов. По DDL описанию
строится вспомогательный код, который опрерирует атрибутами в том
порядке, в котором они описаны в DDL. Но вспомогательный код не
занимается непосресдственным формированием сериализованного образа
объекта. Этим занимаются классы-<i>сериализаторы сущностей</i>.
Данные классы реализуют интефейсы oess_1::stdsn::ient_t,
oess_1::stdsn::oent_t и своей реализацией определяют, какой же механизм
разметки должен использоваться.

Однако очевидно, что подобное использование вспомогательного кода
в ObjESSty более близко к подходу ASN1 PER. Поэтому в oess v.1.2.0
существует всего две реализации сериализаторов сущностей, которые
строят/разбирают безтеговое двоичное представление объекта:
oess_1::stdsn::ient_std_t, oess_1::stdsn::oent_std_t.

\subsection oess_1_2_0__extensible_types__version_problem__explicit_extensibility Необходимость явной декларации расширяемости

Как было сказано выше, в ObjESSty существует возможность использовать
один и тот же вспомогательный код для формирования принципиально
разных представлений объектов. Теговые представления не нуждаются
в явной декларации расширяемости типа. Безтеговые представления, строго
говоря так же не нуждаются в этом, т.к. вполне можно реализовать такие
схемы сериализации, которые в любом случае помещают в сериализованное
представление признак наличия/существования расширения объекта.

Но в oess v.1.2.0 требуется, чтобы расширяемые типы были явно отмечены
как расширяемые. Сделано это, главным образом, по двум причинам:
- знание того, что объект не является расширяемым, позволяет реализовать
сериализацию/десериализацию объекта более эффективно. Как по скорости,
так и по объему сериализованного представления;
- сериализованные с помощью oess v.1.2.0 объекты должны быть
интероперабельны с сериализованными предыдущими версиями ObjESSty
объектами, в которых расширений не было. Т.е. двоичное представление
не расширяемого объекта полученное с помощью
oess_1::stdsn::oent_no_markers_t в версии 1.2.0 должно быть идентично
представлению того же объекта, полученного с помощью того же класса
в версии, например, 1.1.4.

\section oess_1_2_0__extensible_types__cpp_decl Описание расширяемых типов на C++

На уровне языка C++ нет никакой разницы между расширяемыми и не
расширяемыми типами. Расширение типа заключается в том, что создается
новая версия C++ описания типа, в которое включаются новые атрибуты.
Например:

\par  Версия #1
\code
class	handshake_t : public oess_1::stdsn::serializable_t
{
	OESS_SERIALIZER( handshake_t )
	private :
		// Сериализуемые атрибуты.
		unsigned int	m_version;
		std::string	m_client_id;
	...
};
\endcode

\par Версия #2
	Добавляется описание механизма компрессии и подписи данных.
\code
// Класс добавлен в версии #2.
class	signature_setup_t : public oess_1::stdsn::serializable_t
{
	OESS_SERIALIZER( signature_setup_t )
	private :
		std::set< std::string >	m_supported;
		std::string	m_preferred;
	...
	public :
		const std::string	preferred_method;
};
// Класс добавлен в версии #2.
class	compression_setup_t : public oess_1::stdsn::serializable_t
{
	OESS_SERIALIZER( compression_setup_t )
	private :
		std::set< std::string >	m_supported;
		std::string	m_preferred;
		unsigned char	m_level;
	...
	public :
		const std::string	preferred_method;
		const unsigned char	preferred_level;
};
// Вторая версия класса.
class	handshake_t : public oess_1::stdsn::serializable_t
{
	OESS_SERIALIZER( handshake_t )
	private :
		// Сериализуемые атрибуты из первой версии ДОЛЖНЫ
		// остаться в неизменном виде.
		unsigned int	m_version;
		std::string	m_client_id;

		// Атрибуты из версии #2.
		signature_setup_t	m_signature;
		compression_setup_t	m_compression;
	...
};
\endcode

\par Версия #3
	Добавляется механизм передачи дополнительных свойств (traits),
	список которых предполагается дополнять в будущем.
\code
// Класс добавлен в версии #3.
class	handshake_trait_t : public oess_1::stdsn::serializable_t
{
	OESS_SERIALIZER( handshake_trait_t )
	public :
		virtual int
		id() const = 0;
	...
};
// Класс добавлен в версии #3.
class	handshake_trait_shptr_t : public oess_1::stdsn::shptr_t
{
	OESS_SERIALIZER( handshake_trait_shptr_t )
	OESS_1_SHPTR_IFACE( handshake_trait_shptr_t,
		handshake_trait_t,
		oess_1::stdsn::shptr_t )
};
// Третья версия класса.
class	handshake_t : public oess_1::stdsn::serializable_t
{
	OESS_SERIALIZER( handshake_t )
	private :
		// Сериализуемые атрибуты из первой версии ДОЛЖНЫ
		// остаться в неизменном виде.
		unsigned int	m_version;
		std::string	m_client_id;

		// Атрибуты из версии #2 ДОЛЖНЫ остаться в неизменном виде.
		signature_setup_t	m_signature;
		compression_setup_t	m_compression;

		// Атрибуты из версии #3.
		std::map< int, handshake_trait_shptr_t >	m_traits;
	...
};
\endcode

\section oess_1_2_0__extensible_types__ddl_decl Описание расширяемых типов на DDL

В описании на DDL существуют следующие правила:
- расширяемый тип должен быть помечен тегом <i>{extensible}</i>. Эта отметка
должна быть указана в самой первой версии типа;
- атрибуты расширения должны указываться внутри тега <i>{extension ...}</i>.
В первой версии типа тега <i>{extension}</i> нет. Во второй версии типа внутри
<i>{extension}</i> описываются добавленные во вторую версию атрибуты.
В третьей версии внутри первого тега <i>{extension}</i> должен быть
указан еще один тег <i>{extension}</i> в котором перечисляются атрибуты
третьей версии. И т.д.;
- внутри описания расширяемого типа может быть только один тег
<i>{extension}</i>;
- внутри тега <i>{extension}</i> может быть только один тег <i>{extension}</i>;
- глубина вложенности тегов <i>{extension}</i> друг в друга не ограничена.

\note В версии 1.2.0 не накладывается ограничений на то, где должен
располагаться тег <i>{extension}</i> - до списка атрибутов, среди описаний
атрибутов или после атрибутов. Но <b>рекомендуется</b> указывать тег
<i>{extension}</i> последним тегом своего родительского тега (т.е.
указывать его всегда после списка атрибутов). Т.к. возможно, что в будущих
версиях ObjESSty расположение тега <i>{extension}</i> будет иметь
важное значение.

Приведенный в предыдущей секции пример записывается на DDL
следующим образом:

\par  Версия #1
\verbatim
|| Первая версия расширяемого типа.
{type {extensible}	handshake_t
	{attr	m_version {of oess_1::uint_t}}
	{attr	m_client_id {of std::string}}
}
\endverbatim

\par Версия #2
	Добавляется описание механизма компрессии и подписи данных.
\verbatim
|| Класс добавлен в версии #2.
{type {extensible}	signature_setup_t
	{attr	m_supported {stl-set} {of std::string}
		|| Атрибут не обязательный.
		{default {c++ std::set< std::string >()}
			|| Сериализуется только, если в нем есть значения.
			{present_if {c++ m_supported.size()}}
		}
	}
	{attr	m_preferred {of std::string}
		|| Атрибут не обязательный.
		{default {c++ preferred_method}
			|| Сериализуется только, если заданы алгоритмы подписи
			|| и значение отлично от значения по умолчанию.
			{present_if {c++ m_supported.size() &&
				preferred_method != m_preferred}}
		}
	}
}
|| Класс добавлен в версии #2.
{type {extensible}	compression_setup_t
	{attr	m_supported {stl-set} {of std::string}
		|| Атрибут не обязательный.
		{default {c++ std::set< std::string >()}
			|| Сериализуется только, если в нем есть значения.
			{present_if {c++ m_supported.size()}}
		}
	}
	{attr	m_preferred {of std::string}
		|| Атрибут не обязательный.
		{default {c++ preferred_method}
			|| Сериализуется только, если заданы алгоритмы подписи
			|| и значение отлично от значения по умолчанию.
			{present_if {c++ m_supported.size() &&
				preferred_method != m_preferred}}
		}
	}
	{attr	m_level {of oess_1::uchar_t}}
}
|| Вторая версия класса.
{type {extensible}	handshake_t
	{attr	m_version {of oess_1::uint_t}}
	{attr	m_client_id {of std::string}}

	|| Описание расширения версии #2.
	{extension
		{attr	m_signature {of signature_setup_t}
			{default {c++ signature_setup_t()}}
		}
		{attr	m_compression {of compression_setup_t}
			{default {c++ compression_setup_t()}}
		}
	}
}
\endverbatim

\par Версия #3
	Добавляется механизм передачи дополнительных свойств (traits),
	список которых предполагается дополнять в будущем.
\verbatim
|| Класс добавлен в версии #3.
{type {abstract}	handshake_trait_t}
|| Класс добавлен в версии #3.
{type	handshake_trait_shptr_t
	{super oess_1::stdsn::shptr_t}
}
|| Третья версия класса.
{type {extensible}	handshake_t
	{attr	m_version {of oess_1::uint_t}}
	{attr	m_client_id {of std::string}}

	|| Описание расширения версии #2.
	{extension
		{attr	m_signature {of signature_setup_t}
			{default {c++ signature_setup_t()}}
		}
		{attr	m_compression {of compression_setup_t}
			{default {c++ compression_setup_t()}}
		}

		|| Описание расширения версии #3.
		{extension
			{attr	m_traits
				{stl-map {key oess_1::int_t}}
				{of handshake_trait_shptr_t}
				{default {c++ std::map< int,
					handshake_trait_shptr_t >()}
					{present_if {c++ m_traits.size()}}
				}
			}
		}
	}
}
\endverbatim

\subsection oess_1_2_0__extensible_types__ddl_decl__default_values Значения по-умолчанию для атрибутах в расширениях

При описании в DDL атрибутов в расширениях очень важно указывать
значение по-умолчанию для каждого такого атрибута. Дело в том, что при
десериализации объекта ObjESSty назначает значения только тем атрибутам,
которые были обнаружены в сериализованном представлении. Остальные
атрибуты остаются без изменения. Это может стать проблемой, например,
в таком случае: объекту \a A было присвоено какое-то значение. Затем в
объект \a A была осуществлена десериализация некоторого двоичного
представления. Если двоичное представление было создано предыдущей
версией, то в объекте \a A часть атрибутов получат новые значения, но
атрибуты из последнего расширения изменены не будут. Т.е. объект \a A
не будет корректным образом объекта из двоичного представления,
поскольку
отсутствовавшие в этом представлении атрибуты не получили значений
по-умолчанию.

\section oess_1_2_0__extensible_types__which Какие типы нужно делать расширяемыми

ObjESSty требует, чтобы возможность расширения была заложена в самой
первой версии описания типа. Если этого не сделать, то двоичные
представления объектов разных версий одного типа, полученных с
помощью oess_1::stdsn::oent_no_markers_t будут несовместимы друг
с другом. Поэтому важно, чтобы при создании самой первой версии
спецификации типа было известно, допускает ли тип расширения или нет.

Практика показывает, что может возникнуть необходимость расширить
практически любой тип. Даже такой, который, казалось бы, не может быть
расширен. Поэтому можно рекомендовать делать <b>все</b> типы
расширяемыми, с учетом следующих исключений:
- типы "разделяемых указателей", производных от oess_1::stdsn::shptr_t;
- типы, которые описывают хорошо известные, устоявшиеся понятия.
Например, "прямоугольник", "точка", "отрезок" и т.п.

Так же в некоторых случаях можно оставлять не расширяемыми
абстрактные типы. Например, в приведенном выше примере класс
handshake_trait_t вряд-ли когда-нибудь будет иметь собственные атрибуты.
Зато производные от него типы с большой вероятностью будут
расширяемыми.

*/
