Показаны сообщения с ярлыком WCF. Показать все сообщения
Показаны сообщения с ярлыком WCF. Показать все сообщения

вторник, 27 января 2009 г.

Much of muchness. Part 6. XAppDomain EventBroker.

Алилуйя! Мы добрались до заключительной (и самой интересной) части.
Здесь я расскажу, свой подход к реализации кросс-доменного EventAgregator'а.

Необходимость создания этого монстра диктует плагинная архитектура - различные изолированные модули, ничего не знающие об остальных плагинах, должны уметь оповещать окружающих о каком-то важном событии, причем получатели сообщения, обязательно будут жить в другом, изолированном домене.
Фундаментом моей платформы является CAB - composite application block, содержащим реализацию EventAggregator'а. Как сделать так, чтобы он не был ограничен одним доменом? Никак. Дело в том, что в CAB композиты публикуют события и подписываются на них при помощи ObjectBuilder pipeline, в котором специальная стратегия EventAggregator'а аккумулирует всю эту информацию. В нашем же случае все объекты плагинов будут создаваться MAF pipeline'ом, и доступа к ним у нас не будет. Так как же быть? Использовать WCF - Windows Communication Foundation.

В WCF из коробки нет реализации механизма Publish-Subscribe, но есть все средства для его реализации. Подходов может быть несколько:

За основу решения я взял последний framework от Juval Lowy и расширил его, сделав его contract&topic based, с тем, чтобы интегрировать его с CAB EventAggregator (topic based). Но обо всем по порядку.

Publish-Subscribe framework (С) Juval Lowy IDesign.

Дабы не повторяться, не буду описывать механизмы работы последнего, посему настоятельно рекомендую ознакомиться со статьей, в которой он описан.

Прежде всего небольшим изменениям подвергся контракт сервиса подписки. Теперь ему передается не имя метода, которое надо вызвать у подписчика, а некий объект, выражающий интерес в подписке, который содержит имя метода (contract based) и имя topic (topic based), по которым EventAggregator сможет определить заинтересованность того или иного подписчика в неком событии.





Интерфейс сервиса подписки кросс-доменного EventAggregator'а для моей платформы выглядит следующим образом:

основное его назначение - сообщать WCF инфраструктуре о том, что IEventNotifyCallback - это callback contract для нашего кросс-доменного EventAggregator'а. DomainDTO - это суперкласс для моих DTO.

Сам сервис реализован в классе XAppDomainEventBrokerSubscription.

Он выполняет функции сервиса подписки и связывает инфраструктуру CAB с нашим механизмом. Последнее осуществляется аналогичным CAB'овскому образом - поиск публикаторов событий (по соответствующим аттрибутам) и аккумуляция этих публикаторов в CAB EventAggregator с одной лишь разницой, что вместо подписчика подставляется суррогатный объект, который при получении события опубликует его в нашей кросс-доменной системе (фактически просто пробросит дальше). Интегрируется этот сервис в CAB при помощи специальной стратегии ObjectBuilder'а XAppDomainEventBrokerStrategy.


Публикацией ведает XAppDomainEventBrokerPublication - наследник слегка измененного PublicationService сервиса из framework'а Juval Lowy. Первый реализует callback контракт как контракт сервиса, а последний на основании вызываемого метода и topic находит и вызывает соответствующий метод у соответствующего подписчика.


Но возникает резонный вопрос - а где, собственно, этому сервису передается topic? И куда его вообще воткнуть, если это contract-based framework? Ответ прост. Засунуть в заголовок
сообщения и занимаются этим клиенты сервиса. Их только надо унаследовать от PublisherServiceClient - часть моей инфраструктуры WCF (может быть опишу попозже).


Обратите внимание - у базового клиента сервиса подписки есть свойство EventTopic, с помощью которого мы можем передать это значение в WCF pipeline через расширение канала EventTopicChannelExtension, с одним единственным свойством - EventTopic.
Помните суррогатные объекты, добавлявшиеся в EventBroker CAB? Так вот они публикуют события через EventPublisherClient, передавая ему значение EventTopic из аттрибута.
Как EventTopicChannelExtension попал в канал? Все тот же базовый клиент при создании фабрики каналов добавляет EventTopicFlowBehavior, выполняющий всю работу.




Собственно всю работу делает даже не он, а следующие два джентельмена:



