Страницы

воскресенье, 28 августа 2011 г.

Настройка сборки проекта за 5 минут - UppercuT.

  Кому приходилось настраивать сборку проекта с помощью msbuild или nant понимает нудность этой задачи. А кроме того, для особо сложных сборок этот путь громоздок и тернист. Если кто не верит, может ознакомится с огромными build-файлами NHibernate тут и тут.
  И вот, настал очередной раз возникла нужда настраивать сборку проекта... Писать очередную xml портянку не хотелось, и я полез в гугл. На самом деле, инструментов для сборки оказалось довольно много: MsBuild, nant, NuBuild, Psake, FAKE (F# MAKE)Rake, UppercuT.
  С msbuild, nant и nubuild были отвергнуты сразу: старая добрая xml-конфигурация не казалась мне такой уж доброй, и разные ее вариации не делают ее добрее.
  Psake - сборка проекта на основана на powershell скриптах. Уже лучше, но чтобы выполнять скрипты пришлось на каждом компьютере настраивать разрешения для их выполнения. Не сложно, но не хотелось бы.
  Fake & Rake - конфигурация сборки на языке F#. Серьёзно к ним присматривался, но видимо потребуют некоторого времени на изучение.
  В итоге остановился на UppercuT - автор обещал возможность настроить сборку проекта за пять минут. И не обманул. Настроить сборку оказалось в разы быстрее, чем написать этот пост :)
Итак, пять простых шагов, как настроить сборку с помощью:
  1. Uppercut формирует версии сборок  проекта используя файл SolutionVersion.cs. Вот этот файл нам и надо созадать в корневой папке Solution'а. И далее подключить этот файл как ссылку(!) ко всем проектам. 
  2. Далее идём на страницу UppercuT, и качаем архив с последней версией (или не последней, кому как нравится). Находим в архиве папку Uppercut, и копируем всё ее содержимое в корневую папку solution'a. 
  3. Теперь всё-таки придётся заняться xml. Среди скопированных папок найти папку settings,  а в ней открываем файл Uppercut.config. Заполняем четыре свойства, которые понадобится uppercut'у чтобы сгенерировать SolutionVersion.cs:
    <property name="project.name" value="__SOLUTION_NAME_WITHOUT_SLN_EXTENSION__" overwrite="false" />
      <property name="path_to_solution" value="." overwrite="false" />
      <property name="repository.path" value="__REPOSITORY_PATH__" overwrite="false" />
      <property name="company.name" value="__COMPANY_NAME__" overwrite="false" />
    Всё достаточно очевидно. Отмечу только, что сейчас поддерживаются git и svn репозитории. Соответствующий url и надо вписать в конфигурацию. Поскольку я собирался билдить проект прямо из папки решения - поставил в repository.path точку. И это сработало. :)
  4. Прописываем в переменную среды PATH путь к утилите командной строки контроля версий. В моем случае git - C:\Program Files\Git\bin
  5. Запускаем build.bat из корневой папки решения. Наслаждаемся результатом.
  Мне понравилось. Почти не приложив усилий - получаем настроенную сборку проекта. Не только комплияцию, но и прогон тестов, и проверку покрытия с помощью NCover. Не уверен, что для больших и сложных проектов такой способ подойдет, но для простых очень даже хорош.
  Немного изучив содержимое папки lib, удалил оттуда лишнее: xUnit, MbUnit, Eazfuscator и MoMA. Чуть позже попробую настроить создание nuget-пакета с помощью UppercuT. А дальше в планах изучить fake и rake.
PS. Проект о котором идёт речь всё тот же - EventPublisher.

четверг, 25 августа 2011 г.

Реализуем EventPublisher. Шаг четвёртый. Переходим в замок Windsor.

  Изначально EventPublisher прездназначался для отправки сообщений экземплярам классов, которые уже существуют, и явным образом подписаны. Но часто полезно, когда получатель сообщения не живёт постоянно, а создаваётся во время его отправки, и время жизни ограничено временем обработки сообщения. Такой вот одноразовый обработчик: создался, обработал, испарился. Такую вещь можно реализовать как рефлексивный IListenerSource. Но этот способ не лишён недостатков, главный из которых - сложности с управлением зависимостями.

  К счастью, в этом мире проблема управления зависимостями уже решена с помощью DI контейнеров.  Осталось только выбрать контейнер, и написать основанный на контейнере IListenerSource. Не буду скрывать, мой любимый контейнер - Castle.Windsor. Его и буду использовать.

  Перед написанием собственно IListenerSource, стоит подумать об еще одной вещи: как регистрировать наших подписчиков в контейнере? Windsor широко использует принцип convention over configuration, и имеет множество встроенных средств для регистрации компонентов по соглашением. Значит надо такое соглашение придумать. Вот оно:
public interface ITransientListener<TMessage> : IListener<TMessage>
{
} 
  Как можно догадаться из названия - регистрировать подписчиков будем с Transient Lifestyle, чтобы обеспечить желаемый стиль работы: создался, обработал, испарился.

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

  Теперь у нас есть всё для создания IListenersSource: соглашение о подписчиках и инструмент их регистрации. С Windsor'ом это совсем не сложно:
public class TransientSource : IListenerSource
{
	private readonly IKernel _kernel;

	public TransientSource(IKernel kernel)
	{
		_kernel = kernel;
	}

	public virtual IEnumerable<IListener<TMessage>> ResolveListenersFor<TMessage>()
	{
		return _kernel.ResolveAll<ITransientListener<TMessage>>();
	}
}
  Теперь мы можем сконфигурировать EventPublisher, чтобы он перенаправлял сообщения одноразовым обработчикам из контейнера. Но как же быть со старыми добрыми долгоживущими экземплярами подписчиков? Ответ прост: нужен источник подпичиков, который бы аггрегировал подписчиков из разных источников - CompositeListenerSourсe.

  Остался последний штрих. Windsor предлагает еще одну концепцию для упрощения рутинных действий при регистрации компонентов: Facility. Почему бы не сделать EventPublisherFacility?
Основые действия, которые на неё возлагаюстся:
  1. Конфигурирует EventPublisher используя IPublishWay зарегистрированный в контейнере или переданный в конструкторе, и CompositeListenerSource, в который добавлены TransientListnerSource, и все зарегистрировные в контейнере IListenerSource.
  2. Регистрирует в контейнере IAssignee, для возможности добавлять подписчиков. 
  Реализация это не так уж сложно. Теперь у нас есть зарегистированный в контейнере EventPublisher  с возможностью подписывать/отписывать и вытаскивать из контейнера одноразовые обработчики.

  Надеюсь, это материал будет кому-нибудь полезен. Код проекта на github.

вторник, 23 августа 2011 г.

Реализуем EventPublisher. Шаг третий. Слово о тестировании.

 Еще одна важная часть процесса разработки - модульное тестирование. Всё описанное ниже можно заменить на использование мок-объектов. Но лично меня часто раздражает их многословный и не всегда легко читаемый лямбда-синтаксис. Собственно, как помочь в деле деле тестирования? Мне видятся две вещи:
  1. Реализация IPublisher в простом и надёжном варианте. В первую очередь - простое подписывание слушателей,  и однопоточность. 
  2. Удобный класс-помощник для Assert выражений.

Для первого пункта можно сделать простой наследник от Publisher, заранее инициализировав все свойства. Например так:

public class TestPublisher : Publisher
{
public IAssignee Assignee 
{ 
  get { return (IAssignee) ListenerSource; } 
}

public TestPublisher() : base(new SimpleAssignee(), new SimplePublishWay())
{
}
}

