Управление версиями службы
После первоначального развертывания служб и, возможно, несколько раз на протяжении времени их существования возникает потребность в изменении этих служб (и предоставляемых ими конечных точек). Причин тому немало: изменяющиеся потребности бизнеса, требования информационных технологий или необходимость решить какие-либо другие проблемы. В результате каждого изменения создается новая версия службы. В этом разделе объясняется, как рассмотреть возможность управления версиями в Windows Communication Foundation (WCF).
Четыре категории изменений службы
Все изменения, которые может потребоваться внести в службы, можно разделить на четыре категории:
Изменения контракта: например, можно добавить операцию, добавить или изменить элемент данных в сообщении.
Изменения адреса: например, служба меняет местоположение так, что конечные точки получают новые адреса.
Изменения привязки: например, изменяется механизм безопасности или его параметры.
Изменения реализации: например, изменяется внутренняя реализация метода.
Некоторые из этих изменений называются "критическими" и другими являются "неразрывными". Изменение неразрывно, если все сообщения, которые были успешно обработаны в предыдущей версии, успешно обрабатываются в новой версии. Любое изменение, которое не соответствует такому критерию, является критическим изменением.
Ориентация службы и управление версиями
Одним из принципов ориентации службы является автономность (или независимость) служб и клиентов. Кроме всего прочего, из-за этого разработчики службы не имеют оснований предполагать, что они управляют всеми клиентами службы или хотя бы знают о существовании всех клиентов. Это делает невозможным повторное создание и развертывание всех клиентов при изменении версии службы. Материал данного раздела предполагает, что служба соответствует этому принципу и, следовательно, должна быть изменена (или создана новая версия) независимо от клиентов.
Если потребность в критическом изменении возникает неожиданно и ее невозможно устранить, может быть принято решение о пропуске этого принципа приложением, которое, кроме того, потребует повторного создания и развертывания клиентов вместе с новой версией службы.
Управление версиями контракта
Клиент и служба не обязательно должны использовать один и тот же контракт; достаточно, чтобы контракты были совместимы.
Для контрактов службы совместимость предполагает возможность добавления новых операций, предоставленных службой, и невозможность удаления или семантического изменения существующих операций.
Для контрактов данных совместимость предполагает возможность добавления новых определений типов схем и невозможность внесения критических изменений в существующие определения типов схем. К критическим изменениям может относиться удаление членов данных или несовместимое изменение типов данных этих членов. Эта возможность предоставляет службе определенную свободу при изменении версии контрактов, позволяя не нарушать работу клиентов. В следующих двух разделах описываются неразрывные и критические изменения, которые можно вносить в данные WCF и контракты служб.
Управление версиями контракта данных
В этом разделе описывается управление версиями данных при использовании классов DataContractSerializer и DataContractAttribute.
Строгое управление версиями
Во многих сценариях, если изменение версий сопряжено с какими-либо проблемами, разработчик службы не имеет возможности управлять клиентами и поэтому не может делать предположения о том, как они отреагируют на изменения в сообщении XML или схеме. В этих случаях необходимо гарантировать прохождение новыми сообщениями проверки на соответствие старой схеме по двум причинам.
Старые клиенты разработаны в предположении, что схема не изменится. Поэтому возможно, эти клиенты не смогут обработать сообщения, для работы с которыми они не были предназначены.
В действительности, старые клиенты могут выполнить проверку схемы на соответствие старой схеме еще до того, как будет предпринята попытка обработать сообщения.
В таких случаях рекомендуется обрабатывать существующие контракты данных как неизменяемые и создавать новые контракты с уникальными полными именами XML. Затем разработчик службы либо добавляет в существующий контракт службы новые методы, либо создает новый контракт службы с методами, которые используют новый контракт данных.
Очень часто разработчику службы необходимо создать определенную бизнес-логику, которая должна выполняться во всех версиях контракта данных, а также индивидуальный бизнес-код для каждой версии контракта данных. В приложении в конце этого раздела объясняется, как с этой целью можно использовать интерфейсы.
Нестрогое управление версиями
Во многих других сценариях разработчик службы может предположить, что добавление в контракт данных нового дополнительного члена не нарушит работу существующих клиентов. В этом случае разработчику службы необходимо выяснить, проходят ли существующие клиенты проверку схемы и игнорируют ли они неизвестные члены данных. В таких сценариях во избежание внесения критических изменений для добавления новых членов можно воспользоваться функциями контракта данных. Если возможности контракта данных для управления версиями уже использовались для создания первой версии службы, разработчик службы не может с уверенностью делать подобные предположения.
WCF, ASP.NET веб-службы и многие другие стеки веб-служб поддерживают нерекоменданное управление версиями: то есть они не вызывают исключений для новых неизвестных элементов данных в полученных данных.
Очень легко может возникнуть ошибочное предположение, что добавление нового члена не нарушит работу существующих клиентов. Если есть сомнения, могут ли все клиенты бесперебойно функционировать в условиях нестрогого управления версиями, рекомендуется руководствоваться принципами строгого управления версиями и обрабатывать контракты данных как неизменяемые.
Подробные рекомендации по нестрогую и строгую версию контрактов данных см. в рекомендациях по управлению версиями контракта данных.
Отличия контракта данных от типов .Net.
Структура или класс .Net может проецироваться как контракт данных применением атрибута DataContractAttribute к классу. Не следует путать тип .NET и проекции его контракта данных. Несколько типов .NET могут иметь одну и ту же проекцию контракта данных. Это различие особенно полезно, так как позволяет изменять тип .NET и при этом сохранять проецированный контракт данных, тем самым обеспечивая совместимость с существующими клиентами даже в строгом смысле слова. Поддержание вышеупомянутого различия между типом .NET и контрактом данных обеспечивается выполнением следующих действий.
Задайте имя Name и пространство имен Namespace. Всегда следует указать имя и пространство имен контракта данных, чтобы предотвратить предоставление имени и пространства имен типа .NET в контракте. В этом случае даже если впоследствии будет принято решение об изменении пространства имен .NET или имени типа, контракт данных останется неизменным.
Укажите Name. Следует всегда задавать имена членов данных во избежание непреднамеренного раскрытия имени члена .NET в контракте. В этом случае даже если впоследствии будет принято решение об изменении имени члена .NET, контракт данных останется неизменным.
Изменение или удаление членов
Изменение имени или типа элемента данных или удаление элементов данных является критичным изменением, даже если разрешено нестрогое управление версиями. Если в этом есть необходимость, создайте новый контракт данных.
Если совместимость со службой чрезвычайно важна, следует рассмотреть возможность игнорирования неиспользованных членов данных в коде и оставить их на месте. При разделении члена данных на несколько членов можно оставить на месте существующий член и использовать его в качестве свойства, которое может выполнить необходимое разделение и повторное агрегирование для клиентов предыдущих версий (не обновленных до последней версии).
Аналогичным образом изменения в имени или пространстве имен контракта данных являются критическими изменениями.
Полная совместимость версий неизвестных данных
В некоторых сценариях возникает необходимость в обеспечении полной совместимости версий неизвестных данных, которые поступают от добавленных в новую версию членов. Например, служба новой версии отправляет клиенту старой версии данные с несколькими добавленными членами. При обработке сообщения клиент игнорирует добавленные члены, но при этом повторно отправляет службе новой версии те же данные, включая добавленные члены. В таких сценариях, как правило, обновление данных происходит при извлечении данных из службы, их изменении и возвращении.
Полная совместимость версий для определенного типа возможна только в том случае, если этот тип реализует интерфейс IExtensibleDataObject. Интерфейс имеет одно свойство ExtensionData, которое возвращает тип ExtensionDataObject. Это свойство используется для сохранения любых данных из будущих версий контракта данных, не известных текущей версии. Эти данные непрозрачны для клиента, но при сериализации экземпляра содержимое свойства ExtensionData записывается вместе с другими данными членов контракта данных.
Для обеспечения поддержки новых и неизвестных будущих членов необходимо, чтобы все типы реализовывали этот интерфейс.
Библиотеки контрактов данных
Могут существовать библиотеки контрактов данных, в которых контракт публикуется в центральном репозитории, а средства реализации службы и типа реализуют и предоставляют контракты данных из этого репозитория. В этом случае при публикации контракта данных в репозитории невозможно управлять создателями типов, которые реализуют контракт. Следовательно, отсутствует возможность изменения контракта после его публикации, что делает контракт фактически неизменяемым.
При использовании класса XmlSerializer
При использовании класса XmlSerializer действуют те же принципы управления версиями. Если существует потребность в строгом управлении версиями, контракты данных рекомендуется обрабатывать как неизменяемые и создавать для новых версий новые контракты данных с уникальными полными именами. Если есть уверенность, что можно использовать нестрогое управление версиями, можно добавлять в новые версии новые сериализуемые члены, однако изменять или удалять существующие члены нельзя.
Примечание.
Для поддержки полной совместимости версий неизвестных данных класс XmlSerializer использует атрибуты XmlAnyElementAttribute и XmlAnyAttributeAttribute
Управление версиями контракта сообщения
Управление версиями контракта сообщения весьма подобно управлению версиями контрактов данных. При необходимости строгого управления версиями вместо изменения тела сообщения следует создать новый контракт сообщения с уникальным полным именем. Если известно, что возможно нестрогое управление версиями, можно добавить в тело сообщения новые части, но при этом нельзя изменить или удалить существующие. Это правило справедливо и для контрактов сообщения в режиме "bare", и для контрактов в программе-оболочке.
Всегда (даже при строгом управлении версиями) можно добавлять заголовки сообщения. Флаг MustUnderstand может повлиять на управление версиями. Как правило, модель управления версиями для заголовков в WCF описана в спецификации SOAP.
Управление версиями контракта службы
Так же, как и управление версиями контракта данных, управление версиями контракта службы предполагает добавление, изменение и удаление операций.
Задание имени, пространства имен и макрокоманды
По умолчанию именем контракта службы является имя интерфейса. Его пространство имен по умолчанию — http://tempuri.org
это действие http://tempuri.org/contractname/methodname
каждой операции. Рекомендуется явно указать имя и пространство имен для контракта службы, а также действие для каждой операции, чтобы избежать использования http://tempuri.org
и предотвращения предоставления имен интерфейсов и методов в контракте службы.
Добавление параметров и операций
Добавление предоставленных службой операций является некритическим изменением, так как эти новые операции никак не влияют на работу существующих каналов.
Примечание.
Добавление операций в дуплексный контракт обратного вызова является критическим изменением.
Изменение параметра операции или возвращаемых типов
Как правило, изменение параметра или возвращаемых типов является критическим изменением, если новый тип не реализует тот же контракт данных, что и старый тип. Для внесения этого изменения необходимо добавить в контракт службы новую операцию или определить новый контракт службы.
Удаление операций
Удаление операций также является критическим изменением. Для внесения этого изменения необходимо определить новый контракт службы и отобразить его в новой конечной точке.
Контракты сбоя
Атрибут FaultContractAttribute позволяет разработчику контракта службы указывать сведения о сбоях, которые могут быть возвращены из операций контракта.
Список сбоев, описанных в контракте службы, не является исчерпывающим. В любой момент операция может вернуть сбои, не описанные в ее контракте. Поэтому изменение набора сбоев, описанных в контракте, не считается критическим изменением. В качестве примера можно привести добавление в контракт нового сбоя с помощью атрибута FaultContractAttribute или удаление из контракта существующего сбоя.
Библиотеки контрактов службы
Организации могут иметь библиотеки контрактов, в которых контракт публикуется в центральном репозитории, а средства реализации службы реализуют контракты из этого репозитория. В этом случае при публикации контракта службы в репозитории невозможно управлять создателями служб, которые реализуют контракт. Следовательно, невозможно изменить контракт службы после его публикации, что делает его фактически неизменяемым. WCF поддерживает наследование контракта, которое можно использовать для создания нового контракта, который расширяет существующие контракты. Чтобы воспользоваться этой функцией, необходимо определить новый интерфейс контракта службы, наследуемый от старого интерфейса контракта службы, а затем добавить методы в новый интерфейс. После этого требуется изменить службу, реализующую старый контракт, так, чтобы она реализовывала новый контракт, и изменить определение конечной точки старой версии так, чтобы в нем также использовался новый контракт. Для клиентов старой версии эта конечная точка будет по-прежнему отображать контракт старой версии; для клиентов новой версии - контракт новой версии.
Управление версиями адреса и привязки
Изменения адреса конечной точки и привязки являются критическими, если клиенты не в состоянии динамически обнаруживать новый адрес конечной точки или привязки. Одним из механизмов реализации этой возможности является использование реестра и шаблона вызова UDDI (Universal Discovery Description and Integration), когда клиент пытается связаться с конечной точкой и после сбоя запрашивает метаданные текущей конечной точки из хорошо известного реестра UDDI. Затем клиент использует адрес и привязку из этих метаданных для связи с конечной точкой. Если связь успешна, клиент кэширует информацию об адресе и привязке для дальнейшего использования.
Служба маршрутизации и управление версиями
Если внесенные в службу изменения являются критическими и необходимо наличие двух или нескольких версий службы, запущенных одновременно, можно воспользоваться службой маршрутизации WCF для направления сообщений соответствующему экземпляру службы. Служба маршрутизации WCF использует маршрутизацию на основе содержимого, то есть на основе информации в самом сообщении, чтобы определить, куда направить это сообщение. Дополнительные сведения о службе маршрутизации WCF см. в разделе "Служба маршрутизации". Пример использования службы маршрутизации WCF для управления версиями служб см. в статье "Практическое руководство. Управление версиями служб".
Приложение
Общий принцип управления версиями контракта данных при строгом управлении заключается в том, что контракты данных необходимо обрабатывать как неизменяемые и создавать новые, если нужно внести изменения. Так как для каждого нового контракта данных требуется создать новый класс, необходим механизм, благодаря которому удастся избежать использования существующего кода, созданного в терминах старого класса контракта данных, и переписывания этого кода в терминах нового класса контракта данных.
Суть одного из таких механизмов заключается в использовании интерфейсов для определения членов каждого контракта данных и записи внутреннего кода реализации в терминах интерфейсов, а не классов контракта данных, которые реализуют интерфейсы. В следующем примере кода для версии 1 службы показан интерфейс IPurchaseOrderV1
и PurchaseOrderV1
.
public interface IPurchaseOrderV1
{
string OrderId { get; set; }
string CustomerId { get; set; }
}
[DataContract(
Name = "PurchaseOrder",
Namespace = "http://examples.microsoft.com/WCF/2005/10/PurchaseOrder")]
public class PurchaseOrderV1 : IPurchaseOrderV1
{
[DataMember(...)]
public string OrderId {...}
[DataMember(...)]
public string CustomerId {...}
}
Хотя операции контракта службы будут записаны с точки зрения PurchaseOrderV1
, фактическая бизнес-логика будет иметь значение IPurchaseOrderV1
. Затем в версии 2 используется новый интерфейс IPurchaseOrderV2
и новый класс PurchaseOrderV2
, как показано в следующем примере кода.
public interface IPurchaseOrderV2
{
DateTime OrderDate { get; set; }
}
[DataContract(
Name = "PurchaseOrder",
Namespace = "http://examples.microsoft.com/WCF/2006/02/PurchaseOrder")]
public class PurchaseOrderV2 : IPurchaseOrderV1, IPurchaseOrderV2
{
[DataMember(...)]
public string OrderId {...}
[DataMember(...)]
public string CustomerId {...}
[DataMember(...)]
public DateTime OrderDate { ... }
}
Контракт службы обновляется с включением новых операций, созданных в терминах PurchaseOrderV2
. Существующая бизнес-логика, созданная в терминах IPurchaseOrderV1
, продолжает работать для PurchaseOrderV2
, а новая бизнес-логика, требующая свойство OrderDate
, создается в терминах IPurchaseOrderV2
.