четверг, 24 февраля 2011 г.

Что такое WCF?

Недавно на rsdn.ru был задан интересный вопрос, что такое WCF? Вопрос весьма интересный, на который ответить в форуме достаточно сложно, но я все же постарался это сделать, а здесь я хочу привести несколько облагороженный вариант своего ответа.

Общие сведения

Если в двух словах, то WCF (a.k.a. Windows Communication Foundation) — это очередной фреймворк для построения распределенных приложений и межпроцессного взаимодействия, который является логическим развитием предыдущих подобных технологий компании Майкрософт, в частности Веб-сервисов, .Net Remoting и DCOM. И если предшественники были заточены на выполнение какого-то конкретного круга задач, то WCF - это скорее мультипарадигменная технология, вобравшая в себе все лучшее от своих предшественников, добавив при этом, конечно же, кое-каких собственных проблем.

Существенным отличием WCF от .Net Remoting является то, что WCF — это, прежде всего, технология для построения сервис-ориентированной архитектуры приложений (SOA — Service-Oriented Architecture), что позволяет абстрагироваться от конкретной технологи, на которой этот сервис реализован и пользоваться им из других приложений, написанных на любой другой платформе, языке, технологии; главное, чтобы реализация клиента отвечала определенным правилам. Кроме того, логика самого сервиса и его реализация полностью отделена от коммуникационной составляющей, и мы можем декларативно изменять способ взаимодействия с сервисом путем изменения конфигурационного файла. Мы можем изменить протокол взаимодействия, адрес, настроить максимальное количество подключений, ограничить размер пакетов и тайм-аут подключения к сервису, тайм-аут выполнения операции, надежность доставки и многое другое.

Нужно отметить, что подобное декларативная настройка присутствовала и в .Net Remoting, там мы тоже могли без перекомпиляции приложения изменить протокол взаимодействия и многие другие параметры. Главное отличие WCF от своего главного предшественника (да, я все о том же .Net Remoting) заключается в том, что WCF «не выносит свой сор из избы» и не показывает наружу никакие платформенно зависимые детали реализации сервиса, такие как сборки, конкретные классы сервиса, типы аргументов или исключения. Вместо этого сервис представляет собой группу операций, определенных в некотором интерфейсе, которые получают некоторые абстрактные входные/выходные параметры; все это дело описывается с помощью WSDL (Web Service Description Language) и может быть выставлено наружу через, так называемые mex-endpoints (Metadata Exchange Endpoints). Это позволяет получить «метаданные» сервиса подключившись к этому интерфейсу, получить описание сервиса и всех его операций и сгенерировать соответствующий прокси класс для заданного языка или платформы. Именно это позволяет написать сервис с помощью WCF, а использовать его из Java/Python/Ruby и чего угодно еще.

Именно эта сервис-ориентированность делает WCF во многих вопросах не похожей на обыкновенный механизм RPC, привычный многим по использованию .Net Remoting. Поскольку все описание сервиса мы можем получить через WSDL (включая типы, используемые для параметров метода), в WCF существует такое понятие как эквивалентность типов: два типа являются эквивалентными и могут использоваться в одной и той же операции сервиса, если они содержат одинаковое представление на уровне канала передачи данных. Таким образом, если речь идет о взаимодействии WCF-WCF, то на стороне клиента мы можем использовать тип Point с двумя свойствами X и Y, а на стороне сервиса использовать другой тип, определенный в другой сборке, с аналогичным именем, и аналогичными именами и типами свойств. При этом мы можем спокойно передавать на стороне клиента экземпляр одного типа, а на стороне сервиса получить экземпляр другого типа (и это совершенно естественно, помните, наш клиент и сервис могут быть написаны на разных платформах, так что, здесь главное, чтобы сервис и клиент могли понимать друг друга на уровне сообщений).

Прозрачность местоположения

В девяностых и начале двухтысячных годов была безумная мысль создать инфраструктуру, которая бы скрыла тот факт, что объект является удаленным. Так в DCOM и .Net Remoting было такое понятие как «прозрачность местоположения» (location transparency), которое означало, что вам, как разработчику, не стоит задумываться над тем, является ли объект, с которым вы работаете, локальным или удаленным. Поэтому при создании экземпляра объекта мы не знали, является ли этот объект прокси к удаленному объекту или он полностью «сидит» в памяти нашего процесса (на самом деле в .Net Remoting это можно было узнать, вызвав RemotingServices.IsTransparentProxy, но думаю, что идея понятна). Тогда это казалось отличной идеей, но практика показала, что пользовательский код должен четко знать, что он работает с удаленным объектом, поскольку это упрощает разделение коммуникационных ошибок от ошибок бизнес-логики, генерируемых сервисом или клиентом. WCF не скрывает тот факт, что вы работаете с прокси, и вы можете спокойно отделить коммуникационные ошибки (Communication Exceptions), от ошибок бизнес-логики, произошедших в сервисе/клиенте. Все некоммуникационные ошибки также являются частью протокола взаимодействия между клиентом и сервисом (такая себе спецификация исключений), они четко описываются в виде Fault Contract-ов и также «выставляются наружу» через WSDL (ведь мы можем работать с платформой, у которой исключений может не быть вообще).