С тестовым Publisher`ом всё. Теперь перейдём к assert-помощнику. Какие функции он должен нести? Неким образом фиксировать количество вызовов, и полученные сообщения. И еще лучше - лёгко читаемый синтаксис. И вот что в итоге у меня получилось:
public class TestListener<TMessage> : IListener<TMessage>
{
  private readonly List<TMessage> _messages;

  public TestListener()
  {
    _messages = new List<TMessage>();
  }

  public int WasCalled { get; private set; }

  public bool WasCalledAtLeastOnce
  {
    get { return WasCalled > 0; }
  }

  public TMessage LastMessage
  {
    get { return _messages.Last(); }
  }

  public IList<TMessage> Messages
  {
    get { return _messages.AsReadOnly(); }
  }

  public bool WasCalledOnce
  {
    get { return WasCalled == 1; }
  }

  public void ListenTo(TMessage message)
  {
    WasCalled++;
    _messages.Add(message);
  }
}
И как бы это выглядело в тесте:

[Test]
public void SomeTest()
{
   Assert.That(listener.WasCalledOnce);
}
Выглядит достаточно читаемо. По крайней мере для меня :)

суббота, 20 августа 2011 г.

Реализуем EventPublisher. Шаг второй.

В прошлой части я наметил основу будущего диспетчера событий. Пора заняться реализацией.
Для начала определим стоящие перед нами проблемы. К счастью их всего две:
  1. Получение списка подписчиков сообщений. 
  2. Отправка сообщений подписчикам. 
Решение обоих задач крайне желательно сделать легко модифицируемым и расширяемым. Поэтому их реализация начинается... с написания новых интерфейсов. Для каждой из задач оответсвенно:

public interface IListenerSource
{
  IEnumerable<IListener<TMessage>> ResolveListenersFor<TMessage>();
}

public interface IPublishWay
{
  void Publish<TMessage>(TMessage message, IListener<TMessage> listener);
}
Эти интерфейсы открывают путь к простейшей реализации диспетчера событий:
public class Publisher : IPublisher
{
  protected readonly IListenerSource ListenerSource;
  protected readonly IPublishWay PublishWay;

  public Publisher(IListenerSource listenerSource, IPublishWay publishWay)
  {
    if(listenerSource == null)
      throw new ArgumentNullException("listenerSource");
    if(publishWay == null)
      throw new ArgumentNullException("publishWay");

    ListenerSource = listenerSource;
    PublishWay = publishWay;
  }

  public virtual void Publish<TMessage>(TMessage message)
  {
    foreach (var listener in ListenerSource.ResolveListenersFor<TMessage>())
    {
      PublishWay.Publish(message, listener);
    }
  }
}

Теперь можно перейти к реализации IPublishWay. В простейшем случае можно сделать так:
public class SimplePublishWay : IPublishWay
{
  public virtual void Publish<TMessage>(TMessage message, IListener<TMessage> listener)
  {
    listener.ListenTo(message);
  }
}
Для работы в реальных условиях такая реализация вряд ли годится. Но пригодится для тестировочных конфигураций Publisher'a. Но об этом в следующей части. А сейчас посмотрим на более полезную реализацию, с использованием SynchronizationContext. От просто SynchronizationContext пользы немного, но при использовании его наследника WindowsFormsSynchronizationContext мы получим маршаллинг событий в поток пользовательского интерфейса. Подробнее об это можно почитать например здесь. Опять же, простейшая реализация:
public class SynchronizationContextPublishWay : IPublishWay
{
  private readonly SynchronizationContext _synchronizationContext;

  public SynchronizationContextPublishWay(SynchronizationContext synchronizationContext)
  {
    _synchronizationContext = synchronizationContext;
  }

  public void Publish<TMessage>(TMessage message, IListener<TMessage> listener)
  {
    _synchronizationContext.Post(x => listener.ListenTo(message), null);
  }
}
Метод Post позволит реализовать асинхронную передачу сообщений, освободив вызывающий поток для другой работы.
Остался последний штрих - реализовать IListenerSource. Для первой реализации можно взять обыкновенный список. Но лучше воспользоваться новой коллекцией из .net 4 - ISet. Она не позволяет дважды добавить один и тот же объект, что избавит от необходимости предварительной проверки.
public class SimpleAssignee : IAssignee
{
  private readonly ISet<object> _listeners;
  private readonly object _latch;

  public SimpleAssignee()
  {
    _listeners = new HashSet<object>();
    _latch = new object();
  }

  public virtual void Subscribe(object listener)
  {
    lock(_latch)
    {
      _listeners.Add(listener);
    }
  }

  public virtual void Unsubscribe(object listener)
  {
    lock(_latch)
    {
      _listeners.Remove(listener);
    )
  }

  public virtual IEnumerable<IListener<TMessage>> ResolveListenersFor<TMessage>()
  {
    lock(_latch)
    {
      return _listeners.OfType<IListener<TMessage>>().ToList();
    }
  }
}

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

вторник, 16 августа 2011 г.

Реализуем EventPublisher. Шаг первый.

 Шаблон EventAggregator (он же EventPublisher, он же EventBroker) известен давно, и описан Фаулером еще в 2004 году. Обобщённую реализацию для .NET можно найти в серии Build your own CAB  под пунктом 13а.  Основная идея проста: за счёт единообразной диспетчеризации сообщений упрощается код клиентского класса, уменьшается число его зависимостей.
  В интернете можно найти много и других реализаций. Так собственно, зачем городить еще одну? Ответ прост: хочется получить более мощные возможности по модификации поведения, и естественно, получить полный контроль над проектом (как первый этап плана по захвату мира).

public interface IPublisher
{
    void Publish<TMessage>(TMessage "message"); 
}

 Обычно IPublisher включает методы подписки/отписки получателей сообщений. Но это неправильно: многим классам надо только отправлять сообщение. Поэтому вооружимся ISP, и выделим подписку в отдельный интерфейс:
public interface IAssignee : IListenerSource
{
    void Subscribe(object listener);

    void Unsubscribe(object listener);
}

И конечно же, осталось ввести интерфейс для подписчиков:
public interface IListener<TMessage>
{
    void ListenTo(TMessage message);
}
Обобщённая версия интерфейса подписчика даёт большое преимущество: статическую типизацию получателей сообщений по типу сообщения.
Скелет будущего диспетчера событий готов. В следующих частях серии займемся его реализацией.
Код проекта на есть Github.