четверг, 5 сентября 2013 г.

Фреймворк: Rikrop.Core.Wcf.Unity.dll

К оглавлению

Rikrop.Core.Wcf.Unity.dll

Библиотека содержит методы для клиентской и серверной регистрации WCF инфраструктуры приложения внутри контейнера. Регистраторы объединены в два пространства имен:
  • Rikrop.Core.Wcf.Unity.ClientRegistration 
  • Rikrop.Core.Wcf.Unity.ServerRegistration 
Структура объектов в каждом из них обеспечивает простую и удобную пошаговую регистрацию-настройку необходимых компонентов.

Вместо введения.

В наших клиент-серверных проектах мы полностью отказались от декларативного описания служб WCF в конфигурационном файле приложения. Вместо этого у нас есть гибкая структура объектов, описывающих wcf взаимодействие на стороне клиента и сервера, конфигурация которых происходит императивно и типобезопасно. Часть из этих объектов мы рассмотрим в этой статье, а часть в статье посвященной библиотеке Rikrop.Core.Wcf.dll

Декларативная конфигурация.

Традиционно приложение включающее wcf взаимодействие содержит множество информации в конфигурационных файлах клиента и сервера. Это и описание сервиса, включающее задание эндпойнтов и адреса хоста, и настройка behavior'ов, расширяющих взаимодействие, и конфигурация binding'ов. Вот небольшой кусок конфигурации wcf на стороне сервиса из одного очень старого проекта:
<system.serviceModel>
  <services>
    <service behaviorConfiguration="References.BasicReferencesServiceBehavior" name="Fso.Core.Services.References.BasicReferencesService">
      <endpoint address="" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_MaxSize" contract="Fso.Core.Services.References.Contracts.IBasicReferencesService" />
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost:8200/Fso/Services/References/ReferencesService" />
        </baseAddresses>
      </host>
    </service>
  </services>
 
  <bindings>
    <basicHttpBinding>
      <binding name="BasicHttpBinding_MaxSize" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="1073741824" maxBufferPoolSize="1073741824" maxReceivedMessageSize="1073741824" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
        <readerQuotas maxDepth="64" maxStringContentLength="1073741824" maxArrayLength="1073741824" maxBytesPerRead="1073741824" maxNameTableCharCount="1073741824" />
        <security mode="None">
          <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
          <message clientCredentialType="UserName" algorithmSuite="Default" />
        </security>
      </binding>
    </basicHttpBinding>
  </bindings>
  
  <behaviors>
    <serviceBehaviors>
      <behavior name="References.BasicReferencesServiceBehavior">
        <serviceDebug includeExceptionDetailInFaults="false" />
        <serviceMetadata httpGetEnabled="true" />
        <dataContractSerializer maxItemsInObjectGraph="1073741824" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>
Стоит заметить что это конфигурация только одного из множества сервисов которые будут созданы и запущены. В обычном приложении может быть десять таких сервисов, а в крупных корпоративных системах - не одна сотня. А ведь это только сервисная настройка, аналогичную конфигурацию нужно провести и на клиенте:
<bindings>
  <basichttpbinding>
    <binding name="BasicHttpBinding_IBasicReferencesService" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="1073741824" maxBufferPoolSize="0" maxReceivedMessageSize="1073741824" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
      <readerQuotas maxDepth="32" maxStringContentLength="1073741824" maxArrayLength="1073741824" maxBytesPerRead="1073741824" maxNameTableCharCount="1073741824" />
      <security mode="None">
        <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
        <message clientCredentialType="UserName" algorithmSuite="Default" />
      </security>
    </binding>
  </basichttpbinding>
</bindings>
     
<client>
  <endpoint address="http://localhost:8200/Fso/Services/References/ReferencesService" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IBasicReferencesService" contract="ReferencesServiceReference.IBasicReferencesService" name="BasicHttpBinding_IBasicReferencesService" />