Если вдаваться в некоторые специфические различия между WCF и .Net Remoting, то нельзя обойти вниманием механизм обратных вызовов, который претерпел колоссальные изменения. Так, в WCF интерфейс обратного вызова также является частью контракта сервиса и задается в нем непосредственно, это дает возможность в каждом вызове сервиса получить интерфейс обратного вызова, в случае необходимости сохранить его и потом вызвать метод этого интерфейса. Это поддерживается не всеми коммуникационными протоколами (поскольку некоторые протоколы являются по своей сути однонаправленными) и тогда, в случае несовместимости, вы получите ошибку в процессе создания экземпляра сервиса.

Очень важным являются различия использования интерфейсов обратного вызова с протоколом TCP. Все дело в том, что хотя протокол TCP по своей природе является двусторонним (клиент и сервер могут отправлять данные друг другу независимо), этот факт ремоутингом не поддерживается; ремоутинг для обратных вызовов устанавливает еще одно TCP соединение, а это значит, что вы не сможете использовать клиентов, расположенным за NAT-ом. Еще одной проблемой ремоутинга является то, что для создания двусторонней связи вам нужно отнаследовать класс клиента от MarshalByRefObject-а (это же условие справедливо и для сервера) и явно подписаться на события сервера или передать себя же в качестве параметра в одном из методов сервера, чтобы он вас «запомнил». Но проблема в том, что ремоутинг «втихую» старается восстановить соединение от клиента к серверу, в случае потери связи, однако после потери связи необходимо восстановить «обратную» связь от сервера к клиенту. В общем, это всегда вызывало массу головной боли, большая часть из которой ушла при использовании WCF (опять таки, благодаря четкой сигнализации инфраструктурой WCF о коммуникационных ошибках между клиентом и сервисом).

Использование WCF в виде RPC

WCF – это отличная технология для построения сервис-ориентированных приложений, что накладывает ряд ограничений на ее использование. Так, например, WCF (точнее сервисы) не поддерживают перегрузку методов (вы не можете использовать два метода с одинаковыми именами, но разным набором параметров), а также вы не можете использовать полиморфизм: нельзя определить метод сервиса, который принимает (или возвращает) переменную базового типа и передавать в него (или возвращать из него) экземпляры производных типов. Обойти это ограничение можно указав перечень «известных типов» (Known Types) (*), которые также могут быть получены через WSDL. Все это делает WCF не лучшим кандидатом, если вам нужен старый добрый RPC, к которому вы могли привыкнуть при работе с ремоутингом.

Проблемы с известными типами связаны с тем, что сериализатор, используемый в WCF по умолчанию (он называется DataContractSerializer) не добавляет в выходной поток информации о CLR типе и это естественно, поскольку «с другой стороны» может находиться система, которая об этой самой CLR не имеет ни малейшего понятия. Однако WCF содержит и другой тип сериализатора, NetDataContractSerializer, который является полной копией стандартного сериализатора, однако помимо всего прочего он добавляет полное имя типа в сериализованный поток байтов. Это сводит на нет всю сервис-ориентированную кросс-платформенность, но позволяет использовать любые сериализируемые классы в виде аргументов и возвращаемых значений и не заботиться ни о каких известных типах. (Если типы аргументов и возвращаемых значений использовались в ремоутинге и были помечены атрибутом Serializable, то их можно будет использовать и в WCF без изменений, так что переход от одной технологии к другой будет весьма безболезненным). Это позволяет использовать WCF в виде RPC, причем сменить сериализатор можно, опять таки декларативно, из конфигурационного файла приложения (**).

«Слоеная архитектура» WCF

Большинство подобных технологий для построения распределенных приложений (такие как WCF или .Net Remoting) строятся по принципу слоеного пирога (ага, сетевую модель OSI, надеюсь, еще не забыли), когда каждый слой отвечает за свой конкретный уровень абстракции и не знает ничего о нижележащих уровнях. Если говорить коротко, то вся инфраструктура WCF состоит из двух главных уровней: (1) Service Model Layer и (2) Channel Layer. Первый уровень ближе относится к самому сервису и клиенту и отвечает за преобразования метода и его параметров в сообщение для передачи более низкому канальному уровню. Канальный уровень (Channel Layer) инкапсулирует в себе канал передачи данных (спасибо, кэп), которых может быть великое множество: каналы, использующие в качестве транспорта TCP, Http, Named Pipes и т.д. Каждый из этих уровней содержит несколько подуровней, и вы можете вклиниться в каждый из них для каких-то собственных нужд.

