Что такое Indigo
Первые шаги Сервис с одной реализацией и несколькими контрактами Архитектура Indigo |
Примеры к статье: Пример1, Пример 2.
Indigo – это новая коммуникационная подсистема Windows, предназначенная для создания распределенных приложений. Основная задача Indigo – обеспечить взаимодействие частей распределенного приложения. Помимо этого она обеспечивает безопасность, транзакционность и надежность коммуникаций.
Серверная часть приложения, использующего Indigo, называется сервисом. Сервис, прежде всего, содержит функциональность, которую необходимо реализовать на сервере (реализацию). Кроме того, он может содержать метаданные, необходимые для его публикации, и контракта – абстрактного описания методов, параметров и возвращаемых значений точки доступа (endpoint). Взаимодействие с клиентом осуществляется посредством сообщений.
Простейший способ создания как реализации, так и контракта – это разметка обычного класса специальными атрибутами, ServiceContractAttribute и OperationContractAttribute. Первым нужно пометить класс, который является сервисом, вторым – методы, которые будут доступны публично.
ПРИМЕЧАНИЕ На первый взгляд это очень похоже на Web-сервисы. Однако есть различия. Прежде всего, в Indigo нет необходимости наследовать класс от специального класса. Необходимость такового признана архитектурной ошибкой, и вместо наследования предложен атрибут. Это повышает гибкость решений при построении распределенных приложений. Еще одно отличие – поддержка сессий теперь задается не для методов, а для сервиса целиком. Это больше соответствует принципам построения сервисов. |
Ниже приведена таблица, описывающая возможные способы хостинга сервисов, и те их особенности, на которые указывает Microsoft.
Способ хостинга | Сценарий применения | Ключевые особенности | Ограничения |
---|---|---|---|
IIS 5.1, IIS 6.0 | Работа сервисов в составе ASP.NET приложений.Работа сервисов в интернете.Работа сервиса на машине разработчика с установленным IIS. | Среда сервиса интегрирована со средой ASP.NET, следовательно, доступны все преимущества этой среды – пересоздание процессов при утечках памяти и критических сбоях, остановка сервиса при отсутствии обращений, мониторинг состояния процесса и активация по запросу извне. | Поддерживается только протокол HTTP. |
IIS 7.0 | Работа сервисов в составе ASP.NET-приложений.Работа сервисов в интернете.Работа сервиса на машине разработчика с установленным IIS. | Построен поверх WAS, так что обладает всеми его возможностями. Кроме того, доступно взаимодействие с ASP.NET приложениями. | В настоящий момент поддерживается только в Longhorn. |
Сервис Windows | Работа сервисов в качестве сервиса Windows, таким образом, что они могут быть сконфигурированы для запуска при старте ОС. | Сервис может быть запущен автоматически. Возможна работа со всеми поддерживаемыми Indigo протоколами в ОС Windows XP, Windows Server 2003, Windows “Longhorn”. | Возможности мониторинга и управления процессами ограничены возможностями используемой ОС. |
Windows Activation Services (WAS) | Работа сервиса в среде, где нет желания устанавливать IIS. | Обеспечивает все возможности, предоставляемые IIS без необходимости устанавливать Web-сервер.Поддерживает протоколы TCP, HTTP, IPC и MSMQ.Предоставляет возможность автоматической активации сервисов, специальным сервисом активации, наподобие того, как работает активация для приложений COM. | В настоящий момент поддерживается только в Longhorn. |
Отдельное приложение | Работа сервиса в среде, где нет желания устанавливать IIS. | Запускается вручную. Возможна работа со всеми поддерживаемыми Indigo протоколами в ОС Windows XP, Windows Server 2003, Windows “Longhorn”. |
ПРИМЕЧАНИЕ Однако все эти способы в итоге сводятся к использованию ServiceHost<T>, о котором мы поговорим ниже. |
Для начала попробуем создать простейшее приложение с использованием новой технологии. Пусть это будет сервис, возвращающий текущее время на сервере.
ПРИМЕЧАНИЕ Все описания и исходные коды соответствуют Microsoft WinFX SDK – "Avalon" and "Indigo" Community Technology Preview Edition (Март 2005). Версия сборок Indigo – 2.0.5110.20. |
Напишем класс, реализующий требуемую функциональность, и разметим его:
using System; using System.ServiceModel; namespace Service { /// <summary>/// Сервис, возвращающий время сервера./// </summary> [ServiceContract] publicclass TimeService { /// <summary>/// Возвращает время сервера./// </summary> [OperationContract] public DateTime GetServerTime() { Console.WriteLine("GetServerTime() called"); return DateTime.Now; } } } |
После того как мы создали реализацию, необходимо опубликовать сервис, чтобы он был доступен извне. Существует несколько способов сделать это, но в данном случае мы воспользуемся самым наглядным – хостингом сервиса в собственном приложении.
Для формирования среды хостинга служит generic-класс ServiceHost<T>. Ниже приведен пример программы, осуществляющей хостинг сервиса.
using System; using System.ServiceModel; namespace Service { class Program { staticvoid Main(string[] args) { // Создаем базовый адрес. Uri baseHttpAddress = new Uri("http://localhost:8080"); // Формируем среду исполнения сервисаusing (ServiceHost<TimeService> host = new ServiceHost<TimeService>(baseHttpAddress)) { // Создаем совместимый с веб-сервисом HTTP binding. BasicProfileBinding binding = new BasicProfileBinding(); // Добавляем точку доступа к сервису host.AddEndpoint(typeof(TimeService), binding, "/TimeService"); // Запускаем прослушивание host.Open(); // Выводим подсказку и останавливаем основной поток Console.WriteLine("Service is running. Press ENTER to exit..."); Console.ReadLine(); } } } } |
В первой строке мы создаем URI сервиса, по которому он будет доступен. Далее создаем экземпляр хоста. Следующим шагом идет создание т.н. связывания (binding), способа взаимодействия сервиса с клиентом. Связывание отвечает за протокол взаимодействия, и в какой-то мере является аналогом каналов (channels) в Remoting. В примере используется самое простое связывание, совместимое с Web-сервисами и обеспечивающее взаимодействие по протоколу HTTP с использованием SOAP.
После создания связывания создаем точку доступа к сервису (endpoint). Этой операции не было в Web-сервисах, поскольку такая точка у Web-сервиса могла быть только одна. В случае Indigo мы можем задать несколько точек, при этом выставив в разные точки разные интерфейсы (контракты) сервиса, и назначить им различные права доступа, URL, протоколы и т.п.
Наконец, вызовом метода Open() переводим хост-объект в режим ожидания вызова.
Теперь, если набрать в браузере адрес http://localhost:8080, мы должны получить такую страничку:
По завершению работы для освобождения ресурсов необходимо вызвать у хост-объекта метод Close() или использовать конструкцию using для автоматического вызова метода Dispose.
Помимо прямого задания параметров хост-объекта, связывания и точек входа, можно задавать их декларативно, примерно так же, как раньше это делалось в Remoting. Перепишем наш пример для демонстрации этого. Прежде всего в файле конфигурации приложения заполняем секцию конфигурации Indigo.
<?xmlversion="1.0"encoding="utf-8" ?> <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <appSettings> <add key="baseAddress" value="http://localhost:8080" /> </appSettings> <system.serviceModel> <services> <service serviceType="Service.TimeService"> <endpoint bindingSectionName="basicProfileBinding" contractType="Service.TimeService"/> </service> </services> </system.serviceModel> </configuration> |
Теперь код формирования точек доступа можно убрать. Итоговый текст программы будет выглядеть следующим образом:
using System; using System.Configuration; using System.ServiceModel; namespace Service { class Program { staticvoid Main(string[] args) { // Формируем базовый адрес из настроек приложения. Uri baseHttpAddress = new Uri(ConfigurationManager.AppSettings["baseAddress"]); // Создаем сервис и запускаем прослушивание using (ServiceHost<TimeService> host = new ServiceHost<TimeService>(baseHttpAddress)) { host.Open(); Console.WriteLine("Service is running. Press ENTER to exit..."); Console.ReadLine(); } } } } |
Обратите внимание на то, что, в отличие от Remoting, нет необходимости явно вызывать аналог RemotingServices.Configure. Конфигурирование производится автоматически, на основании типа сервиса.
Теперь нам необходимо написать клиентский код, использующий сервис. Поскольку использованное нами связывание совместимо с Web-сервисами, то можно просто использовать обычную Web-ссылку (web reference), однако мы используем родные средства Indigo.
Для начала, так же, как и в случае с Web-сервисами, необходимо создать прокси-классы, представляющие сервис на клиенте. Для создания таких классов существует утилита svcutil. Ее необходимо запустить, указав в качестве аргумента URL сервиса. Помимо URL сервиса можно указать массу дополнительных параметров, как совпадающих с таковыми у wsdl, так и новыми (например, можно сделать генерируемые классы сериализуемыми).
В результате работы утилиты получаем такой исходный код:
Indigo прокси//------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. // Runtime Version:2.0.50110.28 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ [System.ServiceModel.ServiceContractAttribute()] publicinterface TimeService { [System.ServiceModel.OperationContractAttribute( Action = "http://tempuri.org/TimeService/GetServerTime", ReplyAction = "http://tempuri.org/TimeService/GetServerTimeResponse") ] [return: System.ServiceModel.MessageBodyAttribute( Name = "GetServerTimeResult", Namespace = "http://tempuri.org/") ] System.DateTime GetServerTime(); } publicinterface TimeServiceChannel : TimeService, System.ServiceModel.IProxyChannel { } public partial class TimeServiceProxy : System.ServiceModel.ProxyBase<TimeService>, TimeService { public TimeServiceProxy() { } public TimeServiceProxy(string configurationName) : base(configurationName) { } public TimeServiceProxy(System.ServiceModel.Binding binding) : base(binding) { } public TimeServiceProxy(System.ServiceModel.EndpointAddress address, System.ServiceModel.Binding binding) : base(address, binding) { } public System.DateTime GetServerTime() { returnbase.InnerProxy.GetServerTime(); } } |
Сравним результат с тем, что генерирует утилита wsdl для того же сервиса:
WSDL прокси//---------------------------------------------------------------------------- // <auto-generated> // This code was generated by a tool. // Runtime Version:2.0.50110.28 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //---------------------------------------------------------------------------- // // This source code was auto-generated by Microsoft.VSDesigner, // Version 2.0.50110.28. // namespace Client.TimeService { using System.Diagnostics; using System.Web.Services; using System.ComponentModel; using System.Web.Services.Protocols; using System; using System.Xml.Serialization; /// <remarks/> [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute( Name = "BasicProfileBinding_TimeService", Namespace="http://tempuri.org/Bindings") ] public partial class service : System.Web.Services.Protocols.SoapHttpClientProtocol { private System.Threading.SendOrPostCallback GetServerTimeOperationCompleted; /// <remarks/>public service() { this.Url = "http://localhost:8080/"; } /// <remarks/>publicevent GetServerTimeCompletedEventHandler GetServerTimeCompleted; /// <remarks/> [System.Web.Services.Protocols.SoapDocumentMethodAttribute( "http://tempuri.org/TimeService/GetServerTime", RequestNamespace = "http://tempuri.org/", ResponseNamespace = "http://tempuri.org/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped) ] public dateTime GetServerTime() { object[] results = this.Invoke("GetServerTime", newobject[0]); return ((dateTime)(results[0])); } /// <remarks/>publicvoid GetServerTimeAsync() { this.GetServerTimeAsync(null); } /// <remarks/>publicvoid GetServerTimeAsync(object userState) { if ((this.GetServerTimeOperationCompleted == null)) { this.GetServerTimeOperationCompleted = new System.Threading.SendOrPostCallback( this.OnGetServerTimeOperationCompleted); } this.InvokeAsync("GetServerTime", newobject[0], this.GetServerTimeOperationCompleted, userState); } privatevoid OnGetServerTimeOperationCompleted(object arg) { if ((this.GetServerTimeCompleted != null)) { System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); this.GetServerTimeCompleted(this, new GetServerTimeCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); } } /// <remarks/>publicnewvoid CancelAsync(object userState) { base.CancelAsync(userState); } } /// <remarks/> [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute( Namespace = "http://schemas.microsoft.com/2003/10/Serialization/") ] public partial class dateTime { privateint idField; privatebool idFieldSpecified; privateint refField; privatebool refFieldSpecified; private System.Xml.XmlQualifiedName[] baseTypesField; privatestring clrTypeField; privatestring clrAssemblyField; privatebool mustUnderstandField; privatebool mustUnderstandFieldSpecified; private System.Xml.XmlAttribute[] anyAttrField; private System.DateTime valueField; /// <remarks/> [System.Xml.Serialization.XmlAttributeAttribute( Form = System.Xml.Schema.XmlSchemaForm.Qualified) ] publicint Id { get { returnthis.idField; } set { this.idField = value; } } /// <remarks/> [System.Xml.Serialization.XmlIgnoreAttribute()] publicbool IdSpecified { get { returnthis.idFieldSpecified; } set { this.idFieldSpecified = value; } } /// <remarks/> [System.Xml.Serialization.XmlAttributeAttribute( Form = System.Xml.Schema.XmlSchemaForm.Qualified) ] publicint Ref { get { returnthis.refField; } set { this.refField = value; } } /// <remarks/> [System.Xml.Serialization.XmlIgnoreAttribute()] publicbool RefSpecified { get { returnthis.refFieldSpecified; } set { this.refFieldSpecified = value; } } /// <remarks/> [System.Xml.Serialization.XmlAttributeAttribute( Form = System.Xml.Schema.XmlSchemaForm.Qualified) ] public System.Xml.XmlQualifiedName[] BaseTypes { get { returnthis.baseTypesField; } set { this.baseTypesField = value; } } /// <remarks/> [System.Xml.Serialization.XmlAttributeAttribute( Form = System.Xml.Schema.XmlSchemaForm.Qualified) ] publicstring ClrType { get { returnthis.clrTypeField; } set { this.clrTypeField = value; } } /// <remarks/> [System.Xml.Serialization.XmlAttributeAttribute( Form = System.Xml.Schema.XmlSchemaForm.Qualified) ] publicstring ClrAssembly { get { returnthis.clrAssemblyField; } set { this.clrAssemblyField = value; } } /// <remarks/> [System.Xml.Serialization.XmlAttributeAttribute( Form = System.Xml.Schema.XmlSchemaForm.Qualified) ] publicbool MustUnderstand { get { returnthis.mustUnderstandField; } set { this.mustUnderstandField = value; } } /// <remarks/> [System.Xml.Serialization.XmlIgnoreAttribute()] publicbool MustUnderstandSpecified { get { returnthis.mustUnderstandFieldSpecified; } set { this.mustUnderstandFieldSpecified = value; } } /// <remarks/> [System.Xml.Serialization.XmlAnyAttributeAttribute()] public System.Xml.XmlAttribute[] AnyAttr { get { returnthis.anyAttrField; } set { this.anyAttrField = value; } } /// <remarks/> [System.Xml.Serialization.XmlTextAttribute()] public System.DateTime Value { get { returnthis.valueField; } set { this.valueField = value; } } } /// <remarks/>publicdelegatevoid GetServerTimeCompletedEventHandler( object sender, GetServerTimeCompletedEventArgs args); /// <remarks/>public partial class GetServerTimeCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { privateobject[] results; internal GetServerTimeCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : base(exception, cancelled, userState) { this.results = results; } /// <remarks/>public dateTime Result { get { this.RaiseExceptionIfNecessary(); return ((dateTime)(this.results[0])); } } } } |
Очевидно, прокси-класс Indigo намного компактнее. Прежде всего потому что не произошло формирования прокси-класса для аргументов. Вместо фиктивного dateTime в случае WSDL в Indigo мы видим полноценный System.DateTime. Базовый класс теперь представляет собой generic-класс, поэтому нет никаких приведений при обращении к его методам и свойствам, а сам прокси-класс обзавелся типизированным интерфейсом (это позволит использовать несколько разных прокси-классов с одинаковыми контрактами). Нет массы асинхронных методов – функциональность асинхронных вызовов вынесена наружу. Наконец, вместо конструктора без параметров и жесткой привязки к конкретному URL мы имеем несколько перегруженных конструкторов, в параметрах которых можно передать требуемое связывание и точку доступа.
Можно еще заметить интерфейс TimeServiceChannel. Он предназначен для управления запросами и сессиями на клиенте.
Осталось только воспользоваться полученным кодом. Работать с сервисами Indigo так же просто, как и с веб-сервисами. Единственное отличие, видное сразу – экземпляры прокси-классов необходимо явно уничтожать.
Набросаем простую форму, содержащую кнопку вызова серверного метода и TextBox для вывода результатов.
Теперь напишем обработчик нажатия кнопки.
private void _getServerTimeButton_Click(object sender, EventArgs e) { using (TimeServiceProxy proxy = new TimeServiceProxy( new EndpointAddress("http://localhost:8080/"), new BasicProfileBinding())) _serverTimeBox.Text = proxy.GetServerTime().ToString(); } |
Код предельно прост – создаем экземляр прокси-класса, передав на вход адрес сервиса и связывание, после этого вызываем метод.
Запускаем и убеждаемся, что все работает. Простейшее Indigo-приложение создано.
Конфигурацию клиентского прокси-объекта также можно сделать декларативно. В описываемом примере достаточно добавить конфигурационный файл со следующим содержимым:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.serviceModel> <client> <endpoint address="http://localhost:8080" bindingSectionName="basicProfileBinding" contractType="TimeService" /> </client> </system.serviceModel> </configuration> |
В этом случае передавать параметры в конструктор прокси-класса не нужно.
Теперь усложним задачу. Создадим сервис, у которого одной реализации соответствуют несколько контрактов. Возьмем за основу сервис из предыдущего раздела и добавим дополнительный контракт для управления самим сервисом.
Для начала отделим контракт от реализации. Опишем его в виде отдельного интерфейса.
using System; using System.ServiceModel; namespace Service { [ServiceContract] interface ITimeService { [OperationContract] DateTime GetServerTime(); } } |
Теперь опишем интерфейс управления.
using System; using System.ServiceModel; namespace Service { [ServiceContract] publicinterface ITimeServiceController { [OperationContract] void StopServer(); } } |
Наконец, реализация.
using System; using System.ServiceModel; namespace Service { /// <summary>/// Сервис, возвращающий время сервера./// </summary>internalclass TimeService : ITimeService, ITimeServiceController { /// <summary>/// Возвращает время сервера./// </summary>public DateTime GetServerTime() { Console.WriteLine("GetServerTime() called"); return DateTime.Now; } /// <summary>/// Останавливает сервер./// </summary>publicvoid StopServer() { Console.WriteLine("StopServer() called"); Program.ServerRunEvent.Set(); } } } |
Обратите внимание на то, что разметка перекочевала в интерфейсы, а реализация получила модификатор доступа internal. Такая механика позволяет спрятать реализацию внутри сборки – возможность, раньше доступная только Remoting и недоступная Web-сервисам.
Управление работой сервера осуществляется объектом синхронизации "событие".
Ниже приведен декларативный вариант описания точек доступа.
<system.serviceModel> <services> <service serviceType="Service.TimeService"> <endpoint address="" bindingSectionName="basicProfileBinding" contractType="Service.ITimeService"/> <endpoint address="controller" bindingSectionName="basicProfileBinding" contractType="Service.ITimeServiceController"/> </service> </services> <system.serviceModel> |
Точек теперь две и у них разные типы контрактов.
В запускающий код сервера добавлено управление событием.
using System; using System.Configuration; using System.ServiceModel; using System.Threading; namespace Service { class Program { internalstatic ManualResetEvent ServerRunEvent; staticvoid Main(string[] args) { Uri baseHttpAddress = new Uri( ConfigurationManager.AppSettings["baseAddress"]); using (ServiceHost<TimeService> host = new ServiceHost<TimeService>(baseHttpAddress)) { ServerRunEvent = new ManualResetEvent(false); host.Open(); Console.WriteLine("Service is running ..."); ServerRunEvent.WaitOne(); // Подождем отработки последнего вызова Thread.Sleep(300); } } } } |
Thread.Sleep в конце нужен для того, чтобы сервис успел отработать все текущие запросы.
На клиенте создаем новые прокси и вносим соответствующие правки в конфигурацию.
<system.serviceModel> <client> <endpoint address="http://localhost:8080" bindingSectionName="basicProfileBinding" contractType="ITimeService" /> <endpoint address="http://localhost:8080/controller" bindingSectionName="basicProfileBinding" contractType="ITimeServiceController" /> </client> </system.serviceModel> |
Запускаем и убеждаемся, что все работает по-прежнему. Теперь в обработчик события нажатия кнопки Exit добавляем завершение работы сервера.
private void _exitButton_Click(object sender, EventArgs e) { using (TimeServiceControllerProxy proxy = new TimeServiceControllerProxy()) proxy.StopServer(); Close(); } |
Запускаем и убеждаемся, что при нажатии кнопки Exit сервер завершает работу.
После того как мы попробовали создать простейшее распределенное приложение, попытаемся немного подробнее разобраться с тем, что же все таки представляет собой новая система коммуникаций.
Прежде всего, следует отметить, что Indigo не создавалась на пустом месте. В ее основе лежат более старые технологии – COM+, MSMQ, Remoting и Web-сервисы ASP.NET. Была предпринята попытка взять от этих технологий самое лучшее, и на полученной основе создать единый фреймворк для создания распределенных приложений.
Кроме того, Indigo была спроектирована в расчете на взаимодействие не только с новыми приложениями, но и со старыми. Для этого в нее добавлена поддержка большого количества транспортных протоколов (HTTP, TCP, UDP, IPC); стандартные механизмы аутентификации и шифрования; различные топологии взаимодействия (клиент-сервер, P2P, издатель-подписчик): поддержка транзакций.
В качестве основы построения распределенных приложений Indigo предлагает сервис-ориентированную архитектуру (SOA). Остановимся на ней поподробнее.
Ориентация на сервисы – это подход к построению распределенных приложений, служащий дополнением к ООП и компонентному программированию. Если в ООП строительным блоком приложения служит класс, в SOA таковым является автономный сервис. Сервис – это программа, которая способна обмениваться с внешним миром сообщениями. Набор развернутых сервисов образует распределенную систему. Сервисы и клиенты должны быть спроектированы таким образом, чтобы добавление новых сервисов не сказалось на функционировании остальной части системы.
Microsoft выделяет 4 основных принципа построения сервис-ориентированных систем:
1. Границы отражены явно. Современные распределенные системы географически могут быть расположены чрезвычайно широко. Скорость и надежность каналов коммуникаций между частями может сильно различаться. Поэтому сервис-ориентированное приложение должно явно выделять процесс преодоления границ между частями системы, вместо того чтобы делать это неявно при вызове метода. Это позволяет избежать проблем, связанных с нестабильностью и низкой скоростью глобальных каналов коммуникаций.
Идеология явного выделения границ коммуникации распространяется не только на различные сервисы различных поставщиков, но и на разработку нескольких сервисов внутри одной компании, поскольку в ходе дальнейшего развития системы может понадобиться использовать эти сервисы удаленно. Упрощение и универсализация сервисов в сервис-ориентированной архитектуре – это жизненно важный момент для создания надежных и легко изменяемых SOA-приложений.
2. Сервисы автономны. Поскольку части распределенной системы разнесены очень широко, невозможно гарантировать, что все ее части будут работоспособны, иметь одинаковые версии и т.п. Поэтому при проектировании таких приложений необходимо рассматривать как штатную ситуацию отсутствие некоторых сервисов, сбои в каналах связи или неполные совпадения контрактов. Кроме того, поскольку внутрисистемные коммуникации могут проходить по открытым каналам связи, необходимы аутентификация, шифрование и проверка целостности межсервисных сообщений.
3. Сервисы описываются схемой и контрактом, а не классами. Необходимость сохранения работоспособности системы при смене версии сервиса выдвигает дополнительные требования к гибкости описания контракта сервиса. Описание, сходное с ОО-интерфейсами, для сервисов не годится, поскольку не обеспечивает должного уровня совместимости. Поэтому в SOA следует использовать гибкий формат сообщений, снабженный машинно-проверяемыми схемами, а также возможностью передавать дополнительную информацию, не нарушая структуры основной информации, чтобы обеспечить работоспособность уже использующегося кода.
4. Совместимость сервисов определена и основана на политике совместимости. В классическом ООП на совместимость частей кода влияет как структура публичных интерфейсов кода, так и семантика действий, выполняемых этим кодом. В SOA эти два аспекта совместимости объединены в один. Структурная совместимость обеспечивается машинным контролем формата сообщений на соответствие схеме, семантическая – политикой совместимости.
Политика совместимости представляет собой набор машинно-читаемых выражений. Выражения описывают условия и гарантии (называемые утверждениями, assertion), которые должны быть обеспечены для нормального функционирования сервиса. Утверждения политики идентифицируются стабильным глобально уникальным идентификатором, вне зависимости от того, к какому сервису они применяются. Утверждения должно быть достаточно формальными, чтобы обеспечить простую механику их проверки.
Модель сервисов предназначена для легкого использования Indigo в CLR-коде. Она представляет собой набор атрибутов, предназначенных для разметки контрактов сервиса. Поддерживаются два типа контракта: контракт сервиса и контракт данных.
Контракт сервиса описывает способ, которым можно взаимодействовать с сервисом. Для создания контракта сервиса достаточно применить атрибут ServiceContractAttribute к публичному типу, описывающему сервис. Это может быть как реализация сервиса (как в первом примере), так и отдельный интерфейс (как во втором).
Контракт данных описывает структуру сообщения. Этот вид контракта аналогичен XML-схеме, и описывает способ преобразования CLR-типов в сообщения. Для создания контракта данных достаточно пометить тип атрибутом DataContractAttribute, а его поля или свойства, являющиеся частью контракта, атрибутом DataMemberAttribute.
Основной задачей модели сервисов является обеспечение связи между контрактом и пользовательским кодом. Эта задача решается атрибутом OperationContractAttribute. Методы, помеченные этим атрибутом, являются обработчиками определенного типа сообщений. В приведенных выше примерах мы использовали этот атрибут для пометки методов, которые потом вызывали удаленно. Обработчики могут быть как однонаправленными, так и работающими в режиме запрос-ответ, что определяется значением свойства IsOneWay атрибута. В процессе работы сообщение преобразовывается в типизированный набор параметров, а возвращаемое значение преобразовывается в ответное сообщение.
Еще одной задачей, которую выполняет модель сервисов, является поддержание контекста работы. В понятие контекста входят сессии, поддержка контекста вызова, поддержка транзакций.
Наконец, модель сервисов Indigo обеспечивает декларативный механизм связывания сервисов и контроля безопасности взаимодействия. В примерах использовалось связывание, обеспечивающее взаимодействие в стиле Web-сервисов.
Indigo содержит несколько сервисов, предназначенных для упрощения реализации отдельных аспектов распределенных систем. Например, для обеспечения сложных схем авторизации с установлением доверительных отношений между различными сервисами и обменом идентификационной информацией (identity federation). В будущих версиях Windows будет использоваться подобная схема, основанная на WS-Federation (протокол обмена идентификационной информацией между Web-сервисами).
Indigo также обеспечивает значительную поддержку программирования с использованием транзакций. Для поддержки транзакций может использоваться соответствующий протокол Web-сервисов (WS-AtomicTransactions), либо новый фреймворк, интегрированный с Windows – System.Transaction. Этот фреймворк обеспечивает взаимодействие с существующими системами транзакций (DTC, WS-AtomicTransactions), предоставляет облегченный менеджер транзакций, хранящий состояние транзакции в памяти и не поддерживающий постоянные хранилища, позволяет легко создавать собственные менеджеры ресурсов в управляемом коде. В ОС Windows Longhorn, кроме того, добавится поддержка нового менеджера транзакций, интегрированного в ядро (KTM, KernelTransaction Manager) и транзакционного режима работы NTFS (TxNTFS).
Для создания ориентированных на сообщения приложений существует также набор реализаций основных абстракций, используемых для этого. Indigo обеспечивает набор простых, но расширяемых очередей, событий и механизмов их роутинга.