</client>
Слишком громоздко.
Все действия по регистрации поведения WCF делает за программиста инфраструктура Rikrop.Core.Wcf, после небольшой настройки, конечно же. Давайте теперь посмотрим как это настроить.

namespace Rikrop.Core.Wcf.Unity.ServerRegistration

В этом пространстве имен собраны объекты, которые позволяют пошагово настроить сервис. Типичный код регистрации выглядит примерно так:

container.RegisterServerWcf(
                    o => o.RegisterServiceConnection(reg => reg.NetTcp(serviceIp, servicePort))
                          .RegisterServiceHostFactory(reg => reg.WithBehaviors()
                                                                .AddErrorHandlersBehavior(eReg => eReg.AddBusinessErrorHandler()
                                                                                                      .AddLoggingErrorHandler(NLogger.CreateEventLogTarget()))
                                                                .AddDependencyInjectionBehavior()));
Регистрация проходит в два шага:
  • регистрация сервиса подключений (RegisterServiceConnection)
  • регистрация фабрики хостов (RegisterServiceHostFactory)
Весьма интересна структура объектов, обеспечивающих цепочку зависимых шагов. У нас это объекты-шаги регистрации, содержащие метод принимающий делегат Func<T, TResult> (где Т - это объект-регистратор, а TResult - результат выполнения регистрации) и возвращающий следующий объект-шаг.
public class FirstStepRegistrator
    {
        ...
        public SecondStepRegistrator RegisterServiceConnection(Func<ServiceConnectionRegistrator, ServiceConnectionRegistrator.Result> registrator)
        {
            registrator(...);
            return new SecondStepRegistrator(...);
        }
    }
Главное действующее лицо здесь - объект-регистратор. Для первого шага это ServiceConnectionRegistrator который регистрирует в контейнере тип фабрики конечных точек, через которые будет происходить клиент-серверное взаимодействие. В библиотеке уже есть фабрики, создающие NetTcp и NamedPipe эндпойнты, но ничего не мешает создать свою собственную фабрику, реализующую интерфейс IServiceConnection, и зарегистрировать его.
  • public Result NetTcp(string host, int port){...}
    
  • public Result NamedPipe(string pipeName){...}
    
  • public Result Custom<TServiceConnection>(LifetimeManager lifetimeManager = null, params InjectionMember[] injectionMembers) where TServiceConnection : IServiceConnection {...}
    
А вот как выглядит интерфейс IServiceConnection:
public interface IServiceConnection
{
   ServiceEndpoint CreateServiceEndpoint(Type contractType);
}


Следующий шаг это регистрация и настройка фабрики хостов, ответственную за создание узлов службы. Фабрика должна реализовывать интерфейс IServiceHostFactory:
public interface IServiceHostFactory
    {
        ServiceHost CreateServiceHost(Type serviceType);
    }
Несколько таких фабрик уже есть в библиотеке:
  • StandardServiceHostFactory - создаёт узел определенного типа;
  • GenericServiceHostFactory - позволяет задать метод-делегат, который будет создавать узлы;
  • BehaviorBasedServiceHostFactory - позволяет задать расширенное поведение для узла.
Наиболее интересная и часто используемая регистрация последней фабрики. Указав объекту-регистратору, что мы хотим зарегистрировать фабрику с расширенным поведением
.RegisterServiceHostFactory(reg => reg.WithBehaviors()...)
нам сразу будет предложено указать какие расширения мы хотим подключить. В библиотеке есть следующие регистраторы:
  • AddErrorHandlersBehavior отвечает за подмену сервисного обработчика ошибок IErrorHandler, преобразование сервисных ошибок в бизнес-ошибки и создание записей логгере
    .AddErrorHandlersBehavior(eReg => eReg.AddBusinessErrorHandler().AddLoggingErrorHandler(NLogger.CreateEventLogTarget()))
    
  • AddDependencyInjectionBehavior отвечает за подмену сервисного контейнера объектов IInstanceProvider на наш провайдер, использующий Unity контейнер. Необходимо это для того, чтобы когда инфраструктура wcf запросит объект по сервис контракту, его заботливо вернул наш Unity контейнер (забегая вперед - в контейнер контракты будут помещены автоматически при запуске сервиса, никакой явной регистрации не требуется)
    .AddDependencyInjectionBehavior()
    
  • AddCustomBehavior позволяет зарегистрировать свое расширение, унаследованное от IEndpointBehavior.


В сухом остатке у нас есть прозрачная система регистрации и настройки, которая позволяет за счёт строгой типизации не ошибиться и не пропустить какую-то часть регистрации, при этом наличие множества открытых методов регистрации, позволяет создать свою дополнительную инфраструктуру, которая состыкуется с объектами из Rikrop.Core.Wcf.

namespace Rikrop.Core.Wcf.Unity.ClientRegistration

Клиентская регистрация во многом похожа на серверную
container.RegisterClientWcf(
                o => o.RegisterServiceExecutor(reg => reg.Standard()
                                                         .WithExceptionConverters()
                                                         .AddFaultToBusinessConverter())
                      .RegisterChannelWrapperFactory(reg => reg.Standard())
                      .RegisterServiceConnection(reg => reg.NetTcp(serviceIp, servicePort)));

и проходит в три шага:
  • RegisterServiceExecutor - регистрация-настройка объектов, которые будут осуществлять исполнение запросов к серверу;
  • RegisterChannelWrapperFactory - регистрация фабрики обработчиков сетевых подключений wcf;
  • RegisterServiceConnection - регистрация типа фабрики конечных точек.
Объекты исполнения запросов к серверу не требуют особых настроек, поэтому мы просто регистрируем в контейнере необходимую связку интерфейс-тип объекта и подключаем конвертацию wcf ошибок в бизнес-ошибки (подробно об объектах участвующих в регистрации - в статье посвященной Rikrop.Core.Wcf):
_container.RegisterType(typeof(IServiceExecutor<>), typeof(ServiceExecutorWithExceptionConversion<>),
                                    new ContainerControlledLifetimeManager(),
                                    new InjectionConstructor(new ResolvedParameter(typeof(IServiceExecutor<>), serviceExecutorName),
                                                             new ResolvedParameter<IFaultExceptionConverter>()));

Аналогично регистрируется и фабрика обработчиков сетевых подключений:
_container.RegisterType(typeof(IChannelWrapperFactory<>), typeof(StandardChannelWrapperFactory<>), new ContainerControlledLifetimeManager());

Реализация регистрации фабрики конечных точек аналогична серверной с тем отличием, что на клиенте необходимое расширения поведения мы регистрируем именно здесь. Настройки должны быть выставлены идентичные серверным (NetTcp, NamedPipe или же собственная реализация). Подключение расширений выглядит так:
reg.NetTcp(serviceIp, servicePort)
   .WithBehaviors()
   .AddSessionBehavior(sReg => sReg.WithStandardSessionHeaderInfo("SessionNamespace", "SessionId")
   .WithCustomSessionIdResolver<ClientSession>(new ContainerControlledLifetimeManager())

Зачем делать всё именно так?

Потому что мы любим свою работу! И любим заниматься по-настоящему полезными вещами. Вместо того, чтобы заниматься созданием и поддержкой инфраструктуры клиент-серверного приложения, мы используем очень гибкую, удобную и легковесную систему регистрации wcf служб, которая в считанные минуты превращает любое разрабатываемое приложение в сетевое, и позволяет нам сосредоточится на бизнес-логике и решении основных задач. Так же заметно оптимизируется процесс развертывания клиент-серверного приложения для тестирования и в процессе отладки, о чём будет подробнее рассказано в статье, посвященной библиотеке Rikrop.Core.Wcf.dll.

Комментариев нет:

Отправить комментарий