Например, вы можете валидировать параметры методов обобщенным образом: предположим, вам всегда нужно, чтобы объекты определенного типа, используемые в десятке методов сервиса, обладали определенной характеристикой, вместо вызова метода валидации из всех методов сервиса, вы можете написать соответствующий «перехватчик» и обработать это один раз. Вы можете самостоятельно создавать WCF сообщения, изменять существующие (добавлять собственное шифрование, добавлять дополнительную информацию в заголовок сообщения или в его теле), вы можете добавлять свои собственные каналы, которые, будут работать по протоколу UDP с вашей *самописной* системой по вашему *самописному* коммуникационному протоколу. Вы можете добавлять обобщенные каналы связи для поддержки, например, UDP, поскольку такого канала нет в WCF «из коробки» и т.д. Все это может звучать несколько сложно, но если идти от простых задач к более сложным, то страшного в этом ничего нет.

Выводы

Что можно сказать в заключении? Если на платформе .Net вам понадобиться некоторый вид распределенного или межпроцессного взаимодействия, то первое на что стоит посмотреть — это WCF. Даже из коробки эта штука поддерживает большую часть функционала, который вам может понадобиться, а если же этого не произойдет, то вы всегда сможете его расширить по мере необходимости.

WCF - это огромная технология, о которой написан не один десяток книг, и не одна сотня статей. В этой короткой заметке я даже не смог упомянуть всего, что я знаю о WCF, не говоря уже о том, что этот монстр поддерживает (ибо я знаком лишь с малой толикой всех возможностей), так что если вы захотите узнать об этой технологии немного подробнее, бросьте взгляд на перечень ссылок, приведенных в конце статьи.

Дополнительные ссылки

Как я уже упомянул, по WCF существует множество книг, но я бы рекомендовал обратить внимание, как минимум на две:

  1. Justin Smith. Inside Windows Communication Foundation (Pro Developer). Microsoft Press. 2007

Книга Джувела Лёве позволит вам понять, как использовать существующие возможности WCF, а книга Джастина Смита – понять, как эти возможности расширить. Так что, эти книги – не конкуренты друг другу, скорее они дополняют друг друга, покрывая разные аспекты использования этой технологии. (Кстати, книга Лёве вышла в 2010-м году и отражает те новшества, которые появились в .Net Framework 4.0, а предыдущее издание издавалось издательством Питер на русском языке).

Что касается статей по WCF, особенно по теме «расширябельности», то, прежде всего, стоит обратить внимание на статьи Аарона Сконнарда (Aaron Skonard) в MSDN Magazine, и прежде всего на следующие:

  1. Aaron Skonard. Extending WCF with Custom Behaviors. MSDN Magazine, 2007/12
  2. Другие статьи Аарона в MSDN Magazine из серии Service Station.

-----------------------------------

(*) Об «известных типах» я неоднократно писал в блоге, но проще почитать об этом в одной статье, опубликованной на rsdn.ru: «Известные типы (Known Types) в WCF».

(**) Если вы хотите задать использование NetDataContractSerializer, в качестве сериализатора вашего сервиса в конфигурационном файле приложения, вам придется немного попотеть, поскольку эта функциональность «из коробки» не поддерживается. За подробностями, прошу к другой моей статье: «Декларативное использование NetDataContractSerializer-а»

6 комментариев:

  1. Спасибо, очень хорошо описано. Между прочим очень частый вопрос на собеседованиях.

    ОтветитьУдалить
  2. Интересно.

    >Именно это позволяет написать сервис с помощью WCF, а использовать его из Java/Python/Ruby и чего угодно еще.

    А чем это отличается от обычных SOAP-вебсервисов?

    >Тогда это казалось отличной идеей, но практика показала, что пользовательский код должен четко знать, что он работает с удаленным объектом, поскольку это упрощает разделение коммуникационных ошибок от ошибок бизнес-логики, генерируемых сервисом или клиентом.

    Еще, имхо, это важно потому что клиент может подумать что работает локально и надолбить 1000 запросов :)

    ОтветитьУдалить
  3. @nightcoder:

    > А чем это отличается от обычных SOAP-вебсервисов?

    Ничем, просто WCF объединяет в себе несколько парадигм, сервис-ориентированность и межплатформенное взаимодействие - это один из вариантов использования WCF, но далеко не единственный.

    Главное, что сам наш сервис может не знать, является ли он кросс-платформенным или используется в виде простого RPC.

    ОтветитьУдалить
  4. Сергей, стоит ли ради нововведений в WCF в Net 4.5 попробовать почитать что-то еще или объем этих нововведений не так велик?

    ОтветитьУдалить
    Ответы
    1. Я не помню, чтобы в .NET 4.5 появились какие-то серьезные архитектурные нововведения в WCF. Если WCF - это один из ваших основных инструментов, то почитать будет полезно, если же это один из многих, то может быть можно и забить.

      Удалить