Первый, как следует из его названия, инициаллизирует канал, добавляя в него уже упоминавшееся выше расширение EventTopicChannelExtension для передачи EventTopic второму джентельмену, который собственно добавляет этот topic в заголовок передаваемого сообщения.
Из этого самого заголовка, слегка модифицированный мной, PublicationService вычленяет EventTopic и фильтрует по нему подписчиков.

Вот и все! Теперь все, что нужно сделать по шагам:
1. Создать прокси к сервису подписки и подписаться на интересуемое событие, указав при желании метод контракта, который следует вызвать.
2. Создать прокси к сервису публикации и вызвать, мимикрирующий под контракт подписчика сервис публикации, передав при желании topic.

Не очень надеюсь вы не запутались, потому что сам запутывался не раз.

P.S. ссылки на предыдущие посты в этой серии
Удачи!

среда, 30 апреля 2008 г.

Enterprise Logging System. Part 3. Service.

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


У сервиса 2 обязанности:
  • централизовать хранение настроек;
  • помещать сообщения в сконфигурированное хранилище.
соответственно выглядит его контракт:


Сообщения несут разные наборы информации, и, чтобы не перегружать базовый класс сообщения неиспользуемыми свойствами, я ввел в data contract иерархию.

Для того, чтобы сервис мог десериализовать сообщения классов-наследников базовый класс необходимо пометить атрибутами [KnownType(typeof(...))], где ... - имена классов наследников.

Metadata.

Первая задача предельно проста. Необходимо сохранять и извлекать настройки клиентов из БД. Если настроек для конкретного клиента нет, то возвращаются дефолтные настройки, общие для всех клиентов.
Метаданные хранятся в БД и извлекаются при помощи Linq2Sql классов.

Вот классы, являющиеся метаданными системы:


Клиент идентифицируется по запросу, из полей которого составляется уникальный хеш. По нему то и ведется поиск в базе.






Logs
Сохранение сообщений делегируется адаптеру системы логирования, который подключается к сервису в файле конфигурации хоста сервиса. Я писал об этом здесь: http://bobbbloggg.blogspot.com/2008/04/loosely-coupled-wcf-service.html. В системе поумолчанию используется адаптер Enterprise Library Logging AB. Так как логи должны сохраняться в надежном хранилище с возможностью репликации со всеми данными системы на основной сервер, то конечно же в роли этого хранилища выступает БД. Дабы не тащить за собой Data Access AB из Enterprise Library, т.к. мне совсем не нравится его реализация, я написал свой легковесный custom DBTraceListener, сохраняющий логи в БД при помощи класов Linq2Sql. Этот TraceListener подключается к Logging AB в файле конфигурации и легко может быть заменен на любой другой.

Вот структура таблиц логов в БД.


Linq2Sql поддерживает паттерн отображения наследования с одной таблицой (см. Фаулер). Поэтому в таблицу введено специальное поле дискриминатор (DiscKey) и linq2sql классы образуют повторяющую LogMessage иерархию.


Все сообщения разделены на категории по уровню (LogLevel). Также в системе по умолчанию настроена одна дополнительная "специальная" категория сообщений - CriticalErrors, к которой подключен EmailTraceListener, который при деплойменте может быть настроен на почтовый ящик администратора системы и в случае получения сообщений о критических ошибках он будет получать письмо по почте, с тем чтобы привлечь его внимание к проблеме. Распределением сообщений по категориям занимается LoggingSystemAdapter.


В следующий раз я опишу фасад системы.
Удачи.

понедельник, 28 апреля 2008 г.

Enterprise Logging system. Part2 Context.

Перед тем как писать о сервисе коротко опишу контекст, передаваемый ему (сервису) вместе с сообщением. Контекст входит в data contract сообщения LogBook, т.е. я не использую появившийся в .Net 3.5 xxxContextBinding по той простой причине, что однажды установив его уже нельзя будет изменить. Да и передавать контекст в заголовке сообщения также не вижу особого смысла, поэтому он уютно располагается в теле сообщения.

Контекст в LogBook передается в виде словаря Dictionary<string, object>.
Чтобы облегчить себе жизнь я написал класс LogContext. LogContext имеет implicit operator конвертирующий его в Dictionary, поэтому не надо делать явных приведений типов.
У него есть свойство Current, которое возвращает текущий контекст (собирает небольшой набор данных об окружении) и несколько методов, с помощью которых его можно расширить.
Эти методы написаны в семантике “fluent interface”, каждый из которых возвращает копию исходного контекста с добавленными свойствами.


public LogContext AddAttributes(IDictionary attributes)
public LogContext AddAttribute(string name, object value)

и создание нового расширенного контекста будет выглядеть следующим образом:

var context = LogContext.Current.AddAttribute("1", 1)
.AddAttribute("datacontract", new Class1 { MyProperty = 1})
.AddAttribute("serializable", new Class2 { MyProperty = 2});
- очень удобно!

для того, чтобы передавать в контексте свои «кастомные» объекты, необходимо проделать следующее:
1. Пометить эти классы одним из атрибутов : Serializable или DataContract;
2. Сконфигурировать DataContractSerializer на клиенте и на сервере, добавив в конфигурационный файл приложения следующий блок:

<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="System.Collections.Generic.Dictionary`2,mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<knownType type="ContextSendingTest.Class1, ContextSendingTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<knownType type="ContextSendingTest.Class2, ContextSendingTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>


Далее при добавлении атрибута контекст проверит, помечен ли класс одним из упомянутых атрибутов и сконфигурирован ли DataContractSerializer.

Контекст реализует интерфейс IEquatable, сравнивая коллекции аттрибутов с использованием дефолтных EqualityComparer, поэтому «кастомные» классы должны перегружать метод Equals для того чтобы изменить логику их сравнения и сравнения контекстов в целом.

Вот, вкратце так.

Удачи!

вторник, 1 апреля 2008 г.

Loosely-coupled WCF service

В этой заметке я попытаюсь рассказать Вам о реализации сервиса, слабо связанного с кодом доступа к нижележащим ресурсам. Ценность данного подхода заключается в том, что при помощи простой административной настройки конфигурационного файла хоста сервиса вы сможете менять хранилища данных, логику работы и пр. пр., не меняя кода сервиса.

Замечу сразу, что реализация основана на статьях Roman Kiss'а на codeproject, в которых он при помощи WCF сервисов реализует различные WS-* протоколы. Вот эти статьи:
Идея подхода следующая - отделить код сервиса от непосредственной работы с ресурсами при помощи адаптера сервиса (ServiceAdaptor). Реализация основана на модели расширения WCF, которая отлично описана в статье Aaron Skonnard'а Extending WCF with custom behaviors (к сожалению статья на русском - не знаю как заставить MSDN выдавать статьи на том языке, который мне нужен, а не на том который у меня на компе).

Основа решения состоит из следующих элементов:
  • базовый класс сервиса, для доступа к адаптеру;
  • поведение, применяемое к сервису, для создания адаптера;
  • конфигурационной элемент, для настройки поведения в конфиге;

Далее, для каждой конкретной реализации необходимы:

  • интерфейс адаптера, который будет выполнять необходимую сервису работу;
  • класс, реализующий интерфейс адаптера;
  • класс, реализующий контракт сервиса, унаследованный от базового класса с адаптером.

Вот и все теперь по частям:

ServiceAdapterBase

Базовый класс, инкапсулирующий логику получения объекта адаптера из инфрастуктуры WCF.

Здесь мое главное усовершенствование. Адаптер впервые создается при первом обращении к нему из методов контракта. Такое решение позволяет добиться следующих преимуществ:

  • сервис сам управляет временем жизни адаптера (экземпляр адаптера существует в каждом экземпляре сервиса, соответственно их времена жизни равны);
  • при установке ServiceBehavior.InstanceContextMode = Single становится невозможным создать адаптер, при поведении применяемом в конфигурационном файлеб ниоткуда кроме текущей операции, т.к. при создании объекта singlton-сервиса инфраструктура wcf еще не инициаллизирована и поведение применяемое в конфиге позволяет расширить wcf только на уровне сервиса или endpoint.


AdapterAttribute - service behavior

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



ServiceAdapterBehaviorElement

Класс, необходимый для подключения поведения в файле настроек.



Конкретная реализация

ITestAdapter, TestAdapter


IService, Service



Обратите внимание на параметр типа ServiceAdapter в ServiceAdapterBase - здесь вы можете жестко в коде прошить класс адаптера (удобно для разработчика - можно быстро менять реализацию сервиса). Для того чтобы дать понять классу ServiceAdapterBase, что конкретный тип адаптера будет задан в конфиге ему подставляется затычка - ConfigurableAdapter (пустой класс даже картинку не буду вешать).


И теперь последняя деталь - файл конфигурации:



P.S. С выходом .Net framework 3.5 та же функциональность слегкостью может быть достигнута при помощи модели WorkflowServices и loose-xoml. Почитайте у Roman'а http://www.codeproject.com/KB/WF/VirtualServiceForESB.aspx


Удачи!