<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6273369423927430830</id><updated>2024-09-10T20:00:17.941+03:00</updated><category term="TDD/BDD"/><category term="AOP"/><category term="DI/IoC"/><category term="NHibernate"/><category term="SNAP"/><category term="ASP.NET"/><category term="AutoMapper"/><category term="MSpec"/><category term="Python"/><category term="SimpleSpec"/><category term="Visual Studio"/><category term="welcome"/><title type='text'>IObservable&amp;lt;T&amp;gt;</title><subtitle type='html'>where T : software development, .NET technology stack, notes from my life</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>13</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-522373066169425452</id><published>2012-04-29T21:32:00.000+03:00</published><updated>2012-04-29T21:32:55.385+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ASP.NET"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><category scheme="http://www.blogger.com/atom/ns#" term="Visual Studio"/><title type='text'>Web application configuration management for different target environments</title><content type='html'>&lt;p&gt;
Рано или поздно в жизненном цикле любого приложения становятся актуальными задачи развертывания. Это может быть развертывание как на выделенной машине в пределах офиса для того, чтобы передать приложение на тестирование, так и на группе удаленных хостов в облаке, образующих веб-ферму. Где бы не хостилось приложение, встает &lt;strong&gt;вопрос об управлении конфигурационными настройками для разных окружений&lt;/strong&gt;. Эта проблема и одно из ее возможных решений – как раз и будут темой сегодняшнего поста.&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Содержание:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Классический способ “в лоб” управления конфигурацией. &lt;/li&gt;
&lt;li&gt;Где хранить конфигурационные настройки веб приложения для разных окружений? &lt;/li&gt;
&lt;li&gt;Лирическое отступление на тему хороших манер при работе с конфигурационными файлами. &lt;/li&gt;
&lt;li&gt;Как использовать &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/dd465326.aspx&quot;&gt;ASP.NET web.config transformations&lt;/a&gt; для генерации конфигурационных файлов для разных окружений (dev, staging, production). &lt;/li&gt;
&lt;li&gt;Использование ASP.NET web.config transformations для любого XML файла, а не только для web.config.&lt;/li&gt;
&lt;li&gt;Как используя ASP.NET web.config transformations, иметь возможность компилировать приложение на машине без установленного Visual Studio и ASP.NET, а лишь с .NET Framework 4.0. &lt;/li&gt;
&lt;li&gt;Пример использования ASP.NET web.config transformation синтаксиса. &lt;/li&gt;
&lt;li&gt;Автоматический выбор конфигурационного файла для целевого окружения, и подготовка deployment package посредством собственного скрипта, написанного на Python. &lt;/li&gt;
&lt;/ol&gt;

&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;

&lt;h2&gt;
Application target environments&lt;/h2&gt;
&lt;p&gt;
Для типичного веб приложения, о котором сегодня пойдет речь, можно выделить следующие целевые среды развертывания:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;local environment &lt;/li&gt;
&lt;li&gt;development/QA environment &lt;/li&gt;
&lt;li&gt;staging environment &lt;/li&gt;
&lt;li&gt;production environment &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Есть конечно и более специфичные, но сегодня этих будет достаточно. Local environment можно было бы и не выделять как отдельную полноценную среду, обычно разработчик вручную осуществляет настройку приложения на локальной машине, о деплойменте же как о таковом речи вообще может не идти. IIS application может быть отображен напрямую на папку, где находятся исходники проекта, в web.config по сути находятся настройки для локального окружения. Но если не относится к local environment как к частному случаю, то можно унифицировать процесс развертывания, что есть хорошо.&lt;/p&gt;
&lt;h2&gt;
Configuration places&lt;/h2&gt;
&lt;p&gt;
После того как мы определились с целевыми средами, нужно понять что должно или может меняться в зависимости от среды, а соответственно, что мы хотим конфигурировать. Это могут быть строки подключения к источнику данных, адреса, учетные записи и пароли для внешних сервисов, настройки безопасности, например timeout авторизационного cookie, настройки логгирования, уровень логгирования, получатели лог событий, обработка ошибок и отображение собтвенных страниц с ошибками, настройки сторонних модулей (например, &lt;a href=&quot;http://getglimpse.com/&quot;&gt;Glimpse&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;
Web.config and config files good manners&lt;/h3&gt;
&lt;p&gt;
Стандартной практикой является размещение конфигурационных настроек в web.config. Здесь хочется сделать небольшое лирическое отступление, и поговорить о хороших манерах работы с web.config. Мы выносим конфигурационные настройки в web.config или любой другой конфигурационный файл, для того чтобы иметь возможность на лету изменять их без необходимости перекомпиляции всего приложения. Говоря технически, при изменении web.config IIS создает новый AppDomain, загружает туда сборки приложения, и новые запросы обрабатываются уже новой версией приложения в новом AppDomain.&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Конфигурационный файл должен содержать только то, что нужно менять в зависимости от окружения (строки подключения), или же то, что меняется часто или на лету в процессе работы приложения (настройки логгирования).&lt;/strong&gt; Не нужно засорять конфигурационный файл, а тем более XML файл, тем что никогда не будет менятся, или же будет менятся редко. Если что-то меняется редко, реже чем происходит цикл развертывания приложения, то это что-то можно вполне конфигурировать посредством кода и менять в коде, как и любой другой функциональный аспект. Если что-то не меняется никогда, то уносим это что-то в код. Для наглядности можно привести пример с http modules. Если мне не надо включать\отключать некий модуль на лету, то я предпочитаю конфигурировать модуль в коде, а не в конфигурационном файле. Вот как это может выглядеть.&lt;/p&gt;
&lt;p&gt;
Унифицированная инициализация всех сконфигурированных модулей в Global.asax:&lt;/p&gt;
&lt;pre class=&quot;brush: c-sharp; toolbar: false&quot;&gt;
public class MvcApplication : HttpApplication
{
 // ...
 public override void Init()
 {
  base.Init();
  foreach (var httpModule in ServiceLocator.Get&amp;lt;IEnumerable&amp;lt;IHttpModule&amp;gt;&amp;gt;())
  {
   httpModule.Init(this);
  }
 }
 // ...
&lt;/pre&gt;

&lt;p&gt;Регистрация модулей наряду с другими сервисами в контейнере:&lt;/p&gt;


&lt;pre class=&quot;brush: c-sharp; toolbar: false&quot;&gt;
protected override void Load(ContainerBuilder builder)
{
 // ...
 builder.RegisterType&amp;lt;ErrorHandlingModule&amp;gt;().AsImplementedInterfaces().SingleInstance();
 builder.RegisterType&amp;lt;RequestLoggingModule&amp;gt;().AsImplementedInterfaces().SingleInstance();
 // ...
} 

public class ErrorHandlingModule : IHttpModule
{
 // ...
}
&lt;/pre&gt;


&lt;p&gt;
Если же модулю нужны конфигурационные настройки, то:&lt;/p&gt;


&lt;pre class=&quot;brush: c-sharp; toolbar: false&quot;&gt;
public class RequestLoggingModule : IHttpModule
{
 // ...
 public RequestLoggingModule(ILoggingConfig loggingConfig)
 {
  _loggingConfig = loggingConfig;
 }
 // ...
}
&lt;/pre&gt;


&lt;p&gt;
Если вы не хотите, чтобы Вас, как разработчиков, постоянно просили исправлять параметры в конфигурационных файлах - &lt;strong&gt;делайте конфигурационный файл таковым, чтобы он был интуитивно понятен, понятен не только разработчику, а и другим участникам процесса разработки&lt;/strong&gt;: QA, release manager, support team. В практической плоскости это может значить не использовать самописные навороченные config sections, а предпочесть словарь ключей &amp;lt;appSettings&amp;gt;. При использовании &lt;a href=&quot;http://www.castleproject.org/components/dictionaryadapter/index.html&quot;&gt;Castle.DictionaryAdapter&lt;/a&gt; в качестве провайдера конфигурационных параметров, это может выглядеть следующим образом.&lt;/p&gt;


&lt;pre class=&quot;brush: xhtml&quot;&gt;
&amp;lt;appSettings&amp;gt; 
... 
 &amp;lt;add key=&amp;quot;Smtp_Host&amp;quot; value=&amp;quot;...&amp;quot;/&amp;gt;
 &amp;lt;add key=&amp;quot;Smtp_Port&amp;quot; value=&amp;quot;...&amp;quot;/&amp;gt;
 &amp;lt;add key=&amp;quot;Smtp_UserName&amp;quot; value=&amp;quot;...&amp;quot;/&amp;gt;
 &amp;lt;add key=&amp;quot;Smtp_Password&amp;quot; value=&amp;quot;...&amp;quot;/&amp;gt;
 &amp;lt;add key=&amp;quot;Smtp_EnableSsl&amp;quot; value=&amp;quot;...&amp;quot;/&amp;gt; 
... 
&amp;lt;/appSettings&amp;gt; 
&lt;/pre&gt;


&lt;p&gt;
Интерфейс для Castle configuration adapter:&lt;/p&gt;


&lt;pre class=&quot;brush: c-sharp; toolbar: false&quot;&gt;
public interface ISmtpEmailConfig
{
 [Key(&amp;quot;Smtp_Host&amp;quot;)]
 string Host { get; set; }
 [Key(&amp;quot;Smtp_Port&amp;quot;)]
 int Port { get; set; }
 [Key(&amp;quot;Smtp_UserName&amp;quot;)]
 string UserName { get; set; }
 [Key(&amp;quot;Smtp_Password&amp;quot;)]
 string Password { get; set; }
 [Key(&amp;quot;Smtp_EnableSsl&amp;quot;)]
 bool EnableSsl { get; set; }
}
&lt;/pre&gt;
&lt;p&gt;Dependency injection:&lt;/p&gt;

&lt;pre class=&quot;brush: c-sharp; toolbar: false&quot;&gt;
public SmtpEmailSender(ISmtpEmailConfig config)
{
 _config = config;
}
&lt;/pre&gt;


&lt;p&gt;
Магией инстанциирования прокси класса, и привязки параметров рулит Castle.DictionaryAdapter. Если интересно, то почитать &lt;a href=&quot;http://www.castleproject.org/components/dictionaryadapter/index.html&quot;&gt;здесь&lt;/a&gt; и &lt;a href=&quot;http://codebetter.com/benhall/2010/07/22/improving-testability-with-the-castle-dictionary-adapter/&quot;&gt;здесь&lt;/a&gt;. &lt;/p&gt;


&lt;h3&gt;Any.config&lt;/h3&gt;


&lt;p&gt;
Несмотря на то, что web.config’а либо app.config’а вполне хватает для хранения конфигурационных настроек, может возникнуть необходимость завести отдельный конфигурационный файл. Например, NLog.config – для хранения настроек логгирования при использовании &lt;a href=&quot;http://nlog-project.org/&quot;&gt;NLog&lt;/a&gt;. Либо приложение интегрируется с неким legacy компонентом, который конфигурируется исключительно посредством отдельного файла. Либо Вы хотите располагать одним файлом и применять его для разных приложения, таким образом уйти от необходимости поддержки нескольких однотипных файлов.
&lt;/p&gt;


&lt;h2&gt;Classic configuration management&lt;/h2&gt;


&lt;p&gt;
Итак, имеем несколько возможных вариантов хостинга конфигурационных параметров, и необходимость подменять их при развертывании в разные окружения. Рассмотрим способ управления конфигурацией, что называется “в лоб”. Конфигурационные файлы в проекте выглядят следующим образом:&lt;/p&gt;


&lt;p&gt;
&lt;img src=&quot;http://content.screencast.com/users/samoshkin/folders/Jing/media/04d83e50-176f-4347-8bb1-091f721a9a9e/project%20structure%20with%20configuration%20files.png&quot; /&gt;&lt;/p&gt;


&lt;p&gt;
Файлы Web.config и NLog.config содержат конфигурационные параметры актуальные для окружения на локальной машине разработчика, то есть ссылаются на локальные внешние сервисы, содержат учетные записи и пароли локальные для текущего домена, в незашифрованном виде, возможно выключенные настройки безопасности, логгирование на Debug уровне. При деплойменте, создается билд, подготавливается deployment package, загружается на целевой хост, развертывается. Если это в первый раз, то существующие конфигурационные файлы берутся за основу, и необходимые настройки вручную меняются. Если уже есть предыдущая версия приложения, то перезаписывается всё кроме конфигурационных файлов, а содержимое этих файлов мерджится вручную.&lt;/p&gt;


&lt;p&gt;
Минусы этого подхода очевидны:&lt;/p&gt;


&lt;ul&gt;
&lt;li&gt;Большой объем ручной работы. &lt;/li&gt;
&lt;li&gt;Невозможность быстрого развертывания приложения (скажем, до 5 минут). &lt;/li&gt;
&lt;li&gt;Необходимость помнить и не перезатереть существующие конфигурационные файлы при развертывании. &lt;/li&gt;
&lt;li&gt;Необходимость помнить что является конфигурационным файлом а что нет, и где они находятся. Речь идет о случае, когда кроме web.config у Вас есть еще другие конфигурационные файлы. &lt;/li&gt;
&lt;li&gt;Конфигурационные настройки для разных окружений хранятся непонятно где. Возможно, только в самих конфигурационных файлах на целевых хостах, возможно, где-то в заначке у того, кто занимается деплойментом. Если приложение в production окружении случайно перезатерлось вместе с конфигурационными файлами, а заначка пропала, то настройки восстановить будет очень сложно. &lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
Configuration management considerations&lt;/h2&gt;

&lt;p&gt;
Подумаем над тем, как могла бы выглядеть оптимальная управления конфигурационными параметрами и развертыванием. Рассмотрим следующие вопросы и компромиссы.&lt;/p&gt;

&lt;h3&gt;
Keep config settings in version control system&lt;/h3&gt;

&lt;p&gt;
На вопрос что хранить, а что не хранить в системе контроля версия – мой ответ обычно таков: “Хранить все, что относится к проекту”. Конфигурационные настройки, особенно для production окружения, жизненно важны для корректного функционирования приложения. Поэтому у меня нет никаких аргументов в пользу того, чтобы не хранить их в системе контроля версий. Если Вас волнуют вопросы безопасности, то можно зашифровать наиболее критические данные, либо же весь файл. &lt;/p&gt;

&lt;h3&gt;Duplication vs diff&lt;/h3&gt;

&lt;p&gt;
Если конфигурационные файлы для разных окружений отличаются не сильно, и нужно изменять только несколько параметров, то &lt;strong&gt;желательно не дублировать содержимое файлов, а описывать только diff&lt;/strong&gt;. Например, в Visual Studio 2010 в ASP.NET приложениях есть функция: &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/dd465326.aspx&quot;&gt;web.config transformation&lt;/a&gt;. Вы можете описать базовый web.config файл, а также файлы с преобразованиями для разных окружений (Web.Debug.config, Web.Release.config). &lt;/p&gt;


&lt;blockquote&gt;
&lt;p&gt;
Web.config files typically include settings that have to be different depending on which environment the application is running in. For example, you might have to change a database connection string or disable debugging when you deploy a Web.config file. For Web application projects, ASP.NET provides tools that automate the process of changing (transforming) Web.config files when they are deployed. For each environment that you want to deploy to, you create a transform file that specifies only the differences between the original Web.config file and the deployed Web.config file for that environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
Однако, если вы следуете принципу “Хранить в конфиге только тот минимум, который меняется”, то возможно все эти транформации будут излишни для Вас и только добавят головной боли. В таком случае, самый простой вариант – продублировать и поддерживать несколько конфигурационных файлов для разных окружений. В этом случае при добавлении нового параметра, его нужно будет не забыть продублировать во всех файлах. В качестве утешения может служить тот факт, что окружений обычно не так уж и много (development, staging, production).&lt;/p&gt;

&lt;p&gt;
Если же вы хотите возпользовать функцией &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/dd465326.aspx&quot;&gt;трансформации конфигурационных файлов&lt;/a&gt;, то стоит учесть, что из коробки она доступна только для ASP.NET приложений, и только если вы пользуетесь “Build Deployment Package” функцией, которую предлагает Visual Studio. Дальше в статье я покажу подход, который позволяет использовать config transformations для любого xml файла, будь то web.config, app.config, любой другой any.config, либо вообще файл, не имеющий отношения к конфигурации.&lt;/p&gt;

&lt;h3&gt;Automate as much as possible&lt;/h3&gt;


&lt;p&gt;
Имея несколько конфигурационных файлов для разных окружений, все задачи по развертыванию на целевой машине, должны осуществлятся автоматически, в том числе выбор и подмена конфигурационных файлов для нужного окружения. В идеале, задача сборки и развертывания должна сводится к одному единтсвенному действию (нажатию кнопки, либо запуску скрипта).&lt;/p&gt;

&lt;h2&gt;Solution&lt;/h2&gt;

&lt;p&gt;И так, перейдем к самой интересной части и посмотрим возможное решение на практике. Дано: &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Visual Studio 2010, .NET 4.0, ASP.NET MVC. &lt;/li&gt;
&lt;li&gt;Два конфигурационных файла: Web.config и NLog.config. &lt;/li&gt;
&lt;li&gt;Четыре целевых среды развертывания: local, development, staging, production. &lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;Compilation and configuration transformations&lt;/h3&gt;

&lt;p&gt;
В данном решении, я хочу показать использование web.config transformation возможности за пределами “Build Deployment Package” функции. Изначальное состояние структуры таково.&lt;/p&gt;

&lt;p&gt;
&lt;img src=&quot;http://content.screencast.com/users/samoshkin/folders/Jing/media/da8407fd-4f83-41a1-a23e-8469f4fbd153/initial_project_structure.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;
Удалим файлы Web.Debug.config и Web.Release.config, и добавим Web.dev.build.config, Web.staging.build.config и Web.production.build.config, которые будут содержать трасформации для целевых окружений. &lt;/p&gt;

&lt;p&gt;
&lt;img src=&quot;http://content.screencast.com/users/samoshkin/folders/Jing/media/25439741-5a00-4d8e-92ee-b9777b7f1e44/2012-04-29_1806_project_structure_with_env_configs.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;
Для того чтобы эти файлы отображались в виде дерева с Web.config в качестве родителя, нужно внести правки вручную в “.csproj” файл проекта.&lt;/p&gt;


&lt;pre class=&quot;brush: xhtml; toolbar: false&quot;&gt;
&amp;lt;Content Include=&amp;quot;Web.config&amp;quot;&amp;gt;
 &amp;lt;SubType&amp;gt;Designer&amp;lt;/SubType&amp;gt;
&amp;lt;/Content&amp;gt;
&amp;lt;Content Include=&amp;quot;Web.dev.build.config&amp;quot;&amp;gt;
 &amp;lt;DependentUpon&amp;gt;Web.config&amp;lt;/DependentUpon&amp;gt;
&amp;lt;/Content&amp;gt;
&amp;lt;Content Include=&amp;quot;Web.staging.build.config&amp;quot;&amp;gt;
 &amp;lt;DependentUpon&amp;gt;Web.config&amp;lt;/DependentUpon&amp;gt;
&amp;lt;/Content&amp;gt;
&amp;lt;Content Include=&amp;quot;Web.production.build.config&amp;quot;&amp;gt;
 &amp;lt;DependentUpon&amp;gt;Web.config&amp;lt;/DependentUpon&amp;gt;
&amp;lt;/Content&amp;gt;
&lt;/pre&gt;


&lt;p&gt;
По умолчанию в ASP.NET приложении, трансформации, описанные в том или ином файле (Web.Debug.config, Web.Release.config) применяются в зависимости от выбранного Build Configuration (Debug, Release), и результат трансформации перезаписывается в исходный web.config.&lt;/p&gt;

&lt;p&gt;
Изменим эту схему таким образом, чтобы во время компиляции, отрабатывали все транформации, описанные в наших *.build.config файлах, а в результате создавались новые файлы: *.env.config.&lt;/p&gt;

&lt;p&gt;
Для этого нам нужно импортировать MSBuild task, который отвечает за трансформацию конфигурационных файлов: “TransformXml”, который описан в сборке “Microsoft.Web.Publishing.Tasks.dll”. Далее, нам нужно вызвать эту задачу указав ей исходный конфигурационный файл, файл с трансформациями, и целевой файл с результатом. Также мы хотим, чтобы эта задача происходила автоматически во время компиляции. Вместо того, чтобы использовать Post-build event, воспользуемся предопределенным MSBuild target: “AfterBuild”. Вот как будет выглядеть “.csproj” файл.&lt;/p&gt;


&lt;pre class=&quot;brush: xhtml; toolbar: false&quot;&gt;
&amp;lt;Import Project=&amp;quot;$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets&amp;quot; /&amp;gt;
&amp;lt;UsingTask TaskName=&amp;quot;TransformXml&amp;quot; AssemblyFile=&amp;quot;$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll&amp;quot; /&amp;gt;
&amp;lt;Target Name=&amp;quot;AfterBuild&amp;quot;&amp;gt;
 &amp;lt;TransformXml Source=&amp;quot;$(ProjectDir)\Web.Config&amp;quot; Transform=&amp;quot;$(ProjectDir)\Web.dev.build.config&amp;quot; Destination=&amp;quot;$(ProjectDir)\Web.dev.env.config&amp;quot; StackTrace=&amp;quot;true&amp;quot; /&amp;gt;
 &amp;lt;TransformXml Source=&amp;quot;$(ProjectDir)\Web.Config&amp;quot; Transform=&amp;quot;$(ProjectDir)\Web.staging.build.config&amp;quot; Destination=&amp;quot;$(ProjectDir)\Web.staging.env.config&amp;quot; StackTrace=&amp;quot;true&amp;quot; /&amp;gt;
 &amp;lt;TransformXml Source=&amp;quot;$(ProjectDir)\Web.Config&amp;quot; Transform=&amp;quot;$(ProjectDir)\Web.production.build.config&amp;quot; Destination=&amp;quot;$(ProjectDir)\Web.production.env.config&amp;quot; StackTrace=&amp;quot;true&amp;quot; /&amp;gt;
&amp;lt;/Target&amp;gt;
&lt;/pre&gt;

&lt;p&gt;В результате компиляции получим желаемые файлы в папке проекта. &lt;/p&gt;

&lt;img src=&quot;http://content.screencast.com/users/samoshkin/folders/Jing/media/dad76a29-57f1-44d0-9706-1a380337e88a/2012-04-29_1820_compilation_output.png&quot; /&gt; 

&lt;p&gt;Проанализируем какие зависимости на внешние сборки имееются:&lt;/p&gt;


&lt;ul&gt;
&lt;li&gt;В первой строке находится import файла: “Microsoft.WebApplication.targets”. Этот import присуствует по умолчанию в проекте типа web application. Путь: “$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications”. &lt;/li&gt;
&lt;li&gt;В второй строке находится ссылка на “TransformXml” из сборки “Microsoft.Web.Publishing.Tasks.dll”. Путь: “$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web”. &lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;
В обеих случаях пути ссылаются на папку с msbuild дополнениями для Visual Studio 2010. На моей локальной машине это –“C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\”. Это значит, что приложение не будет компилироваться на машине, где не установлена Visual Studio 2010, и возможно ASP.NET. Мы же хотим сделать так, что приложение компилировалось без проблем на любой машине, где есть .NET Framework 4.0.&lt;/p&gt;


&lt;p&gt;
Первой идеей было скопировать файлы “Microsoft.WebApplication.targets” и “Microsoft.Web.Publishing.Tasks.dll” в некую папку, который находится в репозитории (речь идет о системе контроля версий) наряду с проектом, и также изменить &amp;quot;.csproj&amp;quot; файл, чтобы он ссылался на эти файлы по новому пути.&lt;/p&gt;


&lt;pre class=&quot;brush: xhtml; toolbar: false&quot;&gt;
&amp;lt;Import Project=&amp;quot;$(ProjectDir)\..\ms_build_extensions\Microsoft.WebApplication.targets&amp;quot; /&amp;gt;
&amp;lt;UsingTask TaskName=&amp;quot;TransformXml&amp;quot; AssemblyFile=&amp;quot;$(ProjectDir)\..\ms_build_extensions\Microsoft.Web.Publishing.Tasks.dll&amp;quot; /&amp;gt;
&lt;/pre&gt;


&lt;p&gt;Папка “ms_build_extensions” могла бы содержать следующие файлы.&lt;/p&gt;

&lt;p&gt;
&lt;img src=&quot;http://content.screencast.com/users/samoshkin/folders/Jing/media/d22ea333-db3d-4d5d-823d-7246478b79d2/2012-04-29_1845_ms_build_extensions.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;
Однако на ручное изменение директивы Import в первой строке Visual Studio реагирует предложением о конвертации приложения. &lt;/p&gt;

&lt;p&gt;
&lt;img src=&quot;http://content.screencast.com/users/samoshkin/folders/Jing/media/2265cfae-7f75-41b6-8240-4ad86dd63e63/2012-04-29_1843_vs_conversion_wizard.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Если продолжить, то в результате исходная Import директива будет воссоздана заново. &lt;/p&gt;

&lt;p&gt;
В результате, правильным решением будет переопределить переменную “$(MSBuildExtensionsPath32)” следующим образом.&lt;/p&gt;

&lt;pre class=&quot;brush: xhtml; toolbar: false&quot;&gt;
&amp;lt;Project ToolsVersion=&amp;quot;4.0&amp;quot; DefaultTargets=&amp;quot;Build&amp;quot; xmlns=&amp;quot;http://schemas.microsoft.com/developer/msbuild/2003&amp;quot;&amp;gt;
 &amp;lt;PropertyGroup&amp;gt;
  &amp;lt;!-- ... --&amp;gt;
  &amp;lt;MSBuildExtensionsPath32&amp;gt;..\..\ms_build_extensions&amp;lt;/MSBuildExtensionsPath32&amp;gt;
  &amp;lt;!-- ... --&amp;gt;
 &amp;lt;/PropertyGroup&amp;gt;
&lt;/pre&gt;


&lt;p&gt;
Относительные пути в данном случае работают, а вот применение переменных типа $(ProjectDir) - нет. Внутренняя структура папки “ms_build_extensions” должна в этом случае соотвествовать содержимому папки, которая была задана переменной “$(MSBuildExtensionsPath32)” до переопределения. Из содержимого я оставил лишь нужные мне “$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications” и “$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web”. В целом папка ms_build_extensions занимает 649KB, что позволяет нам хранить ее в системе контроля версий безо всяких колебаний.&lt;/p&gt;


&lt;p&gt;
Это все что нужно для того, чтобы в результате компиляции на выходе создавались нужные конфигурационные файлы. Если мы хотим, чтобы сгенерированные файлы игнорировались системой контроля версий, то это делается очень легко (на примере .gitignore файла):&lt;/p&gt;


&lt;pre class=&quot;brush: plain; toolbar: false&quot;&gt;
# Generated configuration files for different environments
*.env.config
&lt;/pre&gt;


&lt;h3&gt;Configuration transformation syntax&lt;/h3&gt;


&lt;p&gt;
В данной статье я не хочу углублятся в синтаксис и содержимое файлов трансформации. Детально со всеми возможностями синтаксиса можно ознакомится &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/dd465326.aspx&quot;&gt;здесь&lt;/a&gt;.&lt;/p&gt;


&lt;p&gt;
И все же, в качестве примера рассмотрим преобразование исходного web.config файла, в котором надо изменить значение параметра appSettings, заменить connection string, и включить customErrors mode=”On” для staging окружения. Другие конфигурационные настройки трогать не надо. Исходный web.config файл будет выглядеть следующим образом. &lt;/p&gt;


&lt;pre class=&quot;brush: xhtml; toolbar: false&quot;&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;
&amp;lt;configuration&amp;gt;
 &amp;lt;appSettings&amp;gt;
  &amp;lt;add key=&amp;quot;myKey&amp;quot; value=&amp;quot;myValue&amp;quot;/&amp;gt;
  &amp;lt;add key=&amp;quot;Security_SwitchToSecureConnection&amp;quot; value=&amp;quot;false&amp;quot;/&amp;gt;
 &amp;lt;/appSettings&amp;gt;

 &amp;lt;connectionStrings&amp;gt;
  &amp;lt;add name=&amp;quot;myConnectionSettings&amp;quot; connectionString=&amp;quot;Data Source=localhost;Initial Catalog=dev_db;Integrated Security=True&amp;quot;/&amp;gt;
 &amp;lt;/connectionStrings&amp;gt;

 &amp;lt;system.web&amp;gt;
  &amp;lt;customErrors mode=&amp;quot;Off&amp;quot; defaultRedirect=&amp;quot;~/Content/static/generic_error.htm&amp;quot;&amp;gt;
  &amp;lt;/customErrors&amp;gt;

  &amp;lt;sessionState mode=&amp;quot;InProc&amp;quot; timeout=&amp;quot;10&amp;quot; cookieless=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/sessionState&amp;gt;
 &amp;lt;/system.web&amp;gt;
&amp;lt;/configuration&amp;gt;
&lt;/pre&gt;


&lt;p&gt;Файл с преобразованиями “Web.staging.build.config” будет выглядеть следующим образом.&lt;/p&gt;


&lt;pre class=&quot;brush: xhtml; toolbar: false&quot;&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;
&amp;lt;configuration xmlns:xdt=&amp;quot;http://schemas.microsoft.com/XML-Document-Transform&amp;quot;&amp;gt;
 &amp;lt;appSettings&amp;gt;
  &amp;lt;add key=&amp;quot;Security_SwitchToSecureConnection&amp;quot; value=&amp;quot;true&amp;quot; xdt:Transform=&amp;quot;Replace&amp;quot; xdt:Locator=&amp;quot;Match(key)&amp;quot;  /&amp;gt;
 &amp;lt;/appSettings&amp;gt;

 &amp;lt;connectionStrings&amp;gt;
  &amp;lt;add name=&amp;quot;myConnectionSettings&amp;quot; 
connectionString=&amp;quot;Data Source=stg.host.net;Initial Catalog=stg_db;UserId=myUsername;Password=myPassword&amp;quot; 
xdt:Transform=&amp;quot;SetAttributes&amp;quot; 
xdt:Locator=&amp;quot;Match(name)&amp;quot;/&amp;gt;
 &amp;lt;/connectionStrings&amp;gt;

 &amp;lt;system.web&amp;gt;
  &amp;lt;customErrors mode=&amp;quot;On&amp;quot; xdt:Transform=&amp;quot;SetAttributes&amp;quot; &amp;gt;&amp;lt;/customErrors&amp;gt;    
 &amp;lt;/system.web&amp;gt;
&amp;lt;/configuration&amp;gt;
&lt;/pre&gt;


&lt;p&gt;
В результате после компиляции получим файл “Web.staging.env.config” с примененными преобразованиями.&lt;/p&gt;


&lt;pre class=&quot;brush: xhtml; toolbar: false&quot;&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;
&amp;lt;configuration&amp;gt;
 &amp;lt;appSettings&amp;gt;
  &amp;lt;add key=&amp;quot;myKey&amp;quot; value=&amp;quot;myValue&amp;quot;/&amp;gt;
  &amp;lt;add key=&amp;quot;Security_SwitchToSecureConnection&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;
 &amp;lt;/appSettings&amp;gt;

 &amp;lt;connectionStrings&amp;gt;
  &amp;lt;add name=&amp;quot;myConnectionSettings&amp;quot; connectionString=&amp;quot;Data Source=stg.host.net;Initial Catalog=stg_db;UserId=myUsername;Password=myPassword&amp;quot;/&amp;gt;
 &amp;lt;/connectionStrings&amp;gt;

 &amp;lt;system.web&amp;gt;
  &amp;lt;customErrors mode=&amp;quot;On&amp;quot; defaultRedirect=&amp;quot;~/Content/static/generic_error.htm&amp;quot;&amp;gt;
  &amp;lt;/customErrors&amp;gt;

  &amp;lt;sessionState mode=&amp;quot;InProc&amp;quot; timeout=&amp;quot;10&amp;quot; cookieless=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/sessionState&amp;gt;
 &amp;lt;/system.web&amp;gt;
&amp;lt;/configuration&amp;gt;
&lt;/pre&gt;


&lt;h3&gt;Transformations for Any.config&lt;/h3&gt;


&lt;p&gt;
Ничего не мешает использовать подобные преобразования для любого файла в формате XML. Например файл преобразования для NLog.config файла.&lt;/p&gt;


&lt;pre class=&quot;brush: xhtml; toolbar: false&quot;&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;
&amp;lt;nlog xmlns=&amp;quot;http://www.nlog-project.org/schemas/NLog.xsd&amp;quot;
xmlns:xdt=&amp;quot;http://schemas.microsoft.com/XML-Document-Transform&amp;quot;&amp;gt;
 &amp;lt;!-- ... --&amp;gt;
&amp;lt;/nlog&amp;gt;
&lt;/pre&gt;


&lt;h3&gt;Deployment and automatic config file selection &lt;/h3&gt;


&lt;p&gt;
Итак, на выходе имеем для каждого окружения файл преобразований (*.{target_environment}.build.config) и результирующий файл (*.{target_environment}.env.config). Осталось решить задачу выбора необходимого файла в зависимости от целевого окружения и подмены его в качестве обычного (*.config) файла.&lt;/p&gt;


&lt;p&gt;
В нашем случае всеми задачами развертывания занимается собственный скрипт: подготовка deployment package, копирование на удаленный хост, развертывание на удаленном хосте. Поэтому задача выбора конфигурационных файлов будет одной из задач по подготовке deployment package. Нужно выполнить следующие действия:&lt;/p&gt;


&lt;ol&gt;
&lt;li&gt;В зависимости от целевого {target_environment} определить нужный конфигурационный файл (*.{target_environment}.env.config). &lt;/li&gt;
&lt;li&gt;Скопировать его в качетве обычного конфигурационного файла: (*.{target_environment}.env.config) –&amp;gt; (*.config). &lt;/li&gt;
&lt;li&gt;Удалить все *.env.config. &lt;/li&gt;
&lt;li&gt;Удалить все *.build.config. &lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;
Ниже приведен отрывок Python кода, который выполняет эти задачи в рамках deployment скрипта.&lt;/p&gt;


&lt;pre class=&quot;brush: python; toolbar: false&quot;&gt;
config_prefix_for_target_env = &amp;quot;{0}.env.config&amp;quot;.format(target_env.environment_name)
config_template_for_target_env = &#39;*.&#39; + config_prefix_for_target_env
print(&amp;quot;Apply configuration files for &#39;{0}&#39; environment. Config file pattern is: &#39;{1}&#39;&amp;quot;.format(
 target_env.environment_name,
 config_template_for_target_env))
env_config_files = [file for file
 in walkfiles(build_package_path_temp)
 if fnmatch.fnmatch(os.path.basename(file), &amp;quot;*.env.config&amp;quot;)]
build_config_files = [file for file
 in walkfiles(build_package_path_temp)
 if fnmatch.fnmatch(os.path.basename(file), &amp;quot;*.build.config&amp;quot;)]

# iterate through *.evn.config and apply if config match target environment
# then remove all *.evn.config configs
for file in env_config_files:
 if fnmatch.fnmatch(os.path.basename(file), config_template_for_target_env):
  shutil.copyfile(file, file.replace(config_prefix_for_target_env, &#39;config&#39;))
 os.remove(file)

# remove all *.build.config
for file in build_config_files:
 os.remove(file)
&lt;/pre&gt;


&lt;h2&gt;
The end&lt;/h2&gt;


&lt;p&gt;
Сегодня был рассмотрен один из возможных вариантов управления конфигурацией веб приложения для разных окружений. При этом удалось уйти от необходимости ручной конфигурации приложения, внести конфигурационные параметры для разных окружений под управление системы контроля версий, сделать возможным применить &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/dd465326.aspx&quot;&gt;ASP.NET web.config transformations&lt;/a&gt; для генерации конфигурационных файлов для разных окружений, а также иметь возможность компилировать приложение на машине, на которой установлен лишь .NET framework 4.0, и осуществлять сборку и развертывание автоматически при помощи собственного скрипта. &lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/522373066169425452/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2012/04/web-configuration-management.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/522373066169425452'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/522373066169425452'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2012/04/web-configuration-management.html' title='Web application configuration management for different target environments'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-7546208966655929673</id><published>2011-12-08T23:35:00.001+02:00</published><updated>2011-12-08T23:36:05.301+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="TDD/BDD"/><title type='text'>Tech talk in Grid Dynamics on unit testing, TDD, BDD</title><content type='html'>&lt;p&gt;
Сегодня выступал с докладом в харьковском офисе компании &lt;a href=&quot;http://www.griddynamics.com/&quot;&gt;Grid Dynamics&lt;/a&gt; на тему &lt;a href=&quot;http://prezi.com/460cb2frxiq7/building-better-software/&quot;&gt;How to build better software with unit testing, TDD, BDD.&lt;/a&gt; Иначе говоря, как эти методологии помогают создавать ПО и делать его лучше. Пару слов о содержании презентации и самооценке результатов.
&lt;/p&gt;

&lt;h2&gt;
Содержание презентации
&lt;/h2&gt;

&lt;p&gt;
Саму презентацию решил делать при помощи сервиса &lt;a href=&quot;http://prezi.com/&quot;&gt;prezi.com&lt;/a&gt;. Несмотря на то, что были отзывы, что есть проблемы с демонстрацией ее с проектора. С проектором подружились, хотя за время презентации он пару раз отключался, и приходилось ждать некоторое время, после чего включать его снова.  
&lt;/p&gt;

&lt;p&gt;
Презентации, созданные в &lt;a href=&quot;http://prezi.com/&quot;&gt;prezi.com&lt;/a&gt; - построены на использовании технологии Flash. И в целом такие презентации выглядят поживее и подинамичней, чем их PowerPoint собратья. Можно создавать неплохие анимационные эффекты, которые могут удачно подчеркнуть идею, например делая zoom in/zoom out при переходе между master/detail слайдами. Главное, не переборщить с поворотами, особенно, если слайды меняются довольно часто - может закружится голова. Короче говоря, лучше один раз увидеть, чем что раз услышать.
&lt;/p&gt;

&lt;center&gt;&lt;div class=&quot;prezi-player&quot;&gt;&lt;style type=&quot;text/css&quot; media=&quot;screen&quot;&gt;.prezi-player { width: 600px; } .prezi-player-links { text-align: center; }&lt;/style&gt;&lt;object id=&quot;prezi_460cb2frxiq7&quot; name=&quot;prezi_460cb2frxiq7&quot; classid=&quot;clsid:D27CDB6E-AE6D-11cf-96B8-444553540000&quot; width=&quot;600&quot; height=&quot;500&quot;&gt;&lt;param name=&quot;movie&quot; value=&quot;http://prezi.com/bin/preziloader.swf&quot;/&gt;&lt;param name=&quot;allowfullscreen&quot; value=&quot;true&quot;/&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot;/&gt;&lt;param name=&quot;bgcolor&quot; value=&quot;#ffffff&quot;/&gt;&lt;param name=&quot;flashvars&quot; value=&quot;prezi_id=460cb2frxiq7&amp;amp;lock_to_path=0&amp;amp;color=ffffff&amp;amp;autoplay=no&amp;amp;autohide_ctrls=0&quot;/&gt;&lt;embed id=&quot;preziEmbed_460cb2frxiq7&quot; name=&quot;preziEmbed_460cb2frxiq7&quot; src=&quot;http://prezi.com/bin/preziloader.swf&quot; type=&quot;application/x-shockwave-flash&quot; allowfullscreen=&quot;true&quot; allowscriptaccess=&quot;always&quot; width=&quot;600&quot; height=&quot;500&quot; bgcolor=&quot;#ffffff&quot; flashvars=&quot;prezi_id=460cb2frxiq7&amp;amp;lock_to_path=0&amp;amp;color=ffffff&amp;amp;autoplay=no&amp;amp;autohide_ctrls=0&quot;&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class=&quot;prezi-player-links&quot;&gt;&lt;p&gt;&lt;a title=&quot;Building better software&quot; href=&quot;http://prezi.com/460cb2frxiq7/building-better-software/&quot;&gt;Building better software&lt;/a&gt; on &lt;a href=&quot;http://prezi.com&quot;&gt;Prezi&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/center&gt;


&lt;p&gt;
Изначально, идея была рассказать о &quot;Behavior driven development&quot;, но по мере создания презентации фокус сместился в сторону unit-тестов и test-driven development.
&lt;ol&gt;
  &lt;li&gt;Важность методик unit-testing, TDD, BDD. Их цель.&lt;/li&gt;
  &lt;li&gt;Unit testing: why, how, what&#39;s in practice?&lt;/li&gt;
  &lt;li&gt;Понятие test-first development. Шаги к его пониманию.&lt;/li&gt;
  &lt;li&gt;Test driven development. TDD as a software development process. Как использование TDD улучшает дизайн приложения, как помогает при рефакторинге, как помогает фокусироваться на поведение и другие вопросы?&lt;/li&gt;
   &lt;li&gt;Пример написания кода в BDD-стиле из проекта &lt;a href=&quot;https://github.com/samoshkin/MemcacheIt&quot;&gt;MemcacheIt&lt;/a&gt; с применением BDD-framework &lt;a href=&quot;https://github.com/samoshkin/SimpleSpec&quot;&gt;SimpleSpec&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;h2&gt;
Результаты
&lt;/h2&gt;
&lt;p&gt;
Если проанализировать результаты, то я пришел к таким выводам, а именно - что бы я изменил, если бы делал презентацию еще раз.
&lt;ol&gt;
&lt;li&gt;Сократить время выступления до 20 минут против порядка 40.&lt;/li&gt;
&lt;li&gt;Убрать раздел о unit-тестах. Все и так знают, что это такое. Сосредоточиться на TDD и BDD.&lt;/li&gt;
&lt;li&gt;Сократить объем теории и показывать больше примеров.&lt;/li&gt;
&lt;li&gt;Возможно, показать живой пример создания тестов, написания кода модуля, и запуска тестов в IDE, нежели показывать принт-скрины кода на слайдах.&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;
Как бы там ни было, спасибо ребятам из Grid Dynamics, которые проявили гостепреимство и посвятили почти час своего времени моему докладу. Надеюсь, было интересно.
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/7546208966655929673/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/12/techtalk-in-griddynamics-on-unit.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/7546208966655929673'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/7546208966655929673'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/12/techtalk-in-griddynamics-on-unit.html' title='Tech talk in Grid Dynamics on unit testing, TDD, BDD'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-4403204603691233644</id><published>2011-10-30T01:30:00.001+03:00</published><updated>2011-10-30T01:30:34.138+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="SimpleSpec"/><category scheme="http://www.blogger.com/atom/ns#" term="TDD/BDD"/><title type='text'>SimpleSpec - yet another BDD framework</title><content type='html'>&lt;p&gt;
Сегодня продолжим разговор о &lt;a href=&quot;http://www.code-magazine.com/article.aspx?quickid=0805061&amp;page=1&quot;&gt;Behavior-Driven Development&lt;/a&gt; - рассмотрим использование BDD-фреймворка &lt;a href=&quot;https://github.com/samoshkin/SimpleSpec&quot;&gt;SimpleSpec&lt;/a&gt; для описания спецификаций. Статья предполагает у читателя наличие базовых знаний в области Behavior-driven development. В одном из &lt;a href=&quot;http://samoshkin.blogspot.com/2011/09/bdd-explained-with-mspec.html&quot;&gt;моих прошлых постов&lt;/a&gt; описана идея BDD, его отличие от модульного тестирования, существующие фреймворки, такие как MSpec.
&lt;/p&gt;
&lt;p&gt;
Содержание:
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#what-is-simplespec&quot;&gt;Что такое SimpleSpec?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#key-ideas-and-goals&quot;&gt;Ключевые идеи и цели&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#goal-simplicity&quot;&gt;Простота&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#goal-readability&quot;&gt;Читабельность и документирующая функция&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#different-styles&quot;&gt;Поддержка разных стилей описания поведения&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#introduction&quot;&gt;Знакомство с Simple.Spec в scenario-behavior стиле&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#behavior-specification&quot;&gt;Behavior-scenario стиль&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#behavior-suite&quot;&gt;Behavior-suite стиль&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#scenario-validation&quot;&gt;Scenario validation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#hide-implementation-details&quot;&gt;Сокрытие реализации спецификаций&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Выводы&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;what-is-simplespec&quot;&gt;Что такое SimpleSpec?&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
&lt;blockquote&gt;
SimpleSpec is a simple BDD framework for .NET of xSpec type. Specifications are written at a unit level.
&lt;/blockquote&gt;
&lt;p&gt;
&lt;a href=&quot;https://github.com/samoshkin/SimpleSpec&quot;&gt;SimpleSpec&lt;/a&gt; относится к &lt;a href=&quot;http://stackoverflow.com/questions/307895/what-is-the-most-mature-bdd-framework-for-net/5586525#5586525&quot;&gt;xSpec&lt;/a&gt; типу фреймворков, использует &lt;a href=&quot;http://cakebaker.42dh.com/2009/05/28/given-when-then/&quot;&gt;given-when-then&lt;/a&gt; как язык для описания спецификаций на уровне модулей (unit-level). На данный момент, это тонкая обертка над &lt;a href=&quot;http://www.nunit.org/&quot;&gt;NUnit&lt;/a&gt;, которая используя возможности этого unit-testing фреймворка добавляет семантику BDD, позволяя разработчику описывать спецификации в стиле BDD. Не требует конфигурации, нестандартных unit-test раннеров, внешних зависимостей, инструментов - только &lt;strong&gt;NUnit&lt;/strong&gt; и &lt;strong&gt;SimpleSpec&lt;/strong&gt;. &lt;a href=&quot;https://github.com/samoshkin/SimpleSpec&quot;&gt;Проект размещен&lt;/a&gt; на github.
&lt;/p&gt;

&lt;p&gt;
Да, это еще один BDD-фреймворк, автором которого являюсь Я.
&lt;br/&gt;
- &quot;Очередной велосипед!?&quot; - скажете Вы? 
&lt;/br&gt;
- Скорее результат моего личного опыта работы с MSpec, осмысление его недостатоков и поиска более простого и лучшего BDD решения.
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;key-ideas-and-goals&quot;&gt;Ключевые идеи и цели&lt;/a&gt;
&lt;/h2&gt;
&lt;h3&gt;
&lt;a name=&quot;goal-simplicity&quot;&gt;Простота&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Сделать описание и реализацию спецификаций простым, естественным процессом, который бы не отталкивал и не отпугивал разработчиков от идеи BDD. Ограничить использование экзотических языковых конструкций.
&lt;/p&gt;
&lt;p&gt;
Сделать так, чтобы реализация BDD в виде фреймворка, не отпугивала разработчиков от самой идеи BDD, как это я часто наблюдаю, когда показываю смесь из статических классов, анонимных делегатов, конструкций &lt;code&gt;=()=&gt;&lt;/code&gt; из &lt;a href=&quot;https://github.com/machine/machine.specifications&quot;&gt;MSpec&lt;/a&gt;. После такой презентации говорить о высоких идеях behavior-driven development уже бесполезно. &lt;strong&gt;Фреймворк должен воплощать BDD идеи и быть простым.&lt;/strong&gt;
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;goal-readability&quot;&gt;Читабельность и документирующая функция&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Для этого надо прежде всего &lt;strong&gt;разделить описание спецификации и ее реализацию&lt;/strong&gt;. Один из недостатков классических тестов - это то, что тест представляет собой смесь, состоящую из подготовки состояния теста, вызовов API модуля, вспомогательных конструкций, механизмов тестирования (утверждений, моков, стабов).
&lt;/p&gt;

&lt;p&gt;
Подобная идея разделения не нова, фреймворки типа xBehave (&lt;a href=&quot;http://nbehave.codeplex.com/&quot;&gt;NBehave&lt;/a&gt;) так и делают - описание спецификации вынесено в отдельный файл (plain text + custom Domain specific language), а реализация спецификация в другом файле (unit test with C#), и постоянная синхронизация в нагрузку. Такой подход годится для описания спецификаций высокоуровневых пользовательских историй (user stories) в виде приемочных критериев (acceptance criteria), когда обязанности описания и реализации спецификаций разделены между разными ролями (QA, Developer). Однако использование его на уровне модулей (unit-level) привело бы к излишнему усложнению - нужно было бы решать вопросы поиска, синхронизации, реализации собственных DSL.
&lt;/p&gt;

&lt;p&gt;
Чтобы сделать вещи проще и ближе к программистам, спецификации должны быть описаны в одном месте (файл, класс), написаны на одном любимом нами языке, а для разделения описания и реализации должны использоваться конструкции и возможности языка.
&lt;/p&gt;

&lt;p&gt;
Достигнув этого разделения, &lt;strong&gt;мы получим спецификации, описанные в терминах бизнеса, не обремененные деталями реализации, которые позволят воплотить в жизнь идею &quot;тесты как документация&quot;&lt;/strong&gt;, которая задекларирована еще как атрибут классического модульного тестирования, но с трудом воплощаемая в жизнь.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;different-styles&quot;&gt;Поддержка разных стилей описания поведения&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
&lt;a href=&quot;http://stevenharman.net/blog/archive/2009/05/27/toward-a-better-use-of-context-specification.aspx&quot;&gt;Context-specification&lt;/a&gt; - это общепринятый стиль описания спецификаций, при котором описывается набор сценариев, каждый из которых содержит набор поведений, которые в наблюдаются в том или ином сценарии. На мой взгляд, сontext-specification - несколько неудачное название, которое не отражает сути. Я предпочитаю называть такой стиль - &lt;strong&gt;Scenario-behavior&lt;/strong&gt;.
&lt;/p&gt;
&lt;p&gt;
Однако не всегда удобно описывать спецификации в модели &quot;cценарий - наблюдаемые поведения&quot;. SimpleSpec поддерживает также модель, при которой описывается поведение, которое наблюдается в тех или иных сценариях (&lt;strong&gt;Behavior-scenario&lt;/strong&gt;), либо просто описывается набор поведений без явного выделения сценария (&lt;strong&gt;Behavior suite&lt;/strong&gt;).
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;introduction&quot;&gt;Знакомство с Simple.Spec в scenario-behavior стиле&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
Познакомимся с Simple.Spec на примере спецификации поведения класса, который анализирует посещаемость сайта, в частности для текущего дня позволяет узнать как изменилась посещаемость сайта по отношению к предыдущему дню.

&lt;img src=&quot;http://content.screencast.com/users/samoshkin/folders/Jing/media/794fe4c3-c069-4d0a-aa4d-232155836f74/Simple.Spec-site-attendance-domain-sample.png&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;
Спецификация с ключевыми моментами выглядит так:
&lt;img src=&quot;http://content.screencast.com/users/samoshkin/folders/Jing/media/cb3f8f84-ee3f-4484-8b07-ba8841a2511e/Simple.Spec-scenario-sample.png&quot; /&gt;
&lt;p&gt;

&lt;p&gt;
&lt;strong&gt;1. This is scenario specification.&lt;/strong&gt;
&lt;br /&gt;
Спецификация описана в стиле scenario-behavior (context-specification), то есть двигаемся от сценария к наблюдаемым поведениям. Для этого помечаем класс атрибутом &lt;code&gt;Scenario.Spec&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;2. Give a short name to the scenario.&lt;/strong&gt;
&lt;br /&gt;
Имя класса используем в качестве краткого описания сценария: &lt;code&gt;when_calculating_visit_count_variation&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;3. Use base class for common stuff.&lt;/strong&gt;
&lt;br /&gt;
В базовой класс &lt;code&gt;with_site_attendance_analyzer&lt;/code&gt; выносим общие вещи, которые повторяются от сценария к сценарию: вспомогательные методы, создание нужных объектов. Однако надо соблюдать баланс между желанием вынести все общее за скобки и самодостаточностью спецификации. В идеале, спецификация не должна быть перегружена повторяющимися деталями и реализацией, но в тоже время для понимания спецификации должно быть достаточно взглянуть на один класс спецификации, без необходимости перемещаться между классами. Некоторые разработчики очень любят строить глубокую иерархию, однако я бы рекомендовал использование максимум одного уровня иерархии. Большее количество уровней излишне усложнит поддержку спецификаций.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;4. Give a detailed description of the scenario in constructor.&lt;/strong&gt;
&lt;br /&gt;
Подробно описываем сценарий в конструкторе. Описание сценария состоит из задания контекста (given) и вызова действия (when).
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;5. Setup context using givens.&lt;/strong&gt;
&lt;br /&gt;
Используем одну или больше конструкций &lt;code&gt;Given(...)&lt;/code&gt; для задания контекста.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;6. Custom Domain specific language using methods and delegates.&lt;/strong&gt;
&lt;br /&gt;
Обычно задание контекста - это именно то, что чаще всего повторяется от сценария к сценарию. Здесь имеет смысл смоделироть логику создания контекста в виде методов, переместить их в базовый класс и вызывать их с нужными параметрами. &lt;code&gt;attendance_statistics_analyzer(...)&lt;/code&gt; - создает объект &lt;code&gt;SiteAttendanceAnalyzer&lt;/code&gt;, собственно это то, что мы тестируем, и его зависимости. &lt;code&gt;attendance_statistics(...)&lt;/code&gt; - задает данные о статистике посещения сайта. Подобный подход позволяет нам убить трех зайцев: выделить повторяющиеся вещи в базовый класс и разгрузить спецификации, разделить описание спецификации и ее реализацию, создать собственный Domain specific language для описания конкретного сценария.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;7. Specify action using when.&lt;/strong&gt;
&lt;br /&gt;
Используем контрукцию &lt;code&gt;When(...)&lt;/code&gt; для описания действия. Сценарий может содержать только одну конструкцию &lt;code&gt;When&lt;/code&gt;. Если посмотреть на код, то здесь мы используем анонимный делегат. Было бы излишним усложнением для одной строчки кода создавать отдельный метод. Более того, вызов метода &lt;code&gt;analyzer.CalculateVisitVariation(...)&lt;/code&gt; и так говорит сам за себя.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;8. Specify one or more observable behaviors.&lt;/strong&gt;
&lt;br /&gt;
После того как сценарий описан, описываем наблюдаемые поведения. Для того чтобы пометить поведение, используется атрибут &lt;code&gt;Behavior&lt;/code&gt;. Несколько поведений в сценарии - это вполне валидная ситуация, более того в context-specification стиле это даже поощряется, так что не нужно ограничивать себя одним поведением на сценарий. В данном стиле cценарий - это то, что является первичным, и то, от чего следует отталкиваться. 
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;9. Do not use NUnit assertions. To be completely BDD&#39;y use shoulds.&lt;/strong&gt;
&lt;br /&gt;
Так или иначе, спецификация должна содержать фазу проверки. Вполне можно использовать механизмы &lt;code&gt;Assert.That&lt;/code&gt;, которые есть в NUnit, но я бы рекомендовал использовать любую библиотеку для проверок в BDD стиле, которая предлагает конструкции типа &lt;code&gt;Should(...)&lt;/code&gt; (&lt;a href=&quot;http://fluentassertions.codeplex.com/&quot;&gt;Fluent.Assertions&lt;/a&gt;, &lt;a href=&quot;https://github.com/remi/NUnit.Should&quot;&gt;NUnit.Should&lt;/a&gt;, и др.).
&lt;/p&gt;

&lt;p&gt;
Результаты запуска тестов в ReSharper выглядят так:
&lt;img src=&quot;http://content.screencast.com/users/samoshkin/folders/Jing/media/5d203d32-bf68-4e8c-91ce-35771dff4b0f/Simple.Spec-specification-result-in-resharper-runner.png&quot; /&gt;
На выходе получена спецификация, которая обладает следующими свойствами:
&lt;ul&gt;
&lt;li&gt;
Описание спецификации отделено от реализации. Все детали реализации и тестирования вынесены в методы.
&lt;/li&gt;
&lt;li&gt;
Для структурирования спецификаций используется язык &lt;code&gt;given-when-then&lt;/code&gt;, а также применяется собственный DSL, засчет именования методов.
&lt;/li&gt;
&lt;li&gt;
Ориентированность на поведение, а не на API-модуля. Так или иначе, вызовы API модуля никуда не делись, но они являются деталями реализации и не интересуют нас в первую очередь, а поэтому скрыты.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;behavior-specification&quot;&gt;Behavior-scenario стиль&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
Подход scenario-behavior, описанный выше применим в большинстве случаев. Однако не всегда. Допустим, в примере с анализом посещаемости сайта нужно провалидировать данные о посещаемости. Бизнес-требования таковы, что данные о посещаемости будут считаться невалидными, если изменение посещаемости превышает заданный порог или если нет данных по некоторым дням. Если исходить из стиля scenario-behavior, то для каждого &quot;если&quot; нужно создать класс и описать сценарий. В каждом таком сценарии мы хотим проверить, что валидация либо пройдет успешно либо не пройдет. В таком случае более проще и компактней будет сначала описать поведение, и лишь потом перейти к списку сценариев, в которых это поведение наблюдается.
&lt;/p&gt;

&lt;pre class=&quot;brush:csharp&quot;&gt;
[Behavior.Spec]
public class attendance_data_should_not_pass_validation : with_site_attendance_analyzer
{
    public attendance_data_should_not_pass_validation()
    {
        Given(attendance_statistics_analyzer);
        CouldFailWith&amp;lt;ValidationException&amp;gt;().Verify(ShouldFail);
    }

    [Scenario]
    public void when_visit_variation_threshold_is_exceeded()
    {
        Given(() =&amp;gt; attendance_statistics(
            new AttendanceSummary(new DateTime(2011, 7, 1), 10, TimeSpan.FromMinutes(1), 2, 13),
            new AttendanceSummary(new DateTime(2011, 7, 2), 15, TimeSpan.FromMinutes(2), 3, 15),
            new AttendanceSummary(new DateTime(2011, 7, 3), 16, TimeSpan.FromMinutes(3), 4, 16)));
        Given(() =&amp;gt; analyzer.LoadClientStatistics(resource, new DateTime(2011, 1, 1), new DateTime(2012, 1, 1)));
        When(() =&amp;gt; analyzer.Validate(0.4));
    }

    [Scenario]
    public void when_statistics_is_not_contiguous_with_some_days_missing()
    {
        Given(() =&amp;gt; attendance_statistics(
            new AttendanceSummary(new DateTime(2011, 7, 1), 10, TimeSpan.FromMinutes(1), 2, 13),
            new AttendanceSummary(new DateTime(2011, 7, 3), 11, TimeSpan.FromMinutes(2), 3, 15),
            new AttendanceSummary(new DateTime(2011, 7, 4), 12, TimeSpan.FromMinutes(3), 4, 16)));
        Given(() =&amp;gt; analyzer.LoadClientStatistics(resource, new DateTime(2011, 1, 1), new DateTime(2012, 1, 1)));
        When(() =&amp;gt; analyzer.Validate(0.4));
    }
}
&lt;/pre&gt;
&lt;p&gt;
&lt;strong&gt;Подход behavior-scenario противоположен подходу scenario-behavior.&lt;/strong&gt; Имя класса отражает поведение. Класс помечен атрибутом &lt;code&gt;Behavior.Spec&lt;/code&gt;. В конструкторе задается контекст, общий для всех сценариев, хотя в данном случае это необязательно и сделано, чтобы разгрузить последующее описание сценариев от повторяющихся элементов, которые не добавляют смысла. Ключевой момент - это описание поведения один раз в конструкторе. Далее описываются сценарии, в которых наблюдается данное поведение, соотвествующие методы помечаются атрибутом &lt;code&gt;Scenario&lt;/code&gt;. Сценарий, как и прежде, состоит из задания контекста (given) и выполнения действия (when).
&lt;/p&gt;
&lt;p&gt;
Вот как выглядит результаты выполнения спецификации:
&lt;img class=&quot;embeddedObject&quot; src=&quot;http://content.screencast.com/users/samoshkin/folders/Jing/media/5411bf38-d4e1-481f-9632-4e54c248b0d8/Simple.Spec-behavior-specification-style.png&quot; border=&quot;0&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Стиль behavior-specification стоит использовать только тогда, когда есть фиксированный малый набор поведений, которые проявляются в большом количестве сценариев.&lt;/strong&gt; Иначе, стоит придерживаться scenario-behavior стиля.
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;behavior-suite&quot;&gt;Behavior-suite стиль&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
Еще один стиль описания спецификаций - &lt;strong&gt;это простой плоский список поведений (behavior suite)&lt;/strong&gt;. Его стоит применять тогда, когда отдельное выделение сценария приведет к излишнему усложнению, либо когда количество сценариев и поведений приблизительно одинаково и все поведения уникальны, либо когда все слишком просто, чтобы заморачиваться со сценариями. Есть плоский список поведений, в котором каждое поведение - это неразрывная связка сценария и собственно поведения.
&lt;/p&gt;
&lt;pre class=&quot;brush:csharp&quot;&gt;
[Behavior.Spec]
public class device_collection_behaviors
    : specification
{
    private DeviceCollection Devices;
    private Device A1Device = new Device(1, &amp;quot;A1&amp;quot;, &amp;quot;A&amp;quot;, &amp;quot;X&amp;quot;, null);
    private Device A1DuplicateDevice = new Device(1, &amp;quot;A1&amp;quot;, &amp;quot;A&amp;quot;, &amp;quot;Z&amp;quot;, null);
    private Device A2Device = new Device(1, &amp;quot;A2&amp;quot;, &amp;quot;A&amp;quot;, &amp;quot;Y&amp;quot;, null);
    private Device B1Device = new Device(1, &amp;quot;B1&amp;quot;, &amp;quot;B&amp;quot;, &amp;quot;X&amp;quot;, null);

    public device_collection_behaviors()
    {
        Given(() =&amp;gt; Devices = new DeviceCollection(new[]
        {
            A1Device,
            A1DuplicateDevice,
            A2Device, 
            B1Device
        }));
    }

    [Behavior]
    public void should_get_devices_by_name()
    {
        Devices.GetByName(&amp;quot;A1&amp;quot;).Should().BeEquivalentTo(A1Device, A1DuplicateDevice);
        Devices.GetByName(&amp;quot;B1&amp;quot;).Should().BeEquivalentTo(B1Device);
        Devices.GetByName(&amp;quot;C&amp;quot;).Should().BeEmpty();
    }

    [Behavior]
    public void should_get_devices_by_type()
    {
        Devices.GetByType(&amp;quot;A&amp;quot;).Should().BeEquivalentTo(A1Device, A1DuplicateDevice, A2Device);
        Devices.GetByName(&amp;quot;B&amp;quot;).Should().BeEquivalentTo(B1Device);
        Devices.GetByName(&amp;quot;C&amp;quot;).Should().BeEmpty();
    }

    [Behavior]
    public void should_get_devices_by_supplier_name()
    {
        Devices.GetBySupplierName(&amp;quot;X&amp;quot;).Should().BeEquivalentTo(A1Device, B1Device);
        Devices.GetBySupplierName(&amp;quot;Y&amp;quot;).Should().BeEquivalentTo(A2Device);
        Devices.GetBySupplierName(&amp;quot;C&amp;quot;).Should().BeEmpty();
    }
}
&lt;/pre&gt;
В примере выше описывается поведение класса-коллекции устройств - &lt;code&gt;DeviceCollection&lt;/code&gt;. В конструкторе задан общий контекст. В методах, помеченных атрибутом &lt;code&gt;Behavior&lt;/code&gt;, совмещен вызов API модуля и проверка результатов. По внешнему виду, этот подход больше напоминает классические юнит тесты.
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;scenario-validation&quot;&gt;Scenario validation&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
Из множества поведений, стоит отдельно выделить одно: проверка сценария на корректность или некорректность. Другими словами, проверка того, что вызов модуля либо завершится ошибкой, либо пройдет успешно. 
&lt;/p&gt;
&lt;p&gt;
Рассмотрим такой сценарий: подсчет изменения посещаемости сайта по отношению к предыдущему дню для первого дня собранной статистики. Очевидно, подсчитывать изменение статистики для первого дня не имеет смысла, поэтому такой сценарий считается невалидным.
&lt;/p&gt;
&lt;pre class=&quot;brush:csharp&quot;&gt;
[Scenario.Spec]
public class when_calculating_visit_count_variation_for_a_day_with_no_attendance_data 
    : with_site_attendance_analyzer
{
    public when_calculating_visit_count_variation_for_a_day_with_no_attendance_data()
    {
        Given(attendance_statistics_analyzer);
        Given(() =&amp;gt; attendance_statistics(
            new AttendanceSummary(new DateTime(2011, 6, 1), 4, TimeSpan.FromMinutes(1), 2, 12),
            new AttendanceSummary(new DateTime(2011, 6, 3), 8, TimeSpan.FromMinutes(2), 10, 123),
            new AttendanceSummary(new DateTime(2011, 6, 4), 5, TimeSpan.FromSeconds(50), 4, 6)));
        Given(() =&amp;gt; analyzer.LoadClientStatistics(resource, new DateTime(2011, 6, 1), new DateTime(2011, 6, 3)));
        When(() =&amp;gt; analyzer.CalculateVisitVariation(new DateTime(2011, 6, 2)));
        CouldFailWith&amp;lt;ArgumentException&amp;gt;();
    }
&lt;/pre&gt;
&lt;p&gt;
Подобно примеру ранее, в конструкторе класса описан сценарий. Обратите внимание на строчку.
&lt;pre class=&quot;brush:csharp&quot;&gt;
        CouldFailWith&amp;lt;ArgumentException&amp;gt;();
&lt;/pre&gt;
Мы не проверяем, что сценарий корректен или некорректен, мы лишь декларируем, что сценарий может быть некорректен.
&lt;/p&gt;
&lt;p&gt;
А далее два пути: либо проверка на корректность, либо на некорректность.
&lt;pre class=&quot;brush:csharp&quot;&gt;
    [Behavior]
    public void should_fail()
    {
        Then(() =&amp;gt; ShouldFail(&quot;does not have attendance statistiscs&quot;));
    }
&lt;/pre&gt;
&lt;pre class=&quot;brush:csharp&quot;&gt;
    [Behavior]
    public void should_not_fail()
    {
        Then(() =&amp;gt; ShouldNotFail());
    }
&lt;/pre&gt;
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;hide-implementation-details&quot;&gt;Сокрытие реализации спецификаций&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
Еще один элемент паззла - это наш базовый класс, в который мы выносим те или иные вспомогательные конструкции.
&lt;pre class=&quot;brush:csharp&quot;&gt;
public class with_site_attendance_analyzer : specification
{
    public SiteAttendanceAnalyzer analyzer;
    public Mock&amp;lt;IAttendanceStatisticsProvider&amp;gt; billingDataProvider;
    public Resource resource;

    protected void attendance_statistics_analyzer()
    {
        resource = new Resource { Uri = new Uri(&amp;quot;http://bdd.com/simplespect&amp;quot;) };
        billingDataProvider = new Mock&amp;lt;IAttendanceStatisticsProvider&amp;gt;();
        analyzer = new SiteAttendanceAnalyzer(billingDataProvider.Object);
    }

    protected void attendance_statistics(params AttendanceSummary[] attendances)
    {
        billingDataProvider
            .Setup(provider =&amp;gt; provider.FetchStatistics(resource, It.IsAny&amp;lt;DateTime&amp;gt;(), It.IsAny&amp;lt;DateTime&amp;gt;()))
            .Returns(attendances);
    }

    protected void attendance_statistics(
        DateTime startDate, 
        DateTime endDate, 
        params AttendanceSummary[] attendances)
    {
        billingDataProvider
            .Setup(provider =&amp;gt; provider.FetchStatistics(resource, startDate, endDate))
            .Returns(attendances);
    }
}
&lt;/pre&gt;
Можно сказать, что стоит так или иначе скрывать все то, что не является описанием спецификации. Сокрытие осущетвляется путем выделения методов.
&lt;ul&gt;
&lt;li&gt;
Код, который повторяется от сценария к сценарию, и не привносит смысловой нагрузки.
&lt;/li&gt;
&lt;li&gt;
Детали реализации спецификации, низкоуровневая работа с API модуля.
&lt;/li&gt;
&lt;li&gt;
Код, который представляет собой механизмы тестирования, к примеру, создание и конфигурация фейков.
&lt;/li&gt;
&lt;/ul&gt;
Снова же, &lt;strong&gt;основная цель - разделение описания и реализации спецификации&lt;/strong&gt;. Выделение базового класса - лишь механизм предоставления доступа разным сценариям к одним и тем же элементам. Если у вас всего два сценария, то скорее всего выделение базового класса неоправдано.
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;conclusion&quot;&gt;Выводы&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
Неважно какой фреймворк вы используете для описания спецификаций в BDD стиле. Важно понимать, что BDD - это прежде всего идея, подход, изменение мышления. &lt;strong&gt;Если выразить идею BDD одним предложением, то я бы сказал, что это переход от тестирования приложения к описанию поведения приложения&lt;/strong&gt;. Понимание этого дает почву для рождения новых идей, и позволяет принимать осмысленные решения в спорных ситуациях. Какой BDD фреймворк или библиотеку Вы используете или не используете не важно, используйте то, что Вам больше всего нравится, с чем Вам проще работать, и то, что считаете оправданным.
&lt;/p&gt;
&lt;p&gt;
Интересно также рассматривать идею Behavior-driven development в связке с &lt;a href=&quot;http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215&quot;&gt;Domain-driven design&lt;/a&gt; (DDD). &lt;strong&gt;DDD - позволяет коду модудей говорить на языке предметной области, BDD - позволяет коду тестов/cпецификаций говорить на языке предметной области.&lt;/strong&gt; Идея BDD удачно гармонирует c DDD, но в тоже время вполне можно использовать BDD без применения DDD.
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/4403204603691233644/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/10/simplespec-bdd-framework.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/4403204603691233644'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/4403204603691233644'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/10/simplespec-bdd-framework.html' title='SimpleSpec - yet another BDD framework'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-2927865299094804025</id><published>2011-10-06T02:05:00.003+03:00</published><updated>2011-10-07T08:21:54.886+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AOP"/><category scheme="http://www.blogger.com/atom/ns#" term="SNAP"/><title type='text'>SNAP - Keeping aspects in a container</title><content type='html'>&lt;p&gt;
Hi, this is my first blog post in English, as I want to share it with an English-speaking audience too. Today I want to speak about aspect oriented framework named SNAP, build simple logging aspect, then describe why it is bad to let SNAP manage aspects instances on its own, and how it restricts us as a developers in building complex aspects. Then I show how I&#39;ve solved the problem described.
&lt;/p&gt;

&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;

&lt;h2&gt;
SNAP - Simple .NET Aspect-Oriented Programming
&lt;/h2&gt;
&lt;p&gt;
Recently I&#39;ve been using &lt;a href=&quot;http://www.simpleaspects.com/&quot;&gt;SNAP&lt;/a&gt;, a yet another Aspect Oriented Programming framework for .NET. I&#39;ve had a positive experience, and here are some key points to describe it.
&lt;ol&gt;
&lt;li&gt;
Does not use code weaving, does not rewrite IL code to get interception mechanism to work. Instead uses &lt;a href=&quot;http://www.castleproject.org/dynamicproxy/index.html&quot;&gt;Castle.DynamicProxy&lt;/a&gt; library features like subclassing, interface proxying.
&lt;/li&gt;
&lt;li&gt;
Ability to intercept only selected methods, instead of intercepting all methods of a given class.
&lt;/li&gt;
&lt;li&gt;
Integrates with a favorite DI/IoC container. Currently five implementations are supported: &lt;a href=&quot;http://code.google.com/p/autofac/&quot;&gt;Autofac&lt;/a&gt;, &lt;a href=&quot;http://docs.castleproject.org/Windsor.MainPage.ashx&quot;&gt;Castle.Windsor&lt;/a&gt;, &lt;a href=&quot;http://structuremap.net/structuremap/&quot;&gt;StructureMap&lt;/a&gt;, &lt;a href=&quot;http://ninject.org/&quot;&gt;NInject&lt;/a&gt;, &lt;a href=&quot;http://code.google.com/p/linfu/&quot;&gt;LinFu&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
Provides unified model to program aspects regardless selected DI/IoC container implementation.
&lt;/li&gt;
&lt;li&gt;
Lightweight and simple.
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;
Sounds good, isn&#39;t it? If you are new to SNAP, I&#39;d recommend to take a look at it. Here is a &lt;a href=&quot;http://www.simpleaspects.com/&quot;&gt;home page&lt;/a&gt;, and here is a &lt;a href=&quot;https://github.com/TylerBrinks/Snap&quot;&gt;source code&lt;/a&gt;, and &lt;a href=&quot;http://nuget.org/List/Search?packageType=Packages&amp;searchCategory=All+Categories&amp;searchTerm=snap&amp;sortOrder=package-download-count&amp;pageSize=10&quot;&gt;nuget packages&lt;/a&gt; are also there.

&lt;blockquote&gt;
&lt;/p&gt;
Snap (Simple .NET Aspect-Oriented Programming) was conceived out of a recurring need for flexible AOP in the wake of PostSharp&#39;s metamorphosis into a commercial product.
&lt;/p&gt;

&lt;p&gt;
Not to be mistaken with a code weaver (i.e. a compile-time tool), Snap is a fully run-time executed library. Snap aims to pair with your Inversion of Control (IoC) container of choice to deliver a powerful aspect-oriented programming model.
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/p&gt;

&lt;h2&gt;
Logging aspect implementation
&lt;/h2&gt;

&lt;/p&gt;
To show SNAP in a work, lets consider a simple scenario. Assume, we have a class &lt;code&gt;CalculateRouteCommandHandler&lt;/code&gt;, which calculates a route between given two points A and B.
&lt;pre class=&quot;brush:csharp&quot;&gt;
public class CalculateRouteCommandHandler
{
    public virtual void HandleCommand(CalculateRouteCommand command)
    {
        try
        {
            var intermediateroutes = CalculateIntermediateRoutes();
            foreach (var intermediateroute in intermediateroutes)
            {
                CalculateDistance(intermediateroute);
                CalculateTransport(intermediateroute);
                FindAlternatives(intermediateroute);
            }
        }
    }
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
Next, we want to log command started and completed events. Logging is a &lt;a href=&quot;http://en.wikipedia.org/wiki/Cross-cutting_concern&quot;&gt;cross-cutting concern&lt;/a&gt;, so being aspect-oriented we need to encapsulate logging functionality in the form of aspect. From a SNAP implementation viewpoint, aspect is a class which inherits from the &lt;code&gt;MethodInterceptor&lt;/code&gt; base class, and decorates target class with a custom behavior.
&lt;pre class = &quot;brush:csharp&quot;&gt;
public class TraceCommand : MethodInterceptor
{
    public override void InterceptMethod(IInvocation invocation, MethodBase method, Attribute attribute)
    {
        var command = (Command) invocation.Arguments.First();
        var targetAttribute = (TraceCommandAttribute) attribute;

        // trace command start
        if(targetAttribute.TraceStart)
        {
           Logger.Info(&quot;Command started: {0}&quot;.FormatString(command));
        }

        // call target method
        invocation.Proceed();

        // trace command completion
        if(targetAttribute.TraceComplete)
        {
           Logger.Info(&quot;Command completed: {0}&quot;.FormatString(command));
        }
    }
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
Next we need to apply the aspect to the business code. We need an attribute class, and then apply it to the target method.
&lt;pre class=&quot;brush:csharp&quot;&gt;
public class TraceCommandAttribute : MethodInterceptAttribute
{
    // these are settings which influence aspect behavior
    public bool TraceStart { get; set; }
    public bool TraceComplete { get; set; }
}
&lt;/pre&gt;
&lt;pre class=&quot;brush:csharp&quot;&gt;
// do not trace command start event, just command completion one
[TraceCommand(TraceStart = false)]
public virtual void HandleCommand(CalculateRouteCommand command)
{
    // business logic goes here
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
Next step is to configure SNAP framework to let it know about our aspects and attributes, and what components do we want to intercept.

&lt;pre class=&quot;brush:csharp&quot;&gt;
// using Autofac as a container implementation
var builder = new ContainerBuilder();

// configure aspects
SnapConfiguration.For(new AutofacAspectContainer(builder)).Configure(c =&gt;
{
    c.IncludeNamespace(&quot;MyCommands.*&quot;);
    c.Bind&amp;lt;TraceCommand&amp;gt;().To&amp;lt;TraceCommandAttribute&amp;gt;();
});

// configure application components in container
builder.RegisterType&amp;lt;CalculateRouteCommandHandler&amp;gt;().As&amp;lt;ICommandHandler&amp;lt;CalculateRouteCommand&amp;gt;&amp;gt;();
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;And that&#39;s all you need to get SNAP to work.&lt;/strong&gt;
&lt;/p&gt;

&lt;h2&gt;
Aspect management problem
&lt;/h2&gt;

&lt;p&gt;
So far so good. But you say: &quot;Hey, this is a very simple scenario. Show us something more practical.&quot;. Ok. Let&#39;s try to identify some SNAP pitfalls with a bit more difficult scenario.
&lt;/p&gt;

&lt;/&gt;
&lt;code&gt;LoggingAspect&lt;/code&gt; uses static &lt;code&gt;Logger&lt;/code&gt; class to get an access to logging functionality. I don&#39;t like logging to be exposed via static &lt;code&gt;Logger&lt;/code&gt; class. I think, you are too. Instead I redesign it into &lt;code&gt;ILoggingService&lt;/code&gt; service.
&lt;pre class=&quot;brush:csharp&quot;&gt;
public interface ILoggingService
{
    void Debug(string message);
    void Info(string message);
    void Warn(string message);
    void Error(string message, Exception failure);
    void Fatal(string message, Exception failure);
}
&lt;/pre&gt;
Furthermore, I register &lt;code&gt;ILoggingService&lt;/code&gt; in a my favorite container. 
&lt;pre class=&quot;brush:csharp&quot;&gt;
builder.RegisterType&amp;lt;LoggingService&amp;gt;().As&amp;lt;ILoggingService&amp;gt;();
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
In fact, aspect could be considered as a regular component, and rare component lives in an isolation. Usually, it has external dependencies. So now I&#39;m going to make some refactoring in the &lt;code&gt;LoggerAspect&lt;/code&gt; class, and switch using static &lt;code&gt;Logger&lt;/code&gt; class to &lt;code&gt;ILoggingService&lt;/code&gt; instead. 
&lt;pre class=&quot;brush:csharp&quot;&gt;
public class TraceCommand : MethodInterceptor
{
    private ILoggingService _loggingService;

    // ILoggingService is constructor injected in TraceCommand aspect class
    public TraceCommand(ILoggingService loggingService)
    {
        _loggingService = loggingService;
    }
        
    public override void InterceptMethod(IInvocation invocation, MethodBase method, Attribute attribute)
    {
        var command = (Command) invocation.Arguments.First();
        _loggingService.Info(&quot;Command started: {0}&quot;.FormatString(command));
        // other aspect code goes here
    }
}
&lt;/pre&gt;

&lt;p&gt;
The last step is to configure our new aspect with an external dependency in a SNAP. &lt;strong&gt;But here we run into a problem.&lt;/strong&gt; SNAP allows to specify aspect type only, not the instance. &lt;strong&gt;There is no way to supply pre-built &lt;code&gt;LoggerAspect&lt;/code&gt; instance to a SNAP&lt;/strong&gt;.
&lt;pre class = &quot;brush:csharp&quot;&gt;
SnapConfiguration.For(new AutofacAspectContainer(builder)).Configure(c =&gt;
{
    c.IncludeNamespace(&quot;MyCommands.*&quot;);
    // no way to do it
    c.Bind(new TraceCommand(new LoggingService)).To&amp;lt;TraceCommandAttribute&amp;gt;();
});
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;To summarize the problem cause, SNAP manages aspect instances on his own.&lt;/strong&gt; It creates a single instance for a given aspect type, and uses that instance for all interceptions. In other words, aspect is a singleton, and it is created by SNAP internally, without even letting developer to supply user-defined aspect instance. This is a significant problem, which impose several restrictions on how aspects are designed.
&lt;ul&gt;
&lt;li&gt;
Aspect should have parameterless constructor.
&lt;/li&gt;
&lt;li&gt;
External dependencies could not be injected in the aspect class. Aspect class can only refer to globally visible services (like singletons or static classes).
&lt;/li&gt;
&lt;li&gt;
Another option is to make aspect class to be autonomous and let encapsulate all stuff required to get it to work. This way, we could end up with an enormously large aspect class with many responsibilities.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;h2&gt;
Keep and resolve aspects from container
&lt;/h2&gt;
&lt;p&gt;
So, now I want to share my solution to the problem described above. Recently I&#39;ve implemented new feature, which allows developers to keep aspects in a container too, and let SNAP know whether aspect is managed by a container or should be instantiated by a SNAP (which is the default behavior). So, the idea is to let container manage components (aspects), and SNAP - to bring aspect-orientation and intercept method calls.  
&lt;/p&gt;

&lt;h3&gt;
All aspects are managed by container
&lt;/h3&gt;
&lt;p&gt;
So, assume you have several aspects, which are managed by a container. Here is how you could tell SNAP about it.
&lt;pre class=&quot;brush:csharp&quot;&gt;
[Test]
public void Autofac_Supports_Resolving_All_Aspects_From_Container()
{
    var builder = new ContainerBuilder();

    SnapConfiguration.For(new AutofacAspectContainer(builder)).Configure(c =&gt;
    {
        c.IncludeNamespace(&quot;SnapTests.*&quot;);
        c.Bind&amp;lt;FirstInterceptor&amp;gt;().To&amp;lt;FirstAttribute&amp;gt;();
        c.Bind&amp;lt;SecondInterceptor&amp;gt;().To&amp;lt;SecondAttribute&amp;gt;();
        c.AllAspects().KeepInContainer();
    });

    builder.Register(r =&amp;gt; new OrderedCode()).As&amp;lt;IOrderedCode&amp;gt;();
    builder.Register(r =&amp;gt; new FirstInterceptor(&quot;first_kept_in_container&quot;));
    builder.Register(r =&amp;gt; new SecondInterceptor(&quot;second_kept_in_container&quot;));

    using (var container = builder.Build())
    {
        var orderedCode = container.Resolve&amp;lt;IOrderedCode&amp;gt;();
        orderedCode.RunInOrder();

        // both interceptors are resolved from container
        CollectionAssert.AreEquivalent(
            OrderedCode.Actions,
            new[] { &quot;first_kept_in_container&quot;, &quot;second_kept_in_container&quot; });
    }
}
&lt;/pre&gt;
This way SNAP uses a container to resolve aspect instances, whenever interception occurs.
&lt;/p&gt;

&lt;h3&gt;
Only subset of aspects are managed by container
&lt;/h3&gt;
&lt;p&gt;
In this scenario, some of your aspects are managed by container, while other are not. Once again, we need to tell SNAP about it.
&lt;pre class=&quot;brush:csharp&quot;&gt;
[Test]
public void Autofac_Supports_Resolving_Only_Selected_Aspects_From_Container()
{
    var builder = new ContainerBuilder();

    SnapConfiguration.For(new AutofacAspectContainer(builder)).Configure(c =&gt;
    {
        c.IncludeNamespace(&quot;SnapTests.*&quot;);
        c.Bind&amp;lt;FirstInterceptor&amp;gt;().To&amp;lt;FirstAttribute&amp;gt;();
        c.Bind&amp;lt;SecondInterceptor&amp;gt;().To&amp;lt;SecondAttribute&amp;gt;();
        c.Aspects(typeof(FirstInterceptor)).KeepInContainer();
    });

    builder.Register(r =&amp;gt; new OrderedCode()).As&amp;lt;IOrderedCode&amp;gt;();
    builder.Register(r =&amp;gt; new FirstInterceptor(&quot;first_kept_in_container&quot;));
    builder.Register(r =&amp;gt; new SecondInterceptor(&quot;second_kept_in_container&quot;));

    using (var container = builder.Build())
    {
        var orderedCode = container.Resolve&amp;lt;IOrderedCode&amp;gt;();
        orderedCode.RunInOrder();

        // first interceptor is resolved from container, while second one is via new() 
        CollectionAssert.AreEquivalent(
            OrderedCode.Actions,
            new[] { &quot;first_kept_in_container&quot;, &quot;Second&quot; });
    }
}
&lt;/pre&gt;
When nothing is configured explicitly, SNAP uses its default behavior and instantiates aspect instances on its own.
&lt;/p&gt;

&lt;h3&gt;
Conclusion
&lt;/h3&gt;
&lt;p&gt;
As you can see, it becomes possible to keep aspects in a container too, not only application components. These changes &lt;a href=&quot;https://github.com/TylerBrinks/Snap/pull/14&quot;&gt;are already pulled&lt;/a&gt; into the &lt;a href=&quot;https://github.com/TylerBrinks/Snap&quot;&gt;main SNAP repository&lt;/a&gt;. So you can already use this IMO valuable feature.
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Stay tuned. Happy aspecting.&lt;/strong&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/2927865299094804025/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/10/keep-aspects-in-container.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/2927865299094804025'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/2927865299094804025'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/10/keep-aspects-in-container.html' title='SNAP - Keeping aspects in a container'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-7139319269190991846</id><published>2011-10-05T02:13:00.002+03:00</published><updated>2011-10-05T02:17:22.396+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AOP"/><category scheme="http://www.blogger.com/atom/ns#" term="SNAP"/><title type='text'>Aspect Oriented Programming with SNAP</title><content type='html'>&lt;p&gt;
Недавно на проекте надо было реализовать механизм диагностики одной из подсистем, который сводился к подробной трассировке различных аспектов работы системы, и предоставления пользователю этой информации в удобном для просмотра формате.
&lt;/p&gt;

&lt;p&gt;
В экосистеме .net cуществует  много средств, задача которых предоставлять механизмы логгирования и трассировки. Это могут быть сторонние библиотеки как &lt;a href=&quot;http://logging.apache.org/log4net/&quot;&gt;log4net&lt;/a&gt;, &lt;a href=&quot;http://nlog-project.org/&quot;&gt;NLog&lt;/a&gt;. Также остается недооцененным функционал трассировки и логгирования, поставляемый в .net из коробки (&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ms733025.aspx&quot;&gt;NET Framework System.Diagnostics&lt;/a&gt;). Есть решения, которые расширяют стандартные средства трассировки .net. В &lt;a href=&quot;http://entlib.codeplex.com/&quot;&gt;Microsoft Enterprise Library&lt;/a&gt; для этих целей есть &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ff664569%28v=PandP.50%29.aspx&quot;&gt;Logging Application Block&lt;/a&gt;, также есть third-party библиотеки, например &lt;a href=&quot;http://essentialdiagnostics.codeplex.com/&quot;&gt;Essential Diagnostics&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
Недостатка в средствах трассировки и логгирования нет. В целом, идея одна и та же, предоставляемый функционал варьируется в ту или иную сторону. Однако, сегодня речь не о выборе logging framework. Более важный вопрос, на мой взгляд, не в выборе той или иной библиотеки, а в ее использовании в нашем приложении, другими словами, как происходят вызовы API библиотеки из нашего кода.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;Содержание статьи.&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#cross-cutting-concerns&quot;&gt;Cross-cutting concerns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#aspect-orientation&quot;&gt;Aspect orientation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#aop-framework-classification&quot;&gt;Классификация AOP framework&#39;ов&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#selecting-aop-framework&quot;&gt;Выбор AOP framework&#39;а&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#snap&quot;&gt;SNAP - Simple .NET Aspect Oriented Programming&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#logging-aspect-implementation&quot;&gt;Реализация аспекта логгирования&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#applying-aspect&quot;&gt;Применение аспекта&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#aspect-configuration&quot;&gt;Конфигурация аспектов&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#one-method-many-aspects&quot;&gt;Один метод - много аспектов&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#snap-drawback&quot;&gt;Недостаток SNAP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#keep-aspects-in-container&quot;&gt;Keep aspects in container&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;

&lt;h2&gt;
&lt;a name=&quot;cross-cutting-concerns&quot;&gt;Cross-cutting concerns&lt;/a&gt;
&lt;/h2&gt;
Посмотрим как можно добавить вызовы logging framework в код приложения. Самый распространненый подход состоит в добавлении кода трассировки непосредственно в код приложения, зашив его напрямую в тело метода. Рассмотрим следующий псевдокод расчета маршрута из пункта A в пункт B.

&lt;pre class=&quot;brush:csharp&quot;&gt;
public class CalculateRouteCommandHandler
{
    private IMapService _mapService;

    public CalculateRouteCommandHandler(IMapService mapService)
    {
        _mapService = mapService;
    }

    public void HandleCommand(CalculateRouteCommand command)
    {
        Logger.Info(&quot;{0} started.&quot;.FormatString(command));
        Logger.Info(&quot;From: {0}; To: &#39;{1}&#39;.&quot;.FormatString(command.Departure, command.Destination));

        try
        {
            var intermediateroutes = CalculateIntermediateRoutes();
            foreach (var intermediateroute in intermediateroutes)
            {
                CalculateDistance(intermediateroute);
                Logger.Debug(&quot;Distance calculated: {0}&quot;.FormatString(intermediateroute.Distance))

                CalculateTransport(intermediateroute);
                Logger.Debug(&quot;Transport calculated: {0}&quot;.FormatString(intermediateroute.Transport))

                var alternatives = FindAlternatives(intermediateroute);
                if(alternative.Any())
                {
                    Logger.Info(&quot;Found alternative routes: &#39;{0}&#39;&quot;.FormatString(alternatives))
                }
            }
            foreach (var jump in command.Jumps)
            {
                Logger.Info(&quot;{} completed&quot;.FormatString(command));
                Logger.Info(&quot;Jump from &#39;{0}&#39; to &#39;{1}&#39; with distance of &#39;{2}&#39;&quot;
                    .FormatString(jump.Departure, jump.Destination, jump.Distance))
            } 
        }
        catch(RouteCalculationException ex)
        {
            Logger.Error(&quot;Fail to calculate route&quot;, ex);
            throw;
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;
Класс состоит из одного метода, который чуть ли не на половину состоит из кода логгирования. Плохо то, что бизнес-логика приложения и инфраструктурные механизмы (в виде логгирования) перемешаны. Инфраструктурные детали, будучи сугубо техническими, засоряют основной код приложения, который выражает требования бизнеса. Даже при относительно небольшом проценте инфраструктурного кода, читать и понимать бизнес задачи, которые этот код призван решать и выражать, становится сложно. Распространенность этого подхода обусловлена легкостью реализации, но отнюдь не соображениями о правильной архитектуре, и оценке последствий такого решения.
&lt;/p&gt;

&lt;p&gt;
Определим понятие &lt;a href=&quot;http://abdullin.com/wiki/cross-cutting-concern.html&quot;&gt;cross-cutting concerns&lt;/a&gt; (сквозная функциональность).
&lt;blockquote&gt;
In software development cross-cutting concerns are logical parts of the program that affect (crosscut) other concerns or modules.
&lt;/blockquote&gt;

Сквозная функциональность - это те аспекты, которые составляют техническое решение бизнес задачи, но в тоже время прямого отношения к бизнес-логике не имеют. Примерами сквозной функциональности может служить:
&lt;ul&gt;
&lt;li&gt;Безопасность&lt;/li&gt;
&lt;li&gt;Поддержка транзакционности&lt;/li&gt;
&lt;li&gt;Обработка ошибок&lt;/li&gt;
&lt;li&gt;Поддержка многопоточности&lt;/li&gt;
&lt;li&gt;Измерение производительности&lt;/li&gt;
&lt;li&gt;Логгирование&lt;/li&gt;
&lt;/ul&gt;
Если представить слоистую архитектуру приложения, состоящую из &lt;code&gt;presentation&lt;/code&gt;, &lt;code&gt;application&lt;/code&gt;, &lt;code&gt;domain&lt;/code&gt; и &lt;code&gt;infrastructure&lt;/code&gt; слоев, расположенных горизонтально друг над другом, то сквозная функциональность не имеет отношения ни к какому конкретному слою, а пересекает все слои вертикально. Отсюда и название - сквозная функциональность. 
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;aspect-orientation&quot;&gt;Aspect orientation&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
Редко целью задач является разработка и поддержание инфраструктуры приложения. Зачастую задачи диктуются бизнесом, и несут в себе определенные бизнес требования. Поэтому разработчик в первую очередь сосредоточен на решении задач приложения, задач бизнеса. Однако не задумываться об инфраструктуре нельзя, иначе в итоге сквозная функциональсть будет беспорядочно и неуправляемо разбросана и повторяться по всему приложению.
&lt;/p&gt;

&lt;p&gt;
Никто не отменял такие принципы, как &lt;a href=&quot;http://en.wikipedia.org/wiki/Don%27t_repeat_yourself&quot;&gt;Don&#39;t repeat yourself&lt;/a&gt;, &lt;a href=&quot;http://en.wikipedia.org/wiki/Single_responsibility_principle&quot;&gt;Single Responsibility Principle&lt;/a&gt;, &lt;a href=&quot;http://en.wikipedia.org/wiki/Separation_of_concerns&quot;&gt;Separation Of Concerns&lt;/a&gt;. Собственно идеей &lt;a href=&quot;http://en.wikipedia.org/wiki/Aspect-oriented_programming&quot;&gt;Aspect Oriented Programming&lt;/a&gt; и является отделение сквозной функциональности от бизнес-логики приложения (separation of concerns), и поощрение принципов DRY и SRP.
&lt;/p&gt;

&lt;pre class=&quot;brush:csharp&quot;&gt;
public class CalculateRouteCommandHandler
{
    private IMapService _mapService;

    public CalculateRouteCommandHandler(IMapService mapService)
    {
        _mapService = mapService;
    }

    [TraceCommand]
    public virtual void HandleCommand(CalculateRouteCommand command)
    {
        try
        {
            var intermediateroutes = CalculateIntermediateRoutes();
            foreach (var intermediateroute in intermediateroutes)
            {
                CalculateDistance(intermediateroute);
                CalculateTransport(intermediateroute);
                FindAlternatives(intermediateroute);
            }
        }
    }

    [TraceCall(Format = &quot;Distance calculated: {1}&quot;, Level = LogLevel.Debug)]
    protected void virtual CalculateDistance(Route intermediate)
    {
        ...
    }

    [TraceCall(Format = &quot;Transport calculated: {1}&quot;, Level = LogLevel.Debug)]
    protected void virtual CalculateTransport(Route intermediate)
    {
        ...
    }

    [TraceCall(Format = &quot;Alternatives found: {1}&quot;, Level = LogLevel.Info, LogStart = false)]
    protected void virtual FindAlternative(Route intermediate)
    {
        ...
    }
}
&lt;/pre&gt;

&lt;p&gt;
&lt;code&gt;HandleCommand&lt;/code&gt; метод более не засорен деталями логгирования и описывает исключительно бизнес задачу. Логгирование применено посредством аспектов. Аспект представляет из себя тот или иной элемент сквозной функциональности. Аспекты &lt;code&gt;TraceCommand&lt;/code&gt; и &lt;code&gt;TraceCall&lt;/code&gt; выполняют логгирование события обработки команд и вызова методов соответственно.
&lt;p&gt;


&lt;h2&gt;
&lt;a name=&quot;aop-framework-classification&quot;&gt;Классификация AOP framework&#39;ов&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
Задачей AOP framework&#39;ов является предоставление разработчику удобных механизмов работы со сквозной функциональностью в виде аспектов, таким образом, чтобы бизнес-логика и сквозная функциональность были отделены друг от друга.
&lt;/p&gt;

AOP фреймворки можно классифицировать по следующим критериям:
&lt;ul&gt;
&lt;li&gt;Каким образом осуществляется перехват кода и вызов аспектов?&lt;/li&gt;
&lt;li&gt;В какие элементы кода можно внедрять аспекты?&lt;/li&gt;
&lt;li&gt;Каким образом можно применять аспекты к коду приложения?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Первый подход состоит в интрументировании IL-кода сборки на этапе пост-компиляции (code weaving) путем внедрения кода вызова аспектов в нужных места кода приложения. Основной представитель этого класса фреймворков - &lt;a href=&quot;http://www.sharpcrafters.com/&quot;&gt;PostSharp&lt;/a&gt;. 

&lt;blockquote&gt;
With PostSharp, software developers can encapsulate implementation patterns into classes called aspects, and apply these aspects to their code using custom attributes. 
&lt;/blockquote&gt;
&lt;/p&gt;

&lt;p&gt;
С другой стороны, можно обойтись и без низкоуровневого изменения IL-кода, а вместо этого использовать run-time средства, такие как subclass либо interface proxy. Идея состоит в прозрачной для клиента подмене реальных классов прокси-классами, которые помимо вызова проксируемого класса добавляют поведение вызова нужных аспектов. Библиотека &lt;a href=&quot;http://www.castleproject.org/dynamicproxy/index.html&quot;&gt;Castle.DynamicProxy&lt;/a&gt; предоставляет механизм создания таких прокси и перехвата виртуальных методов. 
&lt;blockquote&gt;
Castle DynamicProxy is a library for generating lightweight .NET proxies on the fly at runtime. Proxy objects allow calls to members of an object to be intercepted without modifying the code of the class. Both classes and interfaces can be proxied, however only virtual members can be intercepted. 
&lt;/blockquote&gt;
&lt;/p&gt;

&lt;p&gt;
Целью перехвата обычно служит вызов метода. Реже - вызов поля, более того, вызов поля всегда можно смоделировать в виде вызова метода, так что основной use case - это перехват метода (method interception). Фреймворки, которые инструментируют сборку путем переписывания кода, позволяют внедрить перехват в любое место, будь то вызов метода или поля. Минус состоит, во-первых, в самой идее переписывания скомпилированного кода, во-вторых, в увеличении времени компиляции и сборки, а также сложности настройки и громоздкости этого процесса. Подход, который не требует инструментирования сборки, выглядит более заманчивей. В тоже время, он позволяет перехватывать только виртуальные public/protected методы. Если же компоненты в приложении управляются контейнером, то возможности перехвата сужаются еще и до списка зарегистрированных компонент. У подхода с инструментированием сборки этих ограничений нет.
&lt;/p&gt;

&lt;p&gt;
Наиболее предпочительный способ применения аспектов к коду приложения (например, методам или классам) непосредственно в том же коде при помощи атрибутов. Также можно описывать применение аспектов в отдельном xml файле, либо с помощью графического дизайнера. Однако в таком случае становится сложнее анализировать код, так как приходится смотреть в несколько мест, и использовать для этого разные языки либо инструменты.
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;selecting-aop-framework&quot;&gt;Выбор AOP framework&#39;а&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;
После того как идея и подходы AOP поняты, нужно принять решение о выборе конкретного фреймворка для решения конкретных задач. Для этого сформулируем список критериев.
&lt;ol&gt;
&lt;li&gt;Не предполагает переписывание скомпилированного кода и инструментирование сборки.&lt;/li&gt;
&lt;li&gt;Позволяет выборочно осуществлять перехват на уровне методов.&lt;/li&gt;
&lt;li&gt;Интегрируется с выбранным DI/IoC контейнером.&lt;/li&gt;
&lt;li&gt;Приемлемый с точки зрение призводительности и продуктивности разработчика.&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;a href=&quot;http://www.sharpcrafters.com/&quot;&gt;PostSharp&lt;/a&gt; - известный, зрелый иструмент. Однако, идея переписывания IL-кода не прельщает, и хочется избежать сложностей post-build настройки проектов. Заметно увеличивается время сборки проекта. Помимо этого, Postsharp - это &lt;a href=&quot;http://www.sharpcrafters.com/purchase&quot;&gt;платный инструмент&lt;/a&gt;, хотя в наличии есть и бесплатная &lt;a href=&quot;http://www.sharpcrafters.com/purchase/compare&quot;&gt;Starter Edition с урезаным функционалом&lt;/a&gt;. Вобщем, PostSharp - это тяжелая артиллерия, без которой можно обойтись в большинстве use case&#39;ов.
&lt;/p&gt;

&lt;p&gt;
Использовать &lt;a href=&quot;http://www.castleproject.org/dynamicproxy/index.html&quot;&gt;Castle.DynamicProxy&lt;/a&gt; напрямую слишком хлопотно, и в итоге придется создавать поверх него собственный вспомогательный слой. Но нам нужно заниматься бизнем задачами, а не созданием инфраструктуры. Ниже код создания прокси для класса либо интерфейса c указанием interceptor&#39;ов.

&lt;pre class =&quot;brush:csharp&quot;&gt;
public static object CreateProxy(Type interfaceType, object instanceToWrap, params IInterceptor[] interceptors)
{
    if (interfaceType.IsInterface)
        return new ProxyGenerator().CreateInterfaceProxyWithTargetInterface(
            interfaceType, 
            instanceToWrap, 
            interceptors.ToArray());

    var ctor = type.GetConstructors().OrderBy(x =&gt; x.Parameters().Count).LastOrDefault();
    var ctorArgs = ctor == null ? new object[0] : new object[сtor.Parameters().Count];
    return new ProxyGenerator().CreateClassProxyWithTarget(
        interfaceType, 
        instanceToWrap, 
        ctorArgs, 
        interceptors);
}

&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
Если для управления компонент в приложении применяется DI/IoC контейнер, то популярные контейнеры, такие как &lt;a href=&quot;http://docs.castleproject.org/Windsor.MainPage.ashx&quot;&gt;Castle.Windsor&lt;/a&gt;, &lt;a href=&quot;http://code.google.com/p/autofac/&quot;&gt;Autofac&lt;/a&gt;, &lt;a href=&quot;http://structuremap.net/structuremap/&quot;&gt;StructureMap&lt;/a&gt; и др., предоставляют функциональность перехвата вызова компонент зарегистрированных в контейнере. Для этого используется Castle.DynamicProxy как низкоуровневый механизм, однако детали работы с ним в той или иной мере скрыты от разработчика. Рассмотрим &lt;a href=&quot;http://nblumhardt.com/archives/aop-with-autofac-and-dynamicproxy2/&quot;&gt;этот вариант&lt;/a&gt; на примере Autofac.
&lt;pre class=&quot;brush:csharp&quot;&gt;
builder.RegisterType&amp;lt;BarCommandHandler&gt;()
    .Keyed&amp;lt;ICommandHandler&amp;gt;(typeof(BarCommand))
    .SingleInstance()
    .EnableClassInterception();
builder.RegisterType&amp;lt;BazCommandHandler&gt;()
    .Keyed&amp;lt;ICommandHandler&amp;gt;(typeof(BazCommand))
    .SingleInstance()
    .EnableInterfaceInterception();
&lt;/pre&gt;

&lt;p&gt;
Магия состоит в использовании extension методов &lt;code&gt;EnableClassInterception&lt;/code&gt; и &lt;code&gt;EnableInterfaceInterception&lt;/code&gt;, которые находится в библиотеке &lt;a href=&quot;http://code.google.com/p/autofac/wiki/DynamicProxy2&quot;&gt;AutofacContrib.DynamicProxy2&lt;/a&gt;. Исходный код можно &lt;a href=&quot;http://code.google.com/p/autofac/source/browse/contrib/Source/AutofacContrib.DynamicProxy2/RegistrationExtensions.cs&quot;&gt;посмотреть здесь&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt; 
Минус состоит в том, что приведенным выше образом можно сконфигурировать перехват всех методов указанного класса, но без возможности конфигурации на уровне отдельных методов. И это существенный недостаток! Редко когда нужно перехватывать все методы класса без разбора. В качестве workaround,  &lt;a href=&quot;http://stackoverflow.com/questions/4807491/aop-with-autofac-and-dynamicproxy2&quot;&gt;здесь&lt;/a&gt; и &lt;a href=&quot;http://stackoverflow.com/questions/2662981/selectively-intercepting-methods-using-autofac-and-dynamicproxy2&quot;&gt;здесь&lt;/a&gt; описываются подходы как реализовать это самому.
&lt;/p&gt;


&lt;p&gt;
Подведя итог обзора доступных AOP решений, с одной стороны есть тяжелый и сложный, к тому же платный PostSharp, с другой стороный есть низкоуровненый механизм Castle.DynamicProxy, напрямую работать с которым будет неудобно. Наиболее привлекательным решением выглядит использование возможностей поставляемых с DI/IoC контейнерами, однако поведение и возможности варьируются в зависимости от выбранного поставщика. Например, в том же Autofac, нет out-of-the-box возможности выборочного перехвата методов.
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;snap&quot;&gt;SNAP - Simple .NET Aspect Oriented Programming&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
Далее рассмотрим возможности AOP фреймворка под названием SNAP. &lt;a href=&quot;http://www.simpleaspects.com/&quot;&gt;Более подробно здесь&lt;/a&gt;. 
&lt;blockquote&gt;
&lt;p&gt;
A .NET library to make aspect-oriented programming a Snap!
&lt;/p&gt;

&lt;p&gt;
Snap (Simple .NET Aspect-Oriented Programming) was conceived out of a recurring need for flexible AOP in the wake of PostSharp&#39;s metamorphosis into a commercial product.
&lt;/p&gt;

&lt;p&gt;
Not to be mistaken with a code weaver (i.e. a compile-time tool), Snap is a fully run-time executed library. Snap aims to pair with your Inversion of Control (IoC) container of choice to deliver a powerful aspect-oriented programming model.
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/p&gt;

&lt;p&gt;
Итак, в чем отличие этого решения от тех что обсуждались ранее? Опишем плюсы этого решения:
&lt;ul&gt;
&lt;li&gt;
Для реализации механизма перехватов не производится инструментирование сборки на этапе пост-компиляции, вместо этого используется возможности Castle.DynamicProxy (subclass/interface proxy)&lt;/li&gt;
&lt;li&gt;
Интеграция с DI/IoC контейнером. На данный момент поддерживается пять реализаций: &lt;a href=&quot;http://code.google.com/p/autofac/&quot;&gt;Autofac&lt;/a&gt;, &lt;a href=&quot;http://docs.castleproject.org/Windsor.MainPage.ashx&quot;&gt;Castle.Windsor&lt;/a&gt;, &lt;a href=&quot;http://structuremap.net/structuremap/&quot;&gt;StructureMap&lt;/a&gt;, &lt;a href=&quot;http://ninject.org/&quot;&gt;NInject&lt;/a&gt;, &lt;a href=&quot;http://code.google.com/p/linfu/&quot;&gt;LinFu&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
Использование единой программной модели независимо от выбранного поставщика DI/IoC, скрытие от разработчика специфики его работы с возможностями перехвата.
&lt;/li&gt; 
&lt;li&gt;
Выборочный перехват методов.
&lt;/li&gt;
&lt;li&gt;
Проста в использовании. Не требует от разработчика понимать реализацию таких advanced концепций, как dynamic proxies, и как интегрировать их в используемый DI/IoC контейнер.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;
&lt;p&gt;
Проект &lt;a href=&quot;https://github.com/TylerBrinks/Snap&quot;&gt;выложен на github&lt;/a&gt;, а также доступен для установки на &lt;a href=&quot;http://nuget.org/List/Searchjavascript:void(0)?packageType=Packages&amp;searchCategory=All+Categories&amp;searchTerm=snap&amp;sortOrder=package-download-count&amp;pageSize=10&quot;&gt;nuget gallery&lt;/a&gt;.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;logging-aspect-implementation&quot;&gt;Реализация аспекта логгирования&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Далее рассмотрим реализацию логгирования в AOP ключе используя для этого SNAP. Бизнес-логика находится в классе &lt;code&gt;CalculateRouteCommandHandler&lt;/code&gt;, который расчитывает маршрут между двумя пунктами.
&lt;pre class = &quot;brush:csharp&quot;&gt;
public class CalculateRouteCommandHandler
{
    private IMapService _mapService;

    public CalculateRouteCommandHandler(IMapService mapService)
    {
        _mapService = mapService;
    }

    public virtual void HandleCommand(CalculateRouteCommand command)
    {
        try
        {
            var intermediateroutes = CalculateIntermediateRoutes();
            foreach (var intermediateroute in intermediateroutes)
            {
                CalculateDistance(intermediateroute);
                CalculateTransport(intermediateroute);
                FindAlternatives(intermediateroute);
            }
        }
    }
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
Нам необходимо залоггировать начало выполнения команды и результаты ее выполнения. Для этого создадим соответствующий класс аспекта.
&lt;pre class = &quot;brush:csharp&quot;&gt;
public class TraceCommand : MethodInterceptor
{
    public override void InterceptMethod(IInvocation invocation, MethodBase method, Attribute attribute)
    {
        var command = (Command) invocation.Arguments.First();
        var targetAttribute = (TraceCommandAttribute) attribute;
        try
        {
            if(targetAttribute.TraceStart)
            {
                Logger.Info(&quot;Command started: {0}&quot;.FormatString(command));
            }
            invocation.Proceed();
            if(targetAttribute.TraceComplete)
            {
                Logger.Info(&quot;Command completed: {0}&quot;.FormatString(command));
            }
        }
        catch(Exception ex)
        {
            if(targetAttribute.TraceFail)
            {
                Logger.Error(&quot;Command failed: {0}&quot;.FormatString(command), ex);
            }
            throw;
        }
    }
}
&lt;/pre&gt;
Класс аспекта наследуется от базового класса &lt;code&gt;MethodInterceptor&lt;/code&gt;, в котором есть методы &lt;code&gt;BeforeInvocation()&lt;/code&gt;, &lt;code&gt;AfterInvocation()&lt;/code&gt;. Если нужен доступ к информации о вызове, вызванном методе, атрибуте, то можно переопределить метод &lt;code&gt;InterceptMethod&lt;/code&gt;, как это показано выше. Тело метода состоит из логгирования начала команды, удачного либо неудачного ее завершения. Вызов же самого целевого метода осуществляется при помощи &lt;code&gt;IInvocation.Proceed()&lt;/code&gt;.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;applying-aspect&quot;&gt;Применение аспекта&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
После того как класс аспекта с логикой сквозной функциональности готов, необходим механизм применения аспекта к коду приложения. Для этого нам нужен атрибут.
&lt;pre class=&quot;brush:csharp&quot;&gt;
public class TraceCommandAttribute : MethodInterceptAttribute
{
    public bool TraceStart { get; set; }
    public bool TraceComplete { get; set; }
    public bool TraceFail { get; set; }
}
&lt;/pre&gt;
Атрибут может содержать настройки, которые влияют на работу аспекта. В нашем случае это указание того, какие события в обработке команды нужно логгировать. Применение аспекта сводится к применению атрибута.
&lt;pre class=&quot;brush:csharp&quot;&gt;
[TraceCommand(TraceStart = false)]
public virtual void HandleCommand(CalculateRouteCommand command)
{
    // business logic goes here
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;aspect-configuration&quot;&gt;Конфигурация аспектов&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Далее нужно сконфигурировать SNAP таким образом, чтобы он знал о реализованных аспектах и атрибутах, которые используются для применения аспектов. В простейшем случае это выглядит так (на примере Autofac).
&lt;pre class=&quot;brush:csharp&quot;&gt;
var builder = new ContainerBuilder();

// configure aspects
SnapConfiguration.For(new AutofacAspectContainer(builder)).Configure(c =&gt;
{
    c.IncludeNamespace(&quot;MyCommands.*&quot;);
    c.Scan(s =&gt; s.ThisAssembly().WithDefaults());
});

// configure components in container
builder.RegisterType&amp;lt;CalculateRouteCommandHandler&amp;gt;().As&amp;lt;ICommandHandler&amp;lt;CalculateRouteCommand&amp;gt;&amp;gt;();
builder.RegisterType&amp;lt;MapService&amp;gt;().As&amp;lt;IMapService&amp;gt;();
&lt;/pre&gt;
Первым делом указываем осуществлять перехват только тех классов, которые находятся в указанном namespace. Минус - это отсуствие типизации в данном случае, так как название неймспейса - это простая строка. Далее конфигурируем SNAP так, чтобы он сам определил набор аспектов и их атрибутов, используя naming convention - название класса аспекта должно совпадать с названием класса атрибута (без учета суффиксов &quot;Interceptor&quot;, &quot;Attribute&quot;). Также можно подключить свою собственную стратегию автоматической конфигурации компонент, реализовав интерфейс &lt;code&gt;IScanningConvention&lt;/code&gt;. В нашем примере default naming convention выполняется, поэтому нет необходимости явно конфигурировать &lt;code&gt;TraceCommandAspect&lt;/code&gt;. В противном случае это можно сделать так. 
&lt;pre class=&quot;brush:csharp&quot;&gt;
SnapConfiguration.For(new AutofacAspectContainer(builder)).Configure(c =&gt;
{
    c.IncludeNamespace(&quot;MyCommands.*&quot;);
    c.Bind&amp;lt;TraceCommand&amp;gt;().To&amp;lt;TraceCommandAttribute&amp;gt;();
});
&lt;/pre&gt;
&lt;/p&gt;

&lt;h3&gt;&lt;a name=&quot;one-method-many-aspects&quot;&gt;Один метод - много аспектов&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
Можно ли к одному методу применить несколько аспектов? Ответ: &quot;Да&quot;. В таком случае, порядок выполнения аспектов важен. Порядок указания атрибутов в исходном коде не может быть использован в принципе, поэтому есть два механизма конфигурации порядка вызова аспектов: для аспекта в целом, и для отдельного случая его применения. 
&lt;pre class = &quot;brush:csharp&quot;&gt;
SnapConfiguration.For(new AutofacAspectContainer(builder)).Configure(c =&gt;
{
    c.IncludeNamespace(&quot;MyCommands.*&quot;);
    // configure ordering for any aspect usage
    c.Bind&amp;lt;TraceCommand&amp;gt;().To&amp;lt;TraceCommandAttribute&amp;gt;().Order(2);
});
&lt;/pre&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
// configure ordering just for this usage
[TraceCommand(TraceStart = false, Order = 2)]
// handle error first of all
[HandleError(Order = 1)]
public virtual void HandleCommand(CalculateRouteCommand command)
{
    // business logic goes here
}
&lt;/pre&gt;
Если порядок не указан тем или иным образом, аспекты выполняются в алфавитном порядке по названию класса.
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Вот собственно и все, что нужно для применения aspect oriented подхода используя SNAP в качестве фреймворка.&lt;/strong&gt;
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;snap-drawback&quot;&gt;Недостаток SNAP&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
На мой взгляд, у этого фреймворка есть один недостаток, который очень сильно ограничивает его использование на практике, а не в демонстрационных примерах. Для этого рассмотрим предыдущий сценарий, только в этот раз механизм логгирования будет смоделирован в виде интерфейса &lt;code&gt;ILoggingService&lt;/code&gt;.
&lt;pre class=&quot;brush:csharp&quot;&gt;
public interface ILoggingService
{
    void Debug(string message);
    void Info(string message);
    void Warn(string message);
    void Error(string message, Exception failure);
    void Fatal(string message, Exception failure);
}
&lt;/pre&gt;
Этот интерфейс инжектится в класс аспекта и далее используется для логгирования вместо статического класса Logger.
&lt;pre class=&quot;brush:csharp&quot;&gt;
public class TraceCommand : MethodInterceptor
{
    private ILoggingService _loggingService;

    public TraceCommand(ILoggingService loggingService)
    {
        _loggingService = loggingService;
    }
        
    public override void InterceptMethod(IInvocation invocation, MethodBase method, Attribute attribute)
    {
        var command = (Command) invocation.Arguments.First();
        _loggingService.Info(&quot;Command started: {0}&quot;.FormatString(command));
        // other aspect code goes here
    }
}
&lt;/pre&gt;
Более того, компонент &lt;code&gt;ILoggingService&lt;/code&gt; управляется контейнером.
&lt;pre class=&quot;brush:csharp&quot;&gt;
builder.RegisterType&amp;lt;LoggingService&amp;gt;().As&amp;lt;ILoggingService&amp;gt;();
&lt;/pre&gt;
&lt;strong&gt;Вопрос: А как же теперь сконфигурировать SNAP?&lt;/strong&gt; Если позволить найти аспект автоматически, то SNAP не сможет создать экземпляр аспекта не найдя конструктора без параметров. Возможности же явно указать готовый экземпляр аспекта тоже нет. B итоге приходится возвращаться к реализации аспекта со статическим классом &lt;code&gt;Logger&lt;/code&gt;.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;keep-aspects-in-container&quot;&gt;Keep aspects in container&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
&lt;strong&gt;Подведя итог, проблема состоит в том, что SNAP сам занимается созданием и управлением экземпляров  аспектов.&lt;/strong&gt; В итоге, аспекты - это синглтоны, созданные на этапе конфигурации SNAP. Это влечет за собой невозможность использования аспектов, которым нужны внешние сервисы для работы. А значит, мы ограничены созданием очень простых аспектов, либо автономных и самодостаточных классов аспектов, в которых есть все, что нужно им для работы. Это нарушает &lt;code&gt;Single Responsibility Principle&lt;/code&gt; и не поощряет использование good design practices. А хороший фреймворк хорош тогда, когда поощряет разработчика к применению лучших практик.
&lt;/p&gt;

&lt;p&gt;
Печально, но по крайней мере для меня, эта проблема сводит на нет все преимущества этого фреймворка. Поэтому &lt;a href=&quot;https://github.com/samoshkin/Snap&quot;&gt;я решил исправить положение вещей&lt;/a&gt; и &lt;strong&gt;дать возможность контейнеру управлять классами аспектов, и позволить SNAP запрашивать экземпляры аспектов у контейнера, вместо того чтобы создавать их самому&lt;/strong&gt;. Таким образом каждый будет заниматься тем, что у него получается лучше всего: SNAP - аспектами и перехватами, а контейнер - управлением компонентами. В ближайшем будушем я планирую закончить реализацию этой функциональности и отправить pull request в &lt;a href=&quot;https://github.com/TylerBrinks/Snap&quot;&gt;основной репозиторий&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;Stay tuned!&lt;/strong&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/7139319269190991846/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/10/aop-with-snap.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/7139319269190991846'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/7139319269190991846'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/10/aop-with-snap.html' title='Aspect Oriented Programming with SNAP'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-1778681688501508645</id><published>2011-09-25T04:07:00.003+03:00</published><updated>2011-09-29T21:36:15.814+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="MSpec"/><category scheme="http://www.blogger.com/atom/ns#" term="TDD/BDD"/><title type='text'>Behavior Driven Development Explained with MSpec</title><content type='html'>&lt;p&gt;
Давно не писал ничего в блог. Но лето ушло, вернулось рабочее настроение, а также появились интересные, на мой взгляд темы, которые стоит осветить.
&lt;/p&gt;
&lt;p&gt;
Речь пойдет о &lt;a href=&quot;http://www.code-magazine.com/article.aspx?quickid=0805061&amp;page=1&quot;&gt;behavior driven development&lt;/a&gt;, идеях, подходах и используемых инструментах. Слово &lt;code&gt;BDD&lt;/code&gt; в последнее время стало своего рода buzzword&#39;ом. Разработчиков можно в целом разделить на тех, кому нравится &lt;code&gt;BDD&lt;/code&gt; стиль, и те, кто, относится к нему негативно либо крайне негативно. В статье постараюсь пройти по узкой дорожке и не впасть ни в одну из крайностей. Статья ориентирована на разработчиков, которые интересуются &lt;code&gt;BDD&lt;/code&gt; подходом, либо практикуют, либо ищут более оптимальные решения и инструменты.
&lt;/p&gt;

&lt;p&gt;
Рассмотрим следующие вопросы:
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;a href=&quot;#from-unit-tests-to-specifications&quot;&gt;From unit testing to behavior specifications&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;#unit-testing-and-drawback-analysis&quot;&gt;Goal of unit testing and drawback analysis&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#executable-specifications&quot;&gt;Executable specifications&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#given-when-then&quot;&gt;Given-when-then&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#context-specification-style&quot;&gt;Scenarios and context/specification style&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#flavors-of-BDD&quot;&gt;Flavors of BDD approach&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#mspec-usage&quot;&gt;Использование MSpec - BDD framework in context-specification style&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;#mspec-specification-example&quot;&gt;Пример спецификации в стиле context/specification&lt;/a&gt; 
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#failure-verification&quot;&gt;Failure verification&lt;/a&gt; 
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#simple-context&quot;&gt;Simple context&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#action-and-context-common-scenarios&quot;&gt;Action-common and context-common scenarios&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#behavior-specification-suite&quot;&gt;Behavior specification suite&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#behavior-scenario-specification-style&quot;&gt;Behavior/scenario specification style&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#pros-and-cons-of-mspec&quot;&gt;Pros and cons of MSpec&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;#bdd-idea-and-implementation&quot;&gt;BDD - идея и реализация&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#benefits&quot;&gt;Benefits&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#simplespec&quot;&gt;SimpleSpec - simple unit-level BDD framework for .net&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;#resources&quot;&gt;Resources&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;

&lt;h2&gt;
&lt;a name=&quot;from-unit-tests-to-specifications&quot;&gt;From unit tests to behavior specifications.&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
&lt;a name=&quot;unit-testing-and-drawback-analysis&quot;&gt;Goal of unit testing and drawback analysis&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
&lt;strong&gt;Основная задача модульных теств - проверить правильность работы того или иного аспекта модуля, и обнаружить дефекты в программном обеспечении&lt;/strong&gt;. 
&lt;/p&gt;
&lt;p&gt;
Модульные тесты обычно являются API-ориентированными, то есть фокусируясь на проверке правильности работы модуля, они описаны в терминах этого модуля, его API. API модуля в процессе разработки изменяется довольно часто, например, как результат рефакторинга либо перераспределения обязанностей. В итоге, &lt;strong&gt;API-ориентированность тестов увеличивает частоту их изменения при внесении изменений в код.&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;Юнит тест представляет собой смесь состоящую из бизнес задач и механизмов тестирования этих задач.&lt;/strong&gt; К бизнес задачам можно отнести совершение тех или иных действия в заданном контексте, проверка их результата (это то, ЧТО мы тестируем). Механизмы тестирования - это mocking frameworks, assertion libraries, все то, что позволяет осуществлять верификацию результата (это то, КАК мы тестируем). Эта смесь усложняет чтение тестов и будущую их поддержку, так как нужно постоянно отделять бизнес специфику от деталей механизмов тестирования. Это приводит к тому, что тесты не сопровождаются должным образом, становясь балластом, начиная приносить больше вреда чем пользы.
&lt;/p&gt; 

&lt;p&gt;
В погоне за упрощением юнит теста, разработчики структуриют тело теста, например, используя &lt;a href=&quot;http://xunitpatterns.com/Four%20Phase%20Test.html&quot;&gt;Four Phase Test Approach&lt;/a&gt;, либо &lt;a href=&quot;http://c2.com/cgi/wiki?ArrangeActAssert&quot;&gt;Arrance-Act-Assert&lt;/a&gt;. Идея состоит в логическом разделении тела метода на части. Разбиение происходит в терминах механизма тестирования, а не в терминах бизнес специфики: инициализация теста, подготовка тестируемого объекта, совершение действий над объектом, верификация результата. Структурирование набора тестов происходит обычно по принципу &lt;a href=&quot;http://xunitpatterns.com/Testcase%20Class%20per%20Class.html&quot;&gt;Test Case Per Class&lt;/a&gt;, либо же &lt;a href=&quot;http://xunitpatterns.com/Testcase%20Class%20per%20Feature.html&quot;&gt;Test Case Per Feature&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
Отбросив технические сложности, такие как плохая читабельность, сложность сопровождения, то основным концептуальным недостатком, независимо от применения test-last либо test-first development&#39;а, является то, что &lt;strong&gt;юнит тест позволяет проверить то, что созданное ПО работает корректно, но отнюдь не то, что ПО является корректным&lt;/strong&gt;.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;executable-specifications&quot;&gt;Executable specifications&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Размышления над вопросом &lt;strong&gt;&quot;Что значит сделать корректное ПО?&quot;&lt;/strong&gt; приводят к понятию поведения, спецификации требований. Корректное ПО - такое ПО, поведение которого соответсвует предъявленным ему требованиям. &lt;strong&gt;Идея BDD подхода в описании требований (спецификаций), которые могут быть выполнены - executable specifications&lt;/strong&gt;. Вместе с тем, executable specification наследуют все свойства юнит тестов: &lt;code&gt;repeatable, independent, thorough, maintainable, fast, automatic, professional, readable&lt;/code&gt;. &lt;strong&gt;Основное отличие от юнит тестов в том, что юнит тест верифицирует правильность работы модуля, а executable specification - описывает желаемое поведение модуля.&lt;/strong&gt; Описывая поведение модуля, спецификация привязана к API модуля в меньшей степени. Так как поведение меняется значительно реже чем API модуля и сам модуль, то и частота обновления спецификаций ниже, чем частота обновления юнит тестов.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;given-when-then&quot;&gt;Given-when-then&lt;/a&gt;
&lt;/h3&gt;
Использование конструкций &lt;code&gt;given-when-then&lt;/code&gt; является методикой структурирования тела спецификаций. Однако, если подходы &lt;a href=&quot;http://xunitpatterns.com/Four%20Phase%20Test.html&quot;&gt;Four Phase Test Approach&lt;/a&gt;, либо &lt;a href=&quot;http://c2.com/cgi/wiki?ArrangeActAssert&quot;&gt;Arrance-Act-Assert&lt;/a&gt; фокусировались на разбиении теста в терминах механизмов тестирования, то части спецификации &lt;code&gt;given/when/then&lt;/code&gt; выражены в терминах бизнеса. Вместо инициализации теста и подготовке объекта мы думаем о контексте, в рамках которого происходит операция (&lt;code&gt;given&lt;/code&gt;), вместо вызова методов думаем о сути выполняемой операции (&lt;code&gt;when&lt;/code&gt;), вместо тестирования результатов - думаем о поведении (&lt;code&gt;then&lt;/code&gt;). Помимо концептуального различия, такой &lt;strong&gt;given-when-then подход служит для унификации языка описания спецификаций&lt;/strong&gt;.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;context-specification-style&quot;&gt;Scenarios and context/specification style&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Применение BDD подхода в самой малой его толике - это именование юнит тестов таким образом, чтобы оно описывало поведение тестируемого аспекта. Для названия обычно тоже применяется тот же шаблон &lt;code&gt;given-when-then&lt;/code&gt;, либо его разновидности.
&lt;/p&gt;

&lt;p&gt;
Однако &lt;code&gt;BDD&lt;/code&gt; - это нечто большее. &lt;code&gt;BDD&lt;/code&gt; предполагает выделение поведения как отдельной концепции. Поведения в свою очередь проявляются при каких-то условиях. Набор таких условий определяет сценарий. &lt;strong&gt;Сontext/Specification стиль предлагает рассматривать поведения не как плоский список, а фокусироваться на различных сценариях, в рамках которых проявляются те или иные поведения.&lt;/strong&gt; Следствием такого подхода является то, что одно и тоже поведение может проявлятся в разных сценариях. BDD framework&#39;и в отличие от unit testing framework делают явный акцент на поведениях, сценариях, а не на механике тестирования.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;flavors-of-BDD&quot;&gt;Flavors of BDD approach&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
BDD подход можно разделить на два вида: &lt;a href=&quot;http://stackoverflow.com/questions/307895/what-is-the-most-mature-bdd-framework-for-net/5586525#5586525&quot;&gt;xSpec и xBehave&lt;/a&gt;. 
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;xSpec - это применение BDD на уровне модулей (unit level), то есть описание спецификаций по отношению к модулям.&lt;/strong&gt; Обычно используются те же технические инструменты, что и для написания юнит тестов (mocking frameworks, assertion libraries, unit test frameworks). Некоторые BDD framework&#39;и предлагают описывать спецификации, на основании которых потом генерируется код тестов, то есть разделять описание спецификации и реализацию. Думаю, что такая практика неприменима для unit-level спецификаций. Так как единственной ролью, которая работает со спецификациями, путем их описания и реализации, является разработчик, то разделять описание и реализацию спецификации в два разных места не стоит. Более привлекательным выглядит подход, когда описание может быть сгенерировано отдельно на основании кода спецификации, например в виде отчета. Unit-level спецификации применимы на этапе дизайна и реализации ПО, например, как замена стандартных юнит тестов. 
&lt;br /&gt;
Примеры фреймворков: &lt;a href=&quot;http://nspec.org/&quot;&gt;MSpec&lt;/a&gt;, &lt;a href=&quot;http://nspec.org/&quot;&gt;NSpec&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;xBehave - это применение BDD для описания высокоуровневых пользовательских историй (user stories) в виде приемочных критериев (&lt;a href=&quot;http://testdrivendeveloper.com/2008/04/02/AcceptanceTestDrivenDevelopmentExplained.aspx&quot;&gt;acceptance criteria&lt;/a&gt;) при помощи given-when-then синтаксиса.&lt;/strong&gt; Охватывает этап сбора и анализа требований, а также этап дизайна. Такой подход пропагандирует описание спецификаций одной ролью, например аналитиком либо тестировщиком, а реализацию уже разработчиком. Тут уже становится оправданным разделить описание и реализацию спецификаций, например в виде текстового файла с историями и автоматической кодогенераций тестов. В таком случае сложно обеспечивать синхронизацию текста историй и сгенерированного кода тестов. Также возникает сложность в реализации тестов. А именно, как реализовать проверку спецификации таким образом, чтобы не потерять свойств юнит тестов (repeatable, independent, thorough, maintainable, fast, automatic, professional, readable). Если же уровень спецификации - пользовательская история, то проверка может вовлечь в себя взаимодействие систем или подсистем. А значит, в лучшем случае - это будет интреграционный тест. И возможно ли вообще в этом случае сделать автоматический тест? Также предполагается переход от story-level спецификаций к unit-level спецификациям. Насколько этот переход будет бесшовным, и будет ли в нем польза - тоже остается для меня пока вопросом.
&lt;br /&gt;
Примеры фреймворков: &lt;a href=&quot;http://nbehave.codeplex.com/&quot;&gt;NBehave&lt;/a&gt;, &lt;a href=&quot;https://github.com/techtalk/SpecFlow&quot;&gt;SpecFlow&lt;/a&gt;.
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;mspec-usage&quot;&gt;Использование MSpec&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
Далее рассмотрим воплощение идей BDD на реальных примерах, взятых из гипотетической бухгалтерской системы  одного приложения с использованием &lt;a href=&quot;https://github.com/machine/machine.specifications&quot;&gt;MSpec&lt;/a&gt;. Данный раздел не является исчерпывающим руководством по использованию MSpec. Здесь я хочу лишь показать как использовался MSpec, и насколько он применим в тех или иных сценариях.
&lt;/&gt;

&lt;h3&gt;
&lt;a name=&quot;mspec-specification-example&quot;&gt;Пример спецификации в стиле context/specification.&lt;/a&gt; 
&lt;/h3&gt;
&lt;p&gt;
Спецификация состоит из установления контекста (given), в MSpec для этого применяется делегат Establish, выполнения бизнес операции (when), при помощи делегата Because, и наконец верификации поведения (then), при помощи делегатов It. Given и when (Establish, Because) компоненты формируют сценарий, в котором наблюдается то или иное поведение (It).
&lt;/p&gt;

&lt;pre class=&quot;brush:csharp&quot;&gt;
[Subject(typeof(Shipment))]
public class When_applying_postage_info_from_external_source
{
    Establish _ = () =&amp;gt; shipment= InitializePackaging(
        ShippingInfo.Create(PostalService.UA, true, Shape.POSTCARD, new 20.44m.Dollars());
    Because of =()=&amp;gt; pack.ApplyExternalPostage();

    It should_override_shipping_cost =
        () =&amp;gt; shipment.PostageCost.ShouldEqual(new PriceInfo(20.44m, Currency.USD));
    It should_not_override_postal_service =
        () =&amp;gt; shipment.PostalService.ShouldEqual(PostalService.UA);

    static Shipment shipment;
}
&lt;/pre&gt;

&lt;pre class=&quot;brush:csharp&quot;&gt;
[Subject(typeof(Shipment))]
public class When_applying_postage_info_for_album_which_is_shipped_via_usps_pm
{
    Establish _ =()=&amp;gt; shipment = InitializePackaging(&quot;USPS-PM&quot;, 20.44m.Dollars()));
    Because of = () =&amp;gt; shipment.ApplyExternalPostage();

    It should_override_shipping_cost =
        () =&amp;gt; shipment.InternalCost.ShouldEqual(new PriceInfo(20.44m, Currency.USD));

    static Shipment shipment;
}
&lt;/pre&gt;

&lt;p&gt;
Имена класса и делегатов используются в описательных целях, а также показываются в результатах выполнения тестов. MSpec умеет заменять подчеркивания пробелами, так что в итоге может получится более менее внятное и читабельное английское предложение.
&lt;/p&gt;

&lt;p&gt;
Тех, кто привык писать юнит тесты смущает, что на каждый тест приходится писать новый класс. Однако если взглянуть на то, что это не тест, а законченный сценарий (контекст + наблюдаемые поведения), то можно оправдать overhead создания нового класса в целях моделирования отдельного сценария. На самом деле, ничего криминального в подходе class-per-specification на мой взгляд нету. Это не production code, наличие большого количества классов не увеличивает сложность приложения, так как каждый из таких классов независим друг от друга, эти классы не объединяются в модули с внутренними зависимостями между собой.  Для понимания набора спецификаций не нужно анализировать граф зависимостей, спецификации автономны, и представляют скорее плоский список. Также, как показывает практика, при переходе от тестов к спецификациям, их количество уменьшается, засчет того, что количество возможных сценариев в большинстве случаев будет меньше количества сценариев умноженных на уникальное количество поведений (юнит тесты).
&lt;/p&gt;

&lt;p&gt;
Также слышна критика в адрес MSpec касательно применения делегатов и конструкций &quot;=()=&gt;&quot;. Тем не менее, функциональный подход позволяет сделать спецификации self-descriptive, давая внятные имена делегатам (It should_override_shipping_cost). Хотя в NSpec например &lt;a href=&quot;http://nspec.org/#specifications&quot;&gt;применяется несколько другой подход&lt;/a&gt;. Больше смущает то, что классы и его члены смоделированы как static. 
&lt;/p&gt;


&lt;h3&gt;
&lt;a name=&quot;failure-verification&quot;&gt;Failure verification&lt;/a&gt; 
&lt;/h3&gt;

&lt;p&gt;
Одно из разновидностей поведения - это проверка того, что операция не может быть выполнена при определенном сценарии.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
[Subject(typeof(Shipment))]
public class when_verifying_delivery_for_shipment_without_external_postage_info_specified
{
    Establish _ = () =&amp;gt; packaging = InitializePackaging(PostalService.USPS, true, null);
    Because of = () =&amp;gt; failure = Catch.Exception(() =&amp;gt; shipment.VerifyDelivery());

    It should_fail = () =&amp;gt;
    {
       failure.ShouldBeOfType(typeof(DeliveryVerificationFailed));
       failure.Message.Should.Be(&quot;Delivery verification failed due to non existent postage info/&quot;);
    }

    static Shipment shipment;
    static Exception failure;
}
&lt;/pre&gt;

&lt;p&gt;
Для проверки того, что операция не может быть выполнена, приходится совершать много телодвижений. Во-первых, нужно, определить статическое поле типа Exception, которое будет хранить информацию об ошибке. В делегате Because нужно применить &lt;span class=&quot;hintUnderTheHood&quot;&gt;кунг-фу&lt;/span&gt; конструкцию &lt;code&gt;Catch.Exception&lt;/code&gt;, также которая подразумевает использование делегата. В итоге получается конструкция из двух вложенных делегатов, которая отнюдь не поражает нас читабельностью. В конце концов, в делегате It нужно проверить что это именно та ошибка, которая подразумевается сценарием. Для сравнения, конструкция проверки ошибочной ситуации в классическом юнит тесте (используется &lt;a href=&quot;http://fluentassertions.codeplex.com/&quot;&gt;FluentAssertions&lt;/a&gt;).

&lt;pre class=&quot;brush:csharp&quot;&gt;
[Then]
public void test_for_operation_failure()
{
    analyzer.Invoking(a =&amp;gt; a.IsFirstBillingRun(new DateTime(2011, 6, 1)))
        .ShouldThrow&amp;lt;ArgumentException&amp;gt;()
        .WithMessage(&quot;Client was not billed on&quot;, ComparisonMode.Substring);
}
&lt;/pre&gt;
&lt;/p&gt; 

&lt;h3&gt;
&lt;a name=&quot;simple-context&quot;&gt;Simple context&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Иногда сценарий слишком прост, контекст которого подразумевается по умолчанию, либо слишком незначителен чтобы выделять его в отдельный шаг. В таком случае спецификацию можно упростить, объеденив given и when шаги. Например.

&lt;pre class = &quot;brush:csharp&quot;&gt;
[Subject(typeof(PostalService))]
public class when_postal_service_is_parsed_from_string_with_postal_service_and_mail_class_separated_by_hyphen
{
    static PostalServiceservice;
    Because of = () =&amp;gt; service = PostalService.Parse(&quot;USPS-FCM&quot;);

    It should_use_first_part_of_string_before_hyphen_as_postal_service =
        () =&amp;gt; service.PostalService.ShouldEqual(&quot;USPS&quot;);
    It should_use_second_part_of_string_after_hyphen_as_mail_class =
        () =&amp;gt; service.MailClass.ShouldEqual(&quot;FCM&quot;);
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;action-and-context-common-scenarios&quot;&gt;Action-common and context-common scenarios&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Обычно несколько спецификаций можно сгруппировать по общим признакам.
&lt;ol&gt;
&lt;li&gt;Выполняется одна и та же операция, но меняется контекст.&lt;/li&gt;
&lt;li&gt;Контекст один и тот же, но меняется операция.&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;
Эту особенность можно использовать и вынести общую часть за скобки, тем самым разгрузив спецификацию от повторяющихся деталей, которые подразумеваются по умолчанию. Например, как в данном context-common scenario.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt; 
public class with_weight_calculator
{
    Establish _ = WeightCalculator;

    private static void WeightCalculator()
    {
        deviceWeightProvider = MockRepository.GenerateStub&amp;lt;DeviceWeightProvider&amp;gt;();
        boxSelector = MockRepository.GenerateStub&amp;lt;BoxSelector&amp;gt;();
        postageCalculator = new WeightCalculator(
            deviceWeightProvider ,
            boxSelector)
    }

    static WeightCalculator weightCalculator;
    static BoxSelector boxSelector;
    static DeviceWeightProvider deviceWeightProvider;
}

[Subject(typeof(PostageCalculator), &quot;external shipping cost calculation&quot;)]
public class when_calculating_weight_for_medium_device_packed_into_safebox : with_weight_calculator
{
    Establish context = () =&amp;gt;
    {
        device = Device.New(new Client(1, &quot;1&quot;), &quot;Notebook&quot;)
            .WithBoxingInfo(&quot;Safe&quot;) 
            .Build();
    };
    Because of = () =&amp;gt; weight= weightCalculator.CalculateWeight(order);

    It should_be_greather_than_1kg =()=&amp;gt; weight.Should().BeGreather(1.Kilograms());

    static Device device;
    static Weight weight;
}
&lt;/pre&gt;

&lt;p&gt;
MSpec позволяет выделять общие части сценария поднимая их в базовый класс. Когда несколько сценариев разделяют общий контекст, я предпочитаю базовый класс называть по шаблону &quot;with_{COMMON_CONTEXT}&quot;. Если разделяется общая операция - то по шаблону &quot;when_{COMMON_ACTION}&quot;. 
&lt;/p&gt;

&lt;p&gt;
Можно вынести как Establish, так и Because, а также общие члены, которые используются в сценарии. &lt;strong&gt;Однако не все то, что делать можно, делать стоит. Введение иерархии наследования сильно связывает классы, и на это должна быть весомая причина.&lt;/strong&gt; К примеру есть общий контекст, повторяющийся от спецификации к спецификации, который подразумевается по умолчанию, и описательная сила спецификации не уменьшится, если разнести создание контекста по разным местам. Важно помнить, что спецификация должна быть читабельной, и коммуницировать разработчику поведение. Если для того чтобы понять, что имелось ввиду, приходиться ходить по 3-уровневой иерархии наследования, то это не true. Я бы не выходил за рамки 2-уровневой иерархии для структурирования спецификаций. Двух уровней должно быть достаточно.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;behavior-specification-suite&quot;&gt;Behavior specification suite&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Есть случаи, в которых применение &lt;code&gt;context\specification style&lt;/code&gt; представляется излишней сложностью. Например, нужно проверить простое поведение, сценарий в предельно просто и неразрывно связан с поведением, например вызов метода и проверка его результата. В таком случае не хочется создавать много классов с двумя строчками кода в каждом. Например. 

&lt;pre class =&quot;brush:csharp&quot;&gt;
[Subject(typeof(Weight))]
public class When_converting_weight
{
    It should_convert_ounces_to_grams_according_to_coefficient =
        () =&amp;gt; new Weight(1, Weight.Ounce).ConvertToGramms()
            .ShouldEqual(new Weight(1 / gram_to_ounce_coefficient, Weight.Gram));
    It should_convert_grams_to_ounces_according_to_coefficient =
        () =&amp;gt; new Weight(1, Weight.Gram).ConvertToOunces()
            .ShouldEqual(new Weight(1 * gram_to_ounce_coefficient, Weight.Ounce));
    It should_do_nothing_when_converting_grams_to_grams =
        () =&amp;gt; new Weight(1, Weight.Gram).ConvertToGramms().ShouldEqual(new Weight(1, Weight.Gram));
    It should_do_nothing_when_converting_ounces_to_ounces =
        () =&amp;gt; new Weight(1, Weight.Ounce).ConvertToOunces().ShouldEqual(new Weight(1, Weight.Ounce));

    const decimal GRAM_TO_OUNCE_KOEFFICIENT = 0.03527m;
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
В таком случае легче просто описать список поведений, которые неявно включают в себя контекст и операцию, чем выделять эти шаги отдельно, и моделировать каждое поведение в виде отдельного класса.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;behavior-scenario-specification-style&quot;&gt;Behavior/scenario specification style&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Иногда нужно проверить, что отдельно взятое поведение, проявляется в разных сценариях. Если сравнивать с context/specification style, где мы движемся от сценария к поведениям, которые в нем наблюдаются, то в данном случае, мы движемся в обратном направлении - от поведения к сценариям, в которых оно проявляется либо не проявляется. Например.

&lt;pre class = &quot;brush:csharp&quot;&gt;
[Subject(typeof(PostalService))]
public class postal_service_is_considered_to_be_more_expensive
{
    It is_usps_priority_mail = () =&amp;gt; ShippingInfo.Builder
        .New(PostalService.USPS, MailClass.PRIORITY_MAIL).Build()
        .IsPremium.ShouldBeTrue();
    It is_russin_post_fast = () =&amp;gt; ShippingInfo.Builder
        .New(PostalService.RUS, MailClass.FAST).Build()
        .IsPremium.ShouldBeTrue();
    It is_ukraine_post = () =&amp;gt; ShippingInfo.Builder
        .New(PostalService.UAPOST).Build()
        .IsPremium.ShouldBeTrue();
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
Происходит проверка одного и того же поведения (shipping service should be premium) в разных сценариях (when usps priority mail, when fedex, etc.). Если бы мы применили context/specification style, то получили бы четыре сценария. MSpec, будучи context/specification фреймворком, не поддерживает &lt;code&gt;behavior/scenario&lt;/code&gt; стиль. Пример приведенный выше - это лишь попытка использовать существующие средства для описания описания спецификации в behavior/scenario стиле.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;pros-and-cons-of-mspec&quot;&gt;Pros and cons of MSpec&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Подведем итог, сформулируем плюсы и минусы использования MSpec в behavior-driven-development&#39;е.
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Pros&lt;/strong&gt;:
&lt;ol&gt;
&lt;li&gt;
MSpec - фреймворк который был специально построен для поддержки BDD стиля, а значит впитал в себя его идеи. В противоположность unit-testing фреймворкам, где разработчик волен сам выбирать стиль написания юнит тестов, использование MSpec  принуждает разработчика именно к behavioral стилю. Это может быть как плохо так и хорошо, в зависимости от того, насколько жесткие правила и ограничения  фреймворк накладывает на разработчика. Явно позиционируется фокус на спецификацию поведения, а не тестирования функциональности.
&lt;/li&gt;
&lt;li&gt;
Инструментальная поддержка. Интеграция с ReSharper.
&lt;/li&gt;
&lt;li&gt;
Описание спецификации и ее реализация не разделены (разные файлы), как это обычно делается в xBehave BDD framework&#39;ах.
&lt;/li&gt;
&lt;li&gt;
Поддержка вынесения общего контекста или общей операции &quot;за скобки&quot; путем поднятия делегатов Establish, Because в базовый класс.
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;Cons&lt;/strong&gt;:
&lt;ol&gt;
&lt;li&gt;
Использование static классов.
&lt;/li&gt;
&lt;li&gt;
Синтаксический шум. Чрезмерное использование делегатов приводит к появлению громоздких синтаксических конструкций, в которых теряется бизнес специфика.
&lt;/li&gt;
&lt;li&gt;
Низкий уровень readability для средних и больших спецификаций. Небольшие спецификации компактны и читабельны, но с увеличением размера спецификации  читабельность резко ухудшается.
&lt;/li&gt;
&lt;li&gt;
Смесь бизнес и технических деталей в теле спецификации. По сравнению с юнит тестами, бизнес специфика явно утверджается при помощи имен классов и делегатов, но по-прежнему перемешана с механизмами тестирования (использование mocking framework&#39;ов, assertions libraries, создание/подготовка объекта).
&lt;/li&gt;
&lt;li&gt;
Отсуствие поддержки behavior-scenario стиля описания спецификаций.
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;conclusion&quot;&gt;Conclusion&lt;/a&gt;
&lt;/h2&gt;
&lt;h3&gt;
&lt;a name=&quot;bdd-idea-and-implementation&quot;&gt;BDD - идея и реализация&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Что касается перехода от unit-testing к behavior-driven development, стоит различать две вещи: идея, концепция и ее реализация. BDD как идея и концепция мне нравится, это сдвиг мировоззрения по сравнению с применением стандартного подхода юнит-тестирования. Использование же существующего фреймворка, такого как MSpec, показывает в свою очередь и определенные минусы. Однако это не повод отбрасывать идею вовсе. Когда я показываю спецификации, написанные на MSpec, другим разработчикам, то сразу возникают вопросы: &quot;А что: каждый тест - это класс?&quot;, &quot;А почему классы статические?&quot;, &quot;Что за =()=&gt;?&quot;. Не увидев за этими деталями реализации саму идею и принципы BDD, разработчик отворачивается от этого подхода и вовсе, приводя в качестве аргументов &quot;против&quot; неудавшиеся детали реализации.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;benefits&quot;&gt;Benefits&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Моя личная практика использования BDD-подхода, в context/specification style, привела следующим наблюдениям:
&lt;ul&gt;
&lt;li&gt;
Уменьшение количества сценариев по отношению к количеству юнит-тестов.
&lt;/li&gt;
&lt;li&gt;
Поведения группируются относительно сценариев в которых они проявляются. Легче стало проверять одно и тоже поведение, которое проявляется в разных сценариях.
&lt;/li&gt;
&lt;li&gt;
Легче стало применять spec(test)-first подход. Описания спецификаций меньше привязаны API модуля, чем юнит тесты.
&lt;/li&gt;
&lt;li&gt;
Спецификации легче в сопровождении. Возвращаясь через некоторое время к спецификации, бывает достаточно прочитать ее описание для понимания поведения, и реже для этого приходиться лезть в код модуля.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;simplespec&quot;&gt;SimpleSpec - simple unit-level BDD framework for .net&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Те, кто принял идею BDD, и кого не устраивают существующие реализации, могут создать свой фреймворк на основании используемой ими unit-testing library. Слово фреймворк может в этом случае звучать пафосно, так как он может состоять всего из одного класса.
&lt;/p&gt;
&lt;p&gt;
В своей практике я тоже пришел к идее создания &lt;span class=&quot;hintUnderTheHood&quot;&gt;велосипеда&lt;/span&gt; собственной простой версии BDD framework&#39;а, который бы учитывал  наблюдения, которые я сделал при работе с MSpec. В следующей статье из цикла &quot;BDD Clarified&quot; (work is in progress) детали и использование этого фреймворка будут рассмотрены подробнее. А пока лишь реклама. 

&lt;pre class=&quot;brush:csharp&quot;&gt;
[Scenario]
public class when_calculating_user_attendance_variation : with_user_attendance_statistics_analyzer
{
    double revenueVariation;

    public when_calculating_user_attendance_variation()
    {
        IsA&amp;lt;ScenarioSpecification&amp;gt;();
        Given(user_attendance_statistics_analyzer);
        Given(() =&amp;gt; user_sent_visits(
            new UserVisit(client, 1, new DateTime(2011, 7, 1), 10, 1, new Money(3.0m, Currency.USD)),
            new UserVisit(client, 1, new DateTime(2011, 7, 1), 10, 1, new Money(4.0m, Currency.USD)),
            new UserVisit(client, 2, new DateTime(2011, 8, 1), 10, 1, new Money(10.0m, Currency.USD)),
            new UserVisit(client, 3, new DateTime(2011, 9, 1), 10, 2, new Money(4.0m, Currency.USD))));
        Given(() =&amp;gt; analyzer.LoadUserStatistics(client));
        When(() =&amp;gt; userCountVariation = analyzer.CalculateUserCountVariation(new DateTime(2011, 8, 1)));
    }

    [Then]
    public void variation_should_be_calculated_upon_total_count_of_all_users_of_subsequent_statistics_snapshots()
    {
        // (10USD - 7 USD)/7USD
        userCountVariation .Should().BeApproximately(0.4285, 0.0001);
    }
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;resources&quot;&gt;Resources&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;
Behavior driven development:
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;http://www.code-magazine.com/article.aspx?quickid=0805061&amp;page=1&quot;&gt;Behavior driven development by Scott Bellware&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://en.wikipedia.org/wiki/Behavior_driven_development&quot;&gt;BDD on wiki&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://dannorth.net/introducing-bdd/&quot;&gt;Introducing BDD by Dan North&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://blog.daveastels.com/files/BDD_Intro.pdf&quot;&gt;Yet another BDD intro&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://dannorth.net/whats-in-a-story/&quot;&gt;What&#39;s in a story?&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://codebetter.com/iancooper/2009/03/31/seizing-the-bdd-nettle/&quot;&gt;Seizing the BDD Nettle by Ian Cooper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://elegantcode.com/2010/03/01/an-evolution-of-test-specification-styles-my-journey-to-mspec/&quot;&gt;An Evolution of Test-Specification Styles – My Journey to MSpec&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;
BDD frameworks:
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://github.com/machine/machine.specifications&quot;&gt;MSpec&lt;/a&gt; - Machine.Specifications is a Context/Specification framework geared towards removing language noise and simplifying tests. &lt;a href=&quot;http://www.awkwardcoder.com/index.php/2010/04/13/how-to-mspec/&quot;&gt;How to MSpec?&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://github.com/techtalk/SpecFlow&quot;&gt;SpecFlow&lt;/a&gt; - binding business requirements to .Net code. &lt;a href=&quot;http://www.specflow.org/specflow/getting-started.aspx&quot;&gt;Getting started&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://nbehave.codeplex.com/&quot;&gt;NBehave&lt;/a&gt; is a framework for defining and executing application requirement goals.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://nspec.org/&quot;&gt;NSpec&lt;/a&gt; is a BDD framework for .NET of the xSpec (context/specification) flavor. NSpec is intended to be used to drive development through specifying behavior at the unit level. NSpec is heavily inspired by RSpec and built upon the NUnit assertion library.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/1778681688501508645/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/09/bdd-explained-with-mspec.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/1778681688501508645'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/1778681688501508645'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/09/bdd-explained-with-mspec.html' title='Behavior Driven Development Explained with MSpec'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-280885292267785021</id><published>2011-05-23T00:51:00.002+03:00</published><updated>2011-09-29T23:02:25.310+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="NHibernate"/><title type='text'>NHibernate: mapping class hierarchy</title><content type='html'>&lt;p&gt;
Эта статья погрузит Вас в тонкие материи работы с NHibernate, а именно mapping иерархии классов в смешанном стиле &lt;a href =&quot;http://www.nhforge.org/doc/nh/en/index.html#inheritance-tablepersubclass&quot;&gt;table-per-subclass&lt;/a&gt; + &lt;a href=&quot;http://www.nhforge.org/doc/nh/en/index.html#inheritance-tableperclass&quot;&gt;table-per-hierarchy&lt;/a&gt; + &lt;a href=&quot;http://www.nhforge.org/doc/nh/en/index.html#inheritance-tablepersubclass-discriminator&quot;&gt;discriminator&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
В стиле table-per-subclass, равно как и table-per-hierarchy, нет ничего особенного, это все довольно стандартные и банальные вещи, если речь идет об отображении иерархии классов. Интересное начинается тогда, когда задача выходит за рамки простых учебных примеров, и нужно, например, совместить эти два стиля в рамках отображения одной иерархии классов. Как раз в этих ситуациях и проявляется истинная мощь NHibernate. Об этом мы сегодня и поговорим.
&lt;/p&gt;

&lt;p&gt;
Попутно будет раскрыт ряд интересных моментов, таких как:
&lt;ul&gt;
&lt;li&gt;Что такое implicit polymorphism в NHibernate.&lt;/li&gt;
&lt;li&gt;В чем заключается сложность mapping&#39;a интерфейсов.&lt;/li&gt;
&lt;li&gt;[BONUS] Как в NHibernate вытянуть все данные из БД в рамках одного простого запроса.&lt;/li&gt;
&lt;li&gt;Как выглядит sql запрос, который сгенерирует NHibernate для иерархии классов.&lt;/li&gt;
&lt;li&gt;Использование любого sql-выражения в качестве discriminator, а не только фиксированной колонки.&lt;/li&gt;
&lt;/ul&gt;
Mapping будет описываться c помощью &lt;a href = &quot;http://fluentnhibernate.org/&quot;&gt;Fluent NHibernate&lt;/a&gt;, также будут даваться ссылки на raw xml-based конструкции.
&lt;/p&gt;

&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;

&lt;h2&gt;Исходная задача&lt;/h2&gt;

&lt;h3&gt;Object model&lt;/h3&gt;
&lt;p&gt;
Предметной областью является интернет-магазин, который выполняет заказы по созданию эксклюзивных альбомов и отправке их заказчику. Задача состоит в необходимости отслеживания событий, которые произошли с альбомом в процессе его обработки и отправки. Сущность Album обладает понятием статуса (Created, Prepared, Cancelled, ...), однако исключительно статуса недостаточно для описания жизненного цикла заказа. Некоторые значимые события могут не переводить заказ в новый статус. Помимо этого с каждым событием потенциально могут быть ассоциированы некоторые данные, специфичные именно для этого события. Например, для события отмены заказа (cancellation) важно знать кем был отменен заказ (WhoCancels), и какая причина. Если произошла ошибка, то важно знать описание ошибки, и где она произошла. В итоге приходим к такой объектной модели.
&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFtzr4pI9AIUNDj8WDYECw6B33P0pja0y3MlDt4Qvmi6GJN2D756sG-ceS1oVwxjomqsiuXFLYqRC-8w4rRDjAWSCVr9KCLzDkcjT6evFSBlsn6BJXEMdYoe0_k51-qmBrNGZck0DUvNw/s800/album_event_hierarchy.png&quot; alt = &quot;Object model&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;
Данными, общими для всех событий, являются тип события (Type), время его возникновения (Timestamp), статус (AlbumStatus), в который это событие перевело или не перевело заказ. Некоторые события, такие как failure, cancellation, submit, содержат дополнительные данные. Другие события не содержат дополнительных данных, однако в объектной модели мы хотим представить их как отдельные классы, вместо того чтобы использовать базовый. Сущность Album имеет две ассоциации с событиями. Первая - это Events, то есть набор всех событий, произошедших с этим заказом, второй - LastEvent, то есть ссылка на последнее событие. Событие в свою очередь ссылается на заказ, к которому оно относится. То есть получается bidirectional association.
&lt;/p&gt;

&lt;h3&gt;Relational model&lt;/h3&gt;
&lt;p&gt;
Схема базы данных в свою очередь выглядит так.
&lt;img src = &quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTV2QOaH2ZDGk84FXxWTQa8mJw_9pssWs9YZDsTru0AuFDPLpfrSfGHoAXH-f7dbU-kqX3o5S9q-6B2JzG3OkoGoWFCVIRktMXsG-kuWClZSlyTNTnt0m6hVNV-gScy4ny026JylpcRHs/s800/Album_events_table_per_type.png&quot; alt=&quot;Database schema&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;
С точки зрения БД, иерархия смоделирована в виде таблицы AlbumEvent, которая содержит данные, общие для всех событий. Данные расширенных событий (failure, cancellation, creation) помимо таблицы album_event содержатся в дополнительных таблицах (AlbumEventCancel, AlbumEventFail), которые связаны с AlbumEvent 1:1 по первичному ключу. Связи между Album и AlbumEvent представлена при помощи внешних ключей AlbumID и LastEventID.
&lt;/p&gt;


&lt;h2&gt;
Table-per-hierarchy
&lt;/h2&gt;
&lt;p&gt;
Стиль table-per-hierarchy состоит в том, что вся иерархия классов отображается на одну таблицу базы данных. Схема этой таблицы покрывает всё разнообразие данных всех классов иерархии. В нашем случае это могло бы выглядеть так.
&lt;/p&gt;
&lt;img src = &quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfi1dvUEA0iq1-OkqhASL3YIJZyL2AZWYKT5VaAEvPlaxJXJeCL8krqy8UBQR40yqRmWmjB1I9WsdGwVj88V275NP5qp-fUxzWpwnYVO10ANRyl9_rjnFxBi58_rV-U5Acr-Ux76FfHuQ/s800/album_event_all_in_one.png&quot; /&gt;

&lt;p&gt;
Для того, чтобы отличить какому классу в иерархии соответствует та или иная строка, NHibernate&#39;у необходимо указать так называемый discriminator value. В нашем случае - это Event type. Вот как бы выглядел mapping в table-per-hierarchy стиле.

&lt;pre class=&quot;brush:csharp&quot;&gt;
public class AlbumEventMap : ClassMap&amp;lt;AlbumEventBase&amp;gt;
{
    public AlbumEventMap()
    {
        Table(&quot;AlbumEvent&quot;);

        Id(e =&gt; e.ID).Column(&quot;EventID&quot;).GeneratedBy.Native();
        Map(e =&gt; e.AlbumStatus).Not.Nullable().Length(32);
        Map(e =&gt; e.Timestamp).Not.Nullable();

        // tell NHibernate to use value from EventType column as a discriminator 
        DiscriminateSubClassesOnColumn(&quot;EventType&quot;);
    }
}

// usage of SubclassMap
public class FailedAlbumEventMap : SubclassMap&amp;lt;FailedAlbumEvent&amp;gt;
{
    public FailedAlbumEventMap()
    {
        // if EventType column has value FAIL, then map database row to FailedAlbumEvent class
        DiscriminatorValue(&quot;FAIL&quot;);

        // map additional properties
        // NOTE: all additional columns should be marked as nullable
        Map(e =&gt; e.Reason).Nullable().Length(40);
        Map(e =&gt; e.Origin).Nullable().Length(40);
        Map(e =&gt; e.Details).AsUnicodeString().Nullable().Length(1024);
    }
}

public class PreparedAlbumEventMap : SubclassMap&amp;lt;PreparedAlbumEvent&amp;gt;
{
    public PreparedAlbumEventMap()
    {
        DiscriminatorValue(&quot;PREPARED&quot;);
        // no additional properties to map
    }
}
&lt;/pre&gt;
&lt;/p&gt;
Минус в том, что колонки, которые представляют дополнительные данные, должны быть nullable, даже если это и не соответсвует бизнес ограничениям. Это довольно неприятно. Помимо этого, таблица будет скорее всего полузаполненной, снова же из-за null значений. Из плюсов, это то, что данные находятся в одной таблице, а значит выполнение запросов будет быстрым.
&lt;/p&gt;
&lt;p&gt;
Я бы использовал этот стиль только в том случае, если количество различных данных в иерархии довольно мало по отношению к количеству общих данных. Если это не так, то стоит рассмотреть другой вариант - table-per-subclass. 
&lt;/p&gt;

&lt;h2&gt;
Table-per-subclass
&lt;/h2&gt;
&lt;p&gt;
Каждый класс в иерархии отображается на отдельную таблицу. То есть, если бы в нашей иерархии были только классы: CancelledAlbumEvent, FailedAlbumEvent, CreatedAlbumEvent, а в базе данных - таблицы: AlbumEvent, AlbumEventCancel, AlbumEventFail, AlbumEventSubmit, то можно было бы смело заявить, что это стиль table-per-subclass.
&lt;/p&gt;

&lt;p&gt;
Mapping по сравнению с предыдущим сценарием отличается не сильно. В данном случае не нужно указывать discriminator, так как таблицы БД сами по себе являются теми отличительными значениями, которые нужны NHibernate&#39;у.

&lt;pre class = &quot;brush:csharp&quot;&gt;
public class AlbumEventMap : ClassMap&amp;lt;AlbumEventBase&amp;gt;
{
    public AlbumEventMap()
    {
        Table(&quot;AlbumEvent&quot;);
        
        Id(e =&gt; e.ID).Column(&quot;EventID&quot;).GeneratedBy.Native();
        Map(e =&gt; e.AlbumStatus).Not.Nullable().Length(32);
        Map(e =&gt; e.Timestamp).Not.Nullable();

        // no discriminator column
    }
}

// use SubclassMap once again
public class FailedAlbumEventMap : SubclassMap&amp;lt;FailedAlbumEvent&amp;gt;
{
    public FailedAlbumEventMap()
    {
        Table(&quot;AlbumEventFail&quot;);

        Map(e =&gt; e.Reason).Not.Nullable().Length(40);
        Map(e =&gt; e.Origin).Nullable().Length(40);
        Map(e =&gt; e.Details).AsUnicodeString().Not.Nullable().Length(1024);
    }
}

public class CreatedAlbumEventMap : SubclassMap&amp;lt;CreatedAlbumEvent&amp;gt;
{
    public CreatedAlbumEventMap()
    {
        Table(&quot;AlbumEventSubmit&quot;);
        Map(e =&gt; e.AlbumOrigin).Not.Nullable().Length(40);
    }
}
&lt;/pre&gt;

В классическом xml-based mapping используется конcтрукция &lt;a href=&quot;http://www.nhforge.org/doc/nh/en/index.html#inheritance-tablepersubclass&quot;&gt;joined-subclass&lt;/a&gt;.
&lt;/p&gt;

&lt;h3&gt;Table-per-subclass, using a discriminator&lt;/h3&gt;
&lt;p&gt;
Однако NHibernate не ограничивает нас, и дает возможность даже в случае table-per-subclass указать discriminator value. В нашем случае это выглядит довольно разумно, в качестве discriminator используем колонку EventType. Mapping несколько меняется, а в xml-based формате используется конструкция &lt;a href=&quot;http://www.nhforge.org/doc/nh/en/index.html#inheritance-tablepersubclass-discriminator&quot;&gt;subclass + discriminator-value + join&lt;/a&gt;.

&lt;pre class = &quot;brush:csharp&quot;&gt;
public class AlbumEventMap : ClassMap&amp;lt;AlbumEventBase&amp;gt;
{
    public AlbumEventMap()
    {
        Table(&quot;AlbumEvent&quot;);

        Id(e =&gt; e.ID).Column(&quot;EventID&quot;).GeneratedBy.Native();
        Map(e =&gt; e.AlbumStatus).Not.Nullable().Length(32);
        Map(e =&gt; e.Timestamp).Not.Nullable();

        DiscriminateSubClassesOnColumn(&quot;EventType&quot;);
    }
}

public class FailedAlbumEventMap : SubclassMap&amp;lt;FailedAlbumEvent&amp;gt;
{
    public FailedAlbumEventMap()
    {
        DiscriminatorValue(&quot;FAIL&quot;);
        Join(&quot;AlbumEventFail&quot;, j =&gt;
        {
            j.KeyColumn(&quot;EventID&quot;);
            j.Map(e =&gt; e.Reason).Not.Nullable().Length(40);
            j.Map(e =&gt; e.Origin).Nullable().Length(40);
            j.Map(e =&gt; e.Details).AsUnicodeString().Not.Nullable().Length(1024);
        });
    }
}
&lt;/pre&gt;

Можно сказать, что &quot;table-per-subclass, using discriminator&quot; - это мост, который позволяет связать два стиля: table-per-subclass и table-per-hierarchy. Так как table-per-hierarchy основан на использовании discriminator value, то table-per-subclass тоже должен поддерживать эту опцию.
&lt;/p&gt;

&lt;h2&gt;
2-in-1: table-per-subclass + table-per-hierarchy
&lt;/h2&gt;
&lt;p&gt;
Соединим два стиля, изложенных выше, вместе и применим их для решения нашей задачи. Простые классы событий будут отображены на таблицу album_event в стиле table-per-hierarchy, события же с дополнительными данными - на собственные таблицы в стиле table-per-subclass.
&lt;pre class = &quot;brush:csharp&quot;&gt;
// base class mapping
public class AlbumEventMap : ClassMap&amp;lt;AlbumEventBase&amp;gt;
{
    public AlbumEventMap()
    {
        Table(&quot;AlbumEvent&quot;);

        Id(e =&gt; e.ID).Column(&quot;EventID&quot;).GeneratedBy.Native();
        Map(e =&gt; e.AlbumStatus).Not.Nullable().Length(32);
        Map(e =&gt; e.Timestamp).Not.Nullable();

        DiscriminateSubClassesOnColumn(&quot;EventType&quot;)
          
        // NOTE: this cannot be done, we&#39;ve already used EventType as a discriminator column
        // NOTE: mapping the same column twice will cause update conflicts
        Map(e =&gt; e.Type).Column(&quot;EventType&quot;).Length(40);

    }
}

// simple event w/o additional data
// map it in a table-per-hierarchy style
public class PreparedAlbumEventMap : SubclassMap&amp;lt;PreparedAlbumEvent&amp;gt;
{
    public PreparedAlbumEventMap()
    {
        DiscriminatorValue(&quot;PREPARED&quot;);
    }
}

// event with custom data
// use table-per-subclass, using discriminator
public class CancelledAlbumEventMap : SubclassMap&amp;lt;CancelledAlbumEvent&amp;gt;
{
    public CancelledAlbumEventMap()
    {
        DiscriminatorValue(&quot;CANCEL&quot;);
        Join(&quot;AlbumEventCancel&quot;, j =&gt;
        {
            j.KeyColumn(&quot;EventID&quot;);
            j.Map(e =&gt; e.WhoCancels).Not.Nullable().Length(40);
            j.Map(e =&gt; e.Comment).AsUnicodeString().Nullable().Length(1024);
        });
    }
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;h3&gt;
Formula as a discriminator
&lt;/h3&gt;
&lt;p&gt;
Первая проблема, с которой мы сталкиваемся здесь, это то, что мы не можем отобразить свойство EventType на соответствующую колонку, так как она уже используется в качестве discriminator. Посмотрим как можно решить эту проблему.
&lt;/p&gt;

&lt;p&gt;
Корень проблемы лежит в том, что если дважды отобразить одну и ту же колонку таблицы БД (как свойство сущности и как discriminator value), то могут возникнуть конфликты обновления, и NHibernate не сможет корректно разрешить, что именно использовать в качестве значения для этой колонки. Одно из решений - пометить discriminator как read-only.
&lt;pre class = &quot;brush:xml&quot;&gt;
...
&amp;lt;property name = &quot;Type&quot; column = &quot;EventType&quot; /&amp;gt;
&amp;lt;discriminator column = &quot;EventType&quot; insert = &quot;false&quot; /&amp;gt;
...
&lt;/pre&gt;

&lt;/p&gt;
В отличии от xml-based syntax, fluent API не позволяет установить значение &quot;insert = false&quot;. В качестве альтернативного варианта, можно указать &lt;a href=&quot;http://nhforge.org/blogs/nhibernate/archive/2010/05/15/not-so-hidden-gems-of-nhibernate-formula-discriminators.aspx&quot;&gt;формулу для discriminator value, а не фиксированную колонку&lt;/a&gt;. 
&lt;pre class = &quot;brush:csharp&quot;&gt;
Map(e =&gt; e.Type).Column(&quot;EventType&quot;).Length(40);
DiscriminateSubClassesOnColumn(&quot;&quot;).Formula(&quot;EventType&quot;);
&lt;/pre&gt;
&lt;p&gt;

&lt;p&gt;
В качестве формулы допускается любой валидный sql.
&lt;pre class = &quot;brush:csharp&quot;&gt;
Map(e =&gt; e.Type).Column(&quot;EventType&quot;).Length(40);
DiscriminateSubClassesOnColumn(&quot;&quot;)
    .Formula(&quot;case when LEFT(EventType, 8) = &#39;Contract&#39; then &#39;Contract&#39; else EventType end&quot;);
&lt;/pre&gt;
&lt;/p&gt;

&lt;h3&gt;
Mapping against interface
&lt;/h3&gt;
&lt;p&gt;
При наличии иерархии AlbumEventBase : IAlbumEvent, возникает резонный вопрос почему таблица &quot;album_event&quot; отображается на базовый класс AlbumEventBase вместо интерфейса IAlbumEvent. Ведь другие классы ссылаются на интерфейс, а не на базовый класс. Базовый класс - детали реализации, исключительно для удобства, его могло бы и не быть. Чтож, попробуем переписать меппинг для интерфейса вместо базового класса.
&lt;pre class = &quot;brush:csharp&quot;&gt;
public interface IAlbumEvent
{
    int ID { get; }
    EventType Type { get; }
    AlbumStatus Status { get; }
    Album Album { get; }
    DateTime Timestamp { get; }

    void Raise(Album album);
}

// change AlbumEventBase to IAlbumEvent
public class AlbumEventMap : ClassMap&amp;lt;IAlbumEvent&amp;gt;
//public class AlbumEventMap : ClassMap&amp;lt;AlbumEventBase&amp;gt;
{
    // the rest of the mapping is the same
}
&lt;/pre&gt;

NHibernate скажет:
&lt;blockquote&gt;
NHibernate.PropertyNotFoundException: Could not find a setter for property &#39;ID&#39; in class &#39;Events.IAlbumEvent&#39;
&lt;/blockquote&gt;
&lt;/p&gt;

&lt;p&gt;
Если модель допускает поднятие setter&#39;ов на уровне интерфейса, то тогда все хоккей. Если же, нет, то тогда можно попытаться указать использовать backing field в качестве access strategy, чтобы NHibernate искал field, а не property setter.
&lt;pre class = &quot;brush:csharp&quot;&gt;
Id(e =&gt; e.ID).Column(&quot;EventID&quot;)
    .GeneratedBy.Native()
    .Access.ReadOnlyPropertyThroughLowerCaseField(Prefix.Underscore);
&lt;/pre&gt;
И теперь:
&lt;blockquote&gt;
NHibernate.PropertyNotFoundException:  NHibernate.PropertyNotFoundException: Could not find field &#39;_id&#39; in class &#39;Events.IAlbumEvent&#39;
&lt;/blockquote&gt;
Несмотря на то, что NHibernate больше не ищет property setter, а ищет backing field, однако ищет он его по-прежнему в интерфейсе, а не в базовом классе. &lt;a href=&quot;http://stackoverflow.com/questions/845536/programming-to-interfaces-while-mapping-with-fluent-nhibernate&quot;&gt;Здесь обсуждается подобная проблема&lt;/a&gt;. Остается либо поднять property setter на уровень интерфейса, либо описывать меппинг по отношению к базовому классу. &lt;strong&gt;Однако это привносит другую проблему: можно ли будет использовать IAlbumEvent, если для него не описан меппинг?&lt;/strong&gt; Ответ на этот вопрос: &lt;strong&gt;implicit polymorphism&lt;/strong&gt;.
&lt;/p&gt;

&lt;h2&gt;
Implicit polymorphism
&lt;/h2&gt;
&lt;p&gt;
В качестве описания что такое implicit polymorphism, можно процитировать &lt;a href=&quot;http://v4forums.wordpress.com/2008/12/27/implicit-explicit-polymorphism-in-hibernate/&quot;&gt;эту статью&lt;/a&gt;.

&lt;blockquote&gt;
Implicit polymorphism means that instances of the class will be returned by a query that names any superclass or implemented interface or the class and that instances of any subclass of the class will be returned by a query that names the class itself. For most purposes the default, polymorphism=”implicit”, is appropriate.
&lt;/blockquote&gt;

А теперь попроще. Если запрос описан в терминах базового класса, или интерфейса, то помимо экземпляров этого класса запрос вернет экземпляры всех его подклассов, для которых описан меппинг. 
&lt;/p&gt;

&lt;p&gt;
Для нашего примера это значит, что нет необходимости явно описывать меппинг по отношению к интерфейсу IAlbumEvent, более того, используя интерфейс к примеру в ассоциации ISet&amp;lt;IAlbumEvent&amp;gt;, мы делаем такую коллекцию полиморфной, то есть результатом загрузки такой коллекции будет вся наша иерархия классов событий. Вот как будет выглядеть меппинг класса Album.
&lt;pre class = &quot;brush:csharp&quot;&gt;
public class Album
{
    // associations are expressed in terms of IAlbumEvent, not AlbumEventBase
    public virtual IEnumerable&amp;lt;IAlbumEvent&amp;gt; Events { get; }
    public virtual IAlbumEvent LastEvent { get; private set; }
}

public class AlbumMap : ClassMap&amp;lt;Album&amp;gt;
{
    // note, explicit specification of AlbumEventBase as a type-parameter
    o.HasMany&amp;lt;AlbumEventBase&amp;gt;(o =&amp;gt; o.Events).AsSet()
        .Inverse().Cascade.AllDeleteOrphan()
        .KeyColumn(&quot;AlbumID&quot;)
        .Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
    
    o.References&amp;lt;AlbumEventBase&amp;gt;(o =&amp;gt; o.LastEvent)
        .Nullable().Fetch.Join()
        .ForeignKey(&quot;FK_Album_Album_event_LastEventID&quot;);
}
&lt;/pre&gt;
И последний штрих, для того, чтобы все это работало. Нужно явно указать AlbumEventBase в качестве типа-параметра в конструкции o.HasMany&amp;lt;AlbumEventBase&amp;gt;(o =&gt; o.Events), иначе будет такая ошибка.

&lt;blockquote&gt;
NHibernate.MappingException: Association references unmapped class: Events.IAlbumEvent.
&lt;/blockquote&gt;
&lt;/p&gt;

&lt;h3&gt;
How to get everything from DB?
&lt;/h3&gt;
&lt;p&gt;
Один интересный побочный эффект применения &quot;implicit polymorphism&quot;. Каков будет результат выполнения этого запроса:
&lt;pre class = &quot;brush:csharp&quot;&gt;
Session.Query&amp;lt;object&amp;gt;().ToList();
&lt;/pre&gt;
Да-да, вот так просто можно вытянуть всю базу данных. Хотя врядли это Вам понадобится. :)
&lt;/p&gt;

&lt;h2&gt;
Result sql query
&lt;/h2&gt;
&lt;p&gt;
Ну и сейчас наверное самое интересное, особенно для тех, кого беспокоит как будет выглядеть конечный sql запрос и его производительность. А это на самом деле не может не беспокоить, потому что если на проекте есть DBA, то он врядли будет рад километровым запросам с 32-мя уровнями вложенных select&#39;ов, и рано или поздно он к Вам придет.
&lt;/p&gt;

&lt;p&gt;
Итак, сделаем запрос на получение всех заказов вместе с его последним событием.
&lt;pre class=&quot;brush:csharp&quot;&gt;
Session.Query&amp;lt;Album&amp;gt;().ToList();
&lt;/pre&gt;
И вот, что мы имеем на выходе.
&lt;pre class=&quot;brush:sql&quot;&gt;
SELECT
        album0_.AlbumID as AlbumID9_3_,
        album0_.AlbumStatusCode as AlbumSta4_9_3_,
        -- and more album related columns

        client1_.ClientID as ClientID0_0_,
        -- and more client related columns 

        -- all event related columns
        albumevent2_.EventID as EventID3_1_,
        albumevent2_.AlbumStatus as AlbumSta2_3_1_,
        albumevent2_.Timestamp as Timestamp3_1_,
        albumevent2_.EventType as EventType3_1_,
        albumevent2_.AlbumID as AlbumID3_1_,
        albumevent2_1_.WhoCancels as Actor4_1_,
        albumevent2_1_.Comment as Comment4_1_,
        albumevent2_2_.Reason as Reason5_1_,
        albumevent2_2_.Origin as Origin5_1_,
        albumevent2_2_.Details as Details5_1_,
        albumevent2_4_.AlbumOrigin as AlbumOri2_7_1_,
        case 
            when LEFT(albumevent2_.EventType, 8) = &#39;Contract&#39; 
            then &#39;Contract&#39; 
            else albumevent2_.EventType 
        end as clazz_1_
    FROM [Album] album0_ inner join Client client1_ 
        on album0_.ClientID=client1_.ClientID 
    left outer join
        AlbumEvent albumevent2_ 
            on album0_.LastEventID=albumevent2_.EventID 
    left outer join
        AlbumEventCancel albumevent2_1_ 
            on albumevent2_.EventID=albumevent2_1_.EventID 
    left outer join
        AlbumEventFail albumevent2_2_ 
            on albumevent2_.EventID=albumevent2_2_.EventID 
    left outer join
        AlbumEventCreated albumevent2_4_ 
            on albumevent2_.EventID=albumevent2_4_.EventID 
    WHERE
        album0_.AlbumID=@p0;
&lt;/pre&gt;
И здесь NHibernate потрясает нас чистотой запроса. На самом деле запрос близок к написанному человеком, и отличается разве что именованием.
&lt;/p&gt;

&lt;h2&gt;
Лирика
&lt;/h2&gt;
&lt;p&gt;
И напоследок, небольшое лирическое отступление на тему возможностей NHibernate.
&lt;/p&gt;
&lt;p&gt;
NHibernate - мощная технология. Но чтобы овладеть ёё возможностями и заставить её работать, нужны труд и терпение, нужно осознать идею и вникнуть в детали. Поверхностных знаний недостаточно, и книжка &quot;NHibernate за 12 часов&quot; - тут не поможет. Да, высокий порог вхождения, но в этом в то же время и прелесть. Тем больше вероятность, что код будет писаться более опытными разработчиками, и тем меньше вероятность появления косяков.
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;NHibernate - как хороший автомобиль, на плохом бензине даже не заведется!&lt;/strong&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/280885292267785021/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/05/nhibernate-mapping-class-hierarchy.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/280885292267785021'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/280885292267785021'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/05/nhibernate-mapping-class-hierarchy.html' title='NHibernate: mapping class hierarchy'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFtzr4pI9AIUNDj8WDYECw6B33P0pja0y3MlDt4Qvmi6GJN2D756sG-ceS1oVwxjomqsiuXFLYqRC-8w4rRDjAWSCVr9KCLzDkcjT6evFSBlsn6BJXEMdYoe0_k51-qmBrNGZck0DUvNw/s72-c/album_event_hierarchy.png" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-5840622605016498577</id><published>2011-05-06T03:23:00.001+03:00</published><updated>2011-05-06T03:23:59.916+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DI/IoC"/><title type='text'>DI/IoC container lifestyles</title><content type='html'>&lt;p&gt;
В прошлой статье были описаны top-level понятия, охватывающие &lt;a href=&quot;http://samoshkin.blogspot.com/2011/02/blog-post.html&quot;&gt;проблемы управления зависимостями между компонентами в приложении&lt;/a&gt;. Были показаны подходы к их решению, в частности при помощи Dependency Injection/Inversion of control контейнеров.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;В этой статье будет рассмотрено понятие lifestyle в контексте DI/IoC&lt;/strong&gt;, описаны существующие типы lifestyle политик на примере &lt;a href=&quot;http://docs.castleproject.org/Windsor.MainPage.ashx&quot;&gt;Castle.Windsor&lt;/a&gt; и &lt;a href=&quot;http://code.google.com/p/autofac/&quot;&gt;Autofac&lt;/a&gt;, а также будет показана реализация собственной lifestyle политики для Castle.Windsor.
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#what-is-lifestyle&quot;&gt;What is lifestyle?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#lifestyle-types&quot;&gt;Lifestyle types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#component-tracking-and-release&quot;&gt;Component tracking and release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#lifestyle-comparison&quot;&gt;Lifestyle types comparison in Castle.Windsor and Autofac&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#per-lifetime-scope&quot;&gt;&quot;Per lifetime scope&quot; lifestyle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#per-lifetime-scope-castle-windsor&quot;&gt;Implementation of &quot;per lifetime scope&quot; lifestyle for Castle.Windsor&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;

&lt;h2&gt;&lt;a name=&quot;lifestyle-notion&quot;&gt;What is lifestyle?&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;
В рамках разрешения зависимостей контейнер компонент должен решать задачи управления жизненным циклом компонент (создание, использование и освобождение компонента). При регистрации компонента в контейнере указывается lifestyle политика.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
container.Register(Component
    .For&amp;lt;ISimpleService&gt;()
    .ImplementedBy&amp;lt;SimpleServiceImpl&amp;gt;()
    .LifeStyle.Singleton;

container.Register(Component
    .For&amp;lt;ICompositeService&amp;gt;()
    .ImplementedBy&amp;lt;CompositeServiceImpl&amp;gt;()
    .LifeStyle.Transient;
&lt;/pre&gt;

&lt;p&gt;
&lt;strong&gt;Lifestyle политика определяет границы разделения, повторного использования компонента и времени жизни компонента.&lt;/strong&gt; То есть указывая lifestyle, мы даем возможность контейнеру получить ответы на следущие вопросы:
&lt;ul&gt;
&lt;li&gt;
Каким экземпляром компонента разрешать зависимость?
&lt;/li&gt;
&lt;li&gt;
Нужно ли создавать новый компонент всякий раз, чтобы разрешить зависимость, либо использовать для этого ранее созданные экземпляры?
&lt;/li&gt; 
&lt;li&gt;
Каковы границы повторного использования ранее созданных компонент?
&lt;/li&gt; 
&lt;li&gt;
Когда ранее созданные компоненты становятся не пригодными для разрешения новых зависимостей?
&lt;/li&gt;
&lt;li&gt;
Должен или компонент освобождать (release) созданные ранее компоненты? В какой момент времени нужно это делать?
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;
В контексте обсуждения lifestyle&#39;ов неважно в какой форме контейнер разрешает зависимость. Различие лишь в том, кто иницирует процесс разрешения и инвертирован ли этот процесс.
&lt;ul&gt;
&lt;li&gt;
direct request, приложение запросило у контейнера предоставить ему зависимость.
&lt;/li&gt;
&lt;li&gt;
dependency injection, контейнер сам предоставил зависимость компоненту.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;



&lt;h2&gt;&lt;a name=&quot;lifestyle-types&quot;&gt;Lifestyle types&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;
Опишем наиболее общие типы lifestyle&#39;ов, которые можно встретить во разных реализациях DI/IoC контейнеров.
&lt;/p&gt;

&lt;h3&gt;Transient&lt;/h3&gt;
&lt;p&gt;
Контейнер создает новый уникальный экземпляр компонента каждый раз, когда нужно разрешить зависимость. Созданные экземпляры не используются для разрешения последующих зависимостей. Transient политика по своей природе означает, что зависимость не может быть shared, то есть не может использоваться несколькими компонентами совместно.
&lt;pre class = &quot;brush:csharp&quot;&gt;
[Test]
public void Given_transient_dependency_should_resolve_new_instance_of_component_for_any_request()
{
    var container = new WindsorContainer();
    container.Register(Component
        .For&amp;lt;ISimpleService&amp;gt;()
        .ImplementedBy&amp;lt;SimpleServiceImpl&amp;gt;()
        .LifeStyle.Transient);
            
    var instance1 = container.Resolve&amp;lt;ISimpleService&amp;gt;();
    var instance2 = container.Resolve&amp;lt;ISimpleService&amp;gt;();

    // verify instances are different
    Assert.IsFalse(ReferenceEquals(instance1, instance2));
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
Transient политика не определяет границу времени жизни компонента. Освобождение компонента зависит от режима отслеживания контейнером созданных компонентов. Если контейнер отслеживает и хранит ссылки на созданные им компоненты, то они освобождаются при уничтожении контейнера. В противном случае, приложение явно управляет временем жизни нужной ему зависимости, вызывая Dispose() в нужный момент времени.
&lt;/p&gt;

&lt;p&gt;
Важно отметить, что реализация того, как и когда освобождаются transient компоненты, может варьироваться для разных реализаций DI/IoC контейнеров. К примеру, Autofac отслеживает все созданные transient компоненты, которые являются IDisposable, и освобождает их только при уничтожении самого контейнера компонент. 
Это значит, что если у вас в приложении есть контейнер компонент, время жизни которого совпадает с временем жизни приложения (к примеру, Windows service, при старте которого создается контейнер, и уничтожается только при останове сервиса), и вы запрашиваете transient зависимости, которые являются IDisposable, то такие зависимости останутся в памяти до завершения работы приложения, то есть возможны memory leaks. У &lt;a href=&quot;http://nblumhardt.com/about/&quot;&gt;Nicholas Blumhardt&lt;/a&gt;, разработчика Autofac, есть отличный пост на тему &lt;a href=&quot;http://nblumhardt.com/2011/01/an-autofac-lifetime-primer/&quot;&gt;управления временем жизни компонент в Autofac&lt;/a&gt;, в частности он показывает, что нужно делать, чтобы избегать таких ситуаций.
&lt;/p&gt;

&lt;h3&gt;Singleton&lt;/h3&gt;
&lt;p&gt;
Существует только один экземпляр компонента на все время жизни контейнера. Контейнер разрешает любую зависимость при помощи этого единственного экземпляра. Singleton политика имеет детерминированную границу времени жизни компонента - зависимости освобождаются при уничтожении контейнера. Singleton компоненты по своей природе являются разделяемыми, поэтому контейнер отслеживает созданные им singleton компоненты, как для того, чтобы использовать их в последущих запросах, так и для того, чтобы уничтожить их впоследствие.

&lt;pre class=&quot;brush: csharp&quot;&gt;
[Test]
public void Given_singleton_dependency_should_use_same_component_instance_throughout_container_lifetime()
{
    //setup component and dependent component
    var container = new WindsorContainer();
    container.Register(Component
        .For&amp;lt;ISimpleService&amp;gt;()
        .ImplementedBy&amp;lt;SimpleServiceImpl&amp;gt;()
        .LifeStyle.Singleton);
    container.Register(Component
        .For&amp;lt;ICompositeService&amp;gt;()
        .ImplementedBy&amp;lt;CompositeService&amp;gt;()
        .LifeStyle.Singleton);
            
    var simpleInstance1 = container.Resolve&amp;lt;ISimpleService&amp;gt;();
    var simpleInstance2 = container.Resolve&amp;lt;ISimpleService&amp;gt;();
    var compositeInstance = container.Resolve&amp;lt;ICompositeService&amp;gt;();

    // verify container returns same instances when directly asked
    Assert.AreEqual(simpleInstance1, simpleInstance2);
    // verify container uses the very same instance with dependency injection
    Assert.AreEqual(simpleInstance1, compositeInstance.SimpleService);
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;h3&gt;Per thread&lt;/h3&gt;
&lt;p&gt;
Этот lifestyle похож на singleton lifestyle, c тем лишь отличием, что контекст повторного использования компонент ограничен рамками отдельного потока, а не рамками жизни контейнера. Для разных потоков будут созданы различные экземпляры. Зависимость также является разделяемой.

&lt;pre class=&quot;brush: csharp&quot;&gt;
[Test]
public void Given_per_thread_dependency_should_reuse_same_component_within_same_thread()
{
    var container = new WindsorContainer();
    container.Register(Component
        .For&amp;lt;ISimpleService&amp;gt;()
        .ImplementedBy&amp;lt;SimpleServiceImpl&amp;gt;()
        .LifeStyle.PerThread);

    var instanceFromThreadA = GetInstanceResolvedInThreadA(container);
    var instanceFromThreadB = GetInstanceResolvedInThreadB(container);
    // verify instances resolved in different threads are different
    Assert.AreNotEqual(instanceFromThreadA, instanceFromThreadB);

    var instanceFromSameThread1 = container.Resolve&amp;lt;ISimpleService&amp;gt;();
    var instanceFromSameThread2 = container.Resolve&amp;lt;ISimpleService&amp;gt;();
    // verify instances resolved in same threads are same
    Assert.AreEqual(instanceFromSameThread1, instanceFromSameThread2);
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;h3&gt;Per Web Request&lt;/h3&gt;
&lt;p&gt;
Аналогичен per thread, c тем лишь отличием, что границами времени жизни и разделения компонент является отдельный web request (имеется ввиду ASP.NET Http Request).
&lt;/p&gt;

&lt;h2&gt;&lt;a name=&quot;component-tracking-and-release&quot;&gt;Component tracking and release&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;
Помимо указания lifestyle политики разные реализации контейнеров так или иначе позволяют повлиять на то, входит ли в обязанности контейнера отслеживание созданных экземпляров и их освобождение.
&lt;/p&gt;

&lt;p&gt;
Autofac позволяет указать контейнеру не освобождать IDisposable компоненты, подразумевая, что это становится обязанностью приложения. Хотя контейнер по-прежнему продолжает отслеживать разделяемые компоненты для их дальнейшего использования (singleton, per thread, per web request). Однако контейнеру более не надо отслеживать IDisposable transient компоненты.
&lt;/p&gt;

&lt;pre class = &quot;brush: csharp&quot;&gt;
[Test]
public void Autofac_gives_ability_to_configure_container_not_to_release_component()
{
    var containerBuilder = new ContainerBuilder();
  
    // let application to manage component disposal
    containerBuilder
        .RegisterType&amp;lt;SimpleServiceImpl&amp;gt;()
        .As&amp;lt;ISimpleService&amp;gt;()
        .ExternallyOwned();
}
&lt;/pre&gt;

&lt;p&gt;
Castle.Windsor предоставляет похожую функциональность при помощи понятия release policy. Однако по сравнению с Autofac в Castle.Windsor эта политика не может быть применена к отдельному компоненту, а лишь ко всему контейнеру в целом.
&lt;/p&gt;

&lt;pre class = &quot;brush: csharp&quot;&gt;
[Test]
public void Castle_windsor_gives_ability_to_configure_container_not_to_track_components()
{
    var container = new WindsorContainer();
   
    // do not track and dispose transient components
    // let application to manage component disposal
    container.Kernel.ReleasePolicy = new NoTrackingReleasePolicy();
}
&lt;/pre&gt;


&lt;h2&gt;&lt;a name=&quot;lifestyle-comparison&quot;&gt;Lifestyle types comparison in Castle.Windsor and Autofac&lt;/a&gt;&lt;/h2&gt;
Сравним lifestyle политики, которые доступны в Castle.Windsor и Autofac.

&lt;table&gt;
&lt;tr class = &quot;caption&quot;&gt;
&lt;td&gt;Lifestyle policy&lt;/td&gt;
&lt;td&gt;Castle.Windsor&lt;/td&gt;
&lt;td&gt;Autofac&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transient&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Singleton&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per lifetime scope&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per Thread&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per Web Request&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pooled&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom lifestyle policy&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;
Набор out-of-the-box lifestyle политик в Castle.Windsor несомненно больше, чем в Autofac, также в Castle.Windsor есть возможность создать собственную реализацию lifestyle, реализовав ILifestyleManager интерфейс. Однако большинство функциональных возможностей out-of-the-box lifestyle политик, доступных в Castle.Windsor, с легкостью можно получить в Autofac при помощи одной лишь &quot;per lifetime scope&quot; политики. 
&lt;/p&gt;

&lt;p&gt;
Например, per thread поведение можно получить следующим образом.
&lt;/p&gt;

&lt;pre class=&quot;brush: csharp&quot;&gt;
void ThreadStart()
{
   using (var threadLifetime = container.BeginLifetimeScope())
   {
      var thisThreadsInstance = threadLifetime.Resolve&amp;lt;MyThreadScopedComponent&amp;gt;();
   }
} 
&lt;/pre&gt;

&lt;p&gt;
Аналогично можно получить поведение per web request, просто открыв lifetime scope в начала web request&#39;а (HttpApplication.BeginRequest), и закрыв при завершении (HttpApplication.EndRequest). 
&lt;/p&gt;

&lt;p&gt;
Далее более детально посмотрим на &quot;per lifetime scope&quot; lifestyle политику.
&lt;/p&gt;


&lt;h2&gt;&lt;a name=&quot;per-lifetime-scope&quot;&gt;&quot;Per lifetime scope&quot; lifestyle&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;
Ранее описанные политики имеют одну общую черту: границы жизненного цикла и разделения компонент предопределены и являются внешними по отношению к приложению факторами. Per thread ограничен рамками потока, per web request - рамками http запроса, singleton - рамками контейнера. Было бы неплохо, если бы &lt;strong&gt;приложение могло cамо явно задавать рамки повторного использования компонент&lt;/strong&gt;. И это как раз то, чем является &quot;Per lifetime scope&quot;. Короче говоря, &lt;strong&gt;&quot;per lifetime scope&quot; политика позволяет явно обозначить unit of work в приложении.&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
К примеру, если приложение оперирует обработкой заказов, то имеет смысл определить такие unit of work, как client processing, order processing.
&lt;/p&gt;

&lt;pre class = &quot;brush: csharp&quot;&gt;
[Test]
public void Should_be_able_to_create_two_nested_lifetime_scopes()
{
    var containerBuilder = new ContainerBuilder();
    containerBuilder
        .RegisterType&amp;lt;SimpleServiceImpl&amp;gt;()
        .As&amp;lt;ISimpleService&gt;()
        .InstancePerMatchingLifetimeScope(&quot;client&quot;);
    containerBuilder
        .RegisterType&amp;lt;CompositeService&amp;gt;()
        .As&amp;lt;ICompositeService&amp;gt;()
        .InstancePerMatchingLifetimeScope(&quot;order&quot;);

    using (var container = containerBuilder.Build())
    {
        Client client= GetClient();
        // begin single client processing and open lifetime scope
        using(var clientProcessingScope = container.BeginLifetimeScope(&quot;client&quot;))
        {
            // create and use client-specific service
            var simpleService = clientProcessingScope.Resolve&amp;lt;ISimpleService&amp;gt;();
            //process orders of the client
            foreach (var order in client.Orders)
            {
                //begin single order processing, and open lifetime scope
                using (var orderProcessingScope = container.BeginLifetimeScope(&quot;order&quot;))
                {
                    // create order-specific service, which uses service from client processing scope
                    var compositeService = orderProcessingScope.Resolve&amp;lt;ICompositeService&amp;gt;();
                    var clientSpecificServiceReferenced = compositeService.SimpleService;

                    // destroy all order-specific service at the end of scope
                }
            }

            // destroy all client-specific service at the end of scope
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;
Отличительная черта и главная ценность &quot;per lifetime scope&quot; lifestyle по сравнению с другими политиками, это то, что &lt;strong&gt;рамками повторного использования компонент (lifetime scope) управляет приложение&lt;/strong&gt;. Это дает дополнительную гибкость в выборе нужного lifetime scope, и не быть привязанными к готовым lifestyle&#39;ам (per thread, per web request).
&lt;/p&gt;

&lt;h2&gt;&lt;a name=&quot;per-lifetime-scope-castle-windsor&quot;&gt;Implementation of &quot;per lifetime scope&quot; lifestyle for Castle.Windsor&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;
Попытаемся восполнить отсутствие &quot;per lifetime scope&quot; lifestyle политики в Castle.Windsor и реализуем ее, используя ILifestyleManager extension point. В качеcтве вдохновения используем уже готовый lifestyle из Autofac.
&lt;/p&gt;

&lt;p&gt;
Полная версия проекта (.net4, vs 2010) и исходный код этого решения выложен на &lt;a href=&quot;https://github.com/samoshkin/ContextualLifetimeScope&quot;&gt;github&lt;/a&gt;.
&lt;/p&gt;

&lt;h3&gt;
Поведение
&lt;/h3&gt;

&lt;p&gt;
Основные use case, которые хотим покрыть таковы.

&lt;ol&gt;
&lt;li&gt;
Запросить компонент дважды в рамках открытого lifetime scope, и убедиться, что экземпляры идентичны.

&lt;pre class = &quot;brush: csharp&quot;&gt;
[Test]
public void When_resolving_same_service_in_same_scope_several_times_should_return_the_same_component()
{
    var container = new WindsorContainer();
    container.Register(Component
        .For&amp;lt;ISimpleService&amp;gt;()
        .ImplementedBy&amp;lt;SimpleServiceImpl&amp;gt;()
        .LifeStyle.Custom&amp;lt;PerLifetimeScopeLifestyle&amp;lt;string&amp;gt;&amp;gt;());

    using (new LifetimeScope&amp;lt;string&amp;gt;())
    {
        var service1 = container.Resolve&amp;lt;ISimpleService&amp;gt;();
        var service2 = container.Resolve&amp;lt;ISimpleService&amp;gt;();
        Assert.AreEqual(service1, service2);
    }
}
&lt;/pre&gt;
&lt;/li&gt;

&lt;li&gt;
Запросить один и тот же компонент в разных lifetime scope&#39;ах, и убедиться что экземпляры разные.

&lt;pre class = &quot;brush: csharp&quot;&gt;
[Test]
public void When_resolving_same_service_in_different_scope_several_times_should_return_different_components()
{
    var container = new WindsorContainer();
    container.Register(Component
        .For&amp;lt;ISimpleService&amp;gt;()
        .ImplementedBy&amp;lt;SimpleServiceImpl&amp;gt;()
        .LifeStyle.Custom&amp;lt;PerLifetimeScopeLifestyle&amp;lt;string&amp;gt;&amp;gt;());

    ISimpleService service1, service2;
    using (new LifetimeScope&amp;lt;string&amp;gt;())
    {
        service1 = container.Resolve&amp;lt;ISimpleService&amp;gt;();
    }
    using (new LifetimeScope&amp;lt;string&amp;gt;())
    {
        service2 = container.Resolve&amp;lt;ISimpleService&amp;gt;();
    }

    Assert.AreNotEqual(service1, service2);
}
&lt;/pre&gt;
&lt;/li&gt;

&lt;li&gt;
Во вложенном lifetime scope&#39;е разрешить компонент, который имеет зависимость на компонент, который доступен во внешнем lifetime scope.

&lt;pre class = &quot;brush: csharp&quot;&gt;
[Test]
public void If_service_in_scope_has_dependecy_to_service_which_is_in_outer_scope_container_should_wire_it()
{
    var container = new WindsorContainer();
    container.Register(Component
        .For&amp;lt;ISimpleService&amp;gt;()
        .ImplementedBy&amp;lt;SimpleServiceImpl&amp;gt;()
        .LifeStyle.Custom&amp;lt;PerLifetimeScopeLifestyle&amp;lt;int&amp;gt;&amp;gt;());
    container.Register(Component
        .For&amp;lt;ICompositeService&amp;gt;()
        .ImplementedBy&amp;lt;CompositeServiceImpl&amp;gt;()
        .LifeStyle.Custom&amp;lt;PerLifetimeScopeLifestyle&amp;lt;string&amp;gt;&amp;gt;());

    using (new LifetimeScope&amp;lt;int&amp;gt;())
    {
        var simpleService = container.Resolve&amp;lt;ISimpleService&amp;gt;();
        using (new LifetimeScope&amp;lt;string&amp;gt;())
        {
            var compositeService = container.Resolve&amp;lt;ICompositeService&amp;gt;();
            Assert.AreEqual(simpleService, compositeService.SimpleService);
        }
    }
}
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;h3&gt;Объявление границ lifetime scope (unit of work)&lt;/h3&gt;

&lt;p&gt;
Итак, начнем с самого простого. Приложению нужно каким-то образом обозначить начало и конец lifetime scope, то есть обозначить границы unit of work.
&lt;/p&gt;

&lt;pre class=&quot;brush: csharp&quot;&gt;
public class LifetimeScope&amp;lt;TContext&amp;gt; : IDisposable
{
    public LifetimeScope()
    {
        LifetimeScopeStore.Get&amp;gt;TContext&amp;gt;().OpenScope();
    }

    public void Dispose()
    {
        LifetimeScopeStore.Get&amp;lt;TContext&amp;gt;().CloseScope();
    }
}
&lt;/pre&gt;

&lt;h3&gt;Механизм хранения созданных компонент&lt;/h3&gt;
&lt;p&gt;
Далее, необходимо определить, где будут хранится экземпляры компонент с &quot;per lifetime scope&quot; lifestyle, созданные контейнером. Требования к механизму хранения следующие.

&lt;ol&gt;
&lt;li&gt;
Нужно обеспечить, чтобы компоненты, созданные однажды, были доступны в любом месте ниже по call hierarchy. Допустим, имеется цепочка вызовов методов.
&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2mkIBpko_bBs_iTM1Ql8JXfxNGP8WnDq6PjSN0uxRXPSx7_29tMc59tuKGZeAVhTq3aDtixAVCDq5EyTA3FM3Uw0Wyf6fLqmpi3O4XHK0ph2O2-7jEX0BUC17Dtgj5Nx-StmE1-oQvYI/s800/available-deeper-in-call-hierarchy.png&quot; alt=&quot;available-deeper-in-call-hierarchy&quot; /&gt;
&lt;/li&gt;

&lt;li&gt;
Нужно обеспечить, чтобы компоненты были доступны в дочерних потоках.

&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNbmNhitsvOdxxUaHHlC4GsBLkUTNxxZnMBmYbGsGwPN7kCfnCLnMlJuxmHqnC2lVYZ6Djrq9jG1m1WgOey5uhyphenhyphenW31AEWxkqoxKE7Rla3BslGKR1qgoFmuycOTqamWpFMMESm7qO0TeCA/s800/available-in-child-thread.png&quot; alt=&quot;available-in-child-thread&quot; /&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;
Для этого нам подойдет такое хранилище как CallContext, &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext.aspx&quot;&gt;подробнее здесь&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
При использовании CallContext, можно столкнутся с проблемой, что данные, сохраненные в одном потоке, становятся недоступными в дочернем, то есть не удовлетворяется требование №2. Для того чтобы побороть эту проблему, нужно пометить класс, экземпляры которого будут хранится в Call context, маркерным интерфейсом &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.ilogicalthreadaffinative.aspx&quot;&gt;ILogicalThreadAffinative&lt;/a&gt;.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
private class LogicalThreadAffinativeDictionary : Dictionary&amp;lt;object, object&amp;gt;, ILogicalThreadAffinative
{ }
&lt;/pre&gt;

&lt;h3&gt;
Реализация ILifestyleManager
&lt;/h3&gt;

&lt;p&gt;
ILifestyleManager - extension point Castle Windsor&#39;а для создания собственных lifestyle политик. Наша реализация PerLifetimeScopeLifestyle при разрешении зависимости будет создавать новый экземпляр зависимости и сохранять его в слоте данных CallContext. Если компонент ранее был создан, то будет использовать его повторно. В случае явного запроса на освобождение компонента, будет удалять его из слота данных CallContext&#39;а.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
public class PerLifetimeScopeLifestyle&amp;gt;TContext&amp;gt; : AbstractLifestyleManager
{
    public class ComponentInstance : IDisposable { ... }
        
    public override void Dispose()
    { }

    public override bool Release(object instance)
    {
        LifetimeScopeStore.Get&amp;lt;TContext&amp;gt;().TryRemove(this);
        return base.Release(instance);
    } 

    private bool ReleaseOnScopeExiting(object instance)
    {
        return base.Release(instance);
    }

    public override object Resolve(CreationContext context)
    {
        return LifetimeScopeStore.Get&amp;lt;TContext&amp;gt;()
            .GetOrAdd(this, lifestyleManager =&gt; new ComponentInstance(lifestyleManager, base.Resolve(context)))
            .Instance;
    }
}
&lt;/pre&gt;

&lt;h3&gt;
Ограничения и недостатки решения
&lt;/h3&gt;

&lt;p&gt;
Недостатком является то, что Lifetime scope обязательно должен быть помечен неким тегом. В данном реализации тегом выступает тип TContext, используемый при создании класса LifetimeScope&lt;TContext&gt;. 
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
// this is supported
// lifetime scope is tagged with Client
using (new LifetimeScope&amp;lt;Client&amp;gt;())
{...}

// this is not supported
using (new LifetimeScope())
{...}
&lt;/pre&gt;

&lt;h3&gt;
Source code hosted on github
&lt;/h3&gt;
&lt;p&gt;
Полная версия проекта (.net4, vs 2010) и исходный код этого решения выложен на &lt;a href=&quot;https://github.com/samoshkin/ContextualLifetimeScope&quot;&gt;github&lt;/a&gt;.

&lt;pre class=&quot;brush:bash&quot;&gt;
 $ git clone git://github.com/samoshkin/ContextualLifetimeScope.git
&lt;/pre&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/5840622605016498577/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/05/di-ioc-lifestyles.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/5840622605016498577'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/5840622605016498577'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/05/di-ioc-lifestyles.html' title='DI/IoC container lifestyles'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2mkIBpko_bBs_iTM1Ql8JXfxNGP8WnDq6PjSN0uxRXPSx7_29tMc59tuKGZeAVhTq3aDtixAVCDq5EyTA3FM3Uw0Wyf6fLqmpi3O4XHK0ph2O2-7jEX0BUC17Dtgj5Nx-StmE1-oQvYI/s72-c/available-deeper-in-call-hierarchy.png" height="72" width="72"/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-5655378328440718524</id><published>2011-04-29T17:22:00.002+03:00</published><updated>2011-04-29T18:21:33.356+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="NHibernate"/><title type='text'>NHibernate session management</title><content type='html'>&lt;p&gt;
Эту статью я хотел бы посвятить работе с &lt;a href=&quot;http://www.nhforge.org/&quot;&gt;NHibernate&lt;/a&gt;. Зачастую при обсуждении тех или иных реализаций object-relational mapper&#39;ов много внимания уделяется статическим аспектам их работы, в основном возможностям и реализации O/R mapping&#39;а. Однако, рано или поздно, mapping написан, и нужно перейти к решению поведенческих вопросов, в частности, осуществлению базовых CRUD операций.
&lt;/p&gt;

&lt;p&gt;
В рамках двух статей планирую осветить такие вопросы.
&lt;ol&gt;
&lt;li&gt;
Понятие session и transaction scope.
&lt;/li&gt;
&lt;li&gt;
Подходы к выбору гранулярности session/transaction и их совместное использование.
&lt;/li&gt;
&lt;li&gt;
Дизайн и реализация helper class&#39;ов для облегчения задач управления сессией. Понятие &lt;code&gt;unit of work&lt;/code&gt;, применение такой feature &lt;code&gt;NHibernate&lt;/code&gt;&#39;а как &lt;code&gt;&quot;contextual session&quot;&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
Реализация паттерна &lt;code&gt;Repository&lt;/code&gt; в контексте использования &lt;code&gt;NHibernate&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
Возможности формирования запросов в &lt;code&gt;NHibernate&lt;/code&gt;, и адаптация этих возможностей в реализации Repository паттерна. 
&lt;/li&gt;
&lt;/ol&gt;

Первые три вопроса будут рассмотрены в этом посте.
&lt;/p&gt; 

&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;

&lt;h2&gt;Session/transaction scope&lt;/h2&gt;
&lt;p&gt;
При изучении &lt;code&gt;NHibernate&lt;/code&gt;, практически во всех примерах, которые показывают как работать с persistent сущностями, можно увидеть такой код.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
using(var session = SessionFactory.OpenSession())
using(var transaction = session.BeginTransaction())
{
    //do some work, get all orders from DB for example
    
    transaction.Commit();
}
&lt;/pre&gt;

&lt;p&gt;
За время обучения NHibernate&#39;у, этот код настолько отпечатывается в мозгу, что воспринимаешь его как нечто данное по-умолчанию, и не задумываешься, что это всего лишь один из возможных подходов к управлению уровнем гранулярности сессий/транзакций.
&lt;/p&gt;

&lt;p&gt;
Однако, для того, чтобы перейти к рассмотрению возможных вариантов выбора гранулярности сессий и транзакций, нужно сначала определить цель и обязанность каждого из них.
&lt;/p&gt;

&lt;h3&gt;ISession - a scope of object identity&lt;/h3&gt;
&lt;p&gt;
&lt;code&gt;NHibernate ISession&lt;/code&gt; определяет scope of object identity. Выглядит весьма запутано. Для непосвященного человека это определение отнюдь не расшифровывает термин &lt;code&gt;ISession&lt;/code&gt;, а лишь вводит еще один термин &lt;code&gt;scope of object identity&lt;/code&gt;. &lt;strong&gt;Scope of object identity - это условия, при которых .NET identity объекта &lt;code&gt;(ReferenceEquals(a, b))&lt;/code&gt; эквивалетно database identity &lt;code&gt;(a.Id == b.Id)&lt;/code&gt;.&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
К примеру, в базе данных есть запись в таблице &lt;code&gt;Order&lt;/code&gt;. &lt;code&gt;Database identity&lt;/code&gt; определяется первичным ключом таблицы. То есть, если сравнивать две записи, то они будут считаться идентичными, если у них одинаковые значения первичного ключа &lt;code&gt;(a.Id == b.Id)&lt;/code&gt;. Между прочим, реляционная модель не разрешает наличие двух записей с одним и тем же значением первичного ключа.
&lt;/p&gt;

&lt;p&gt;
Если рассматривать .NET application domain, то здесь в качестве &lt;code&gt;object identity&lt;/code&gt; выступает адрес объекта в памяти. Если для двух объектов выполняется условие &lt;code&gt;ReferenceEquals(a, b)&lt;/code&gt;, то такие объекты будут считаться идентичными.
&lt;/p&gt;

&lt;p&gt;
Далее, рассмотрим такой сценарий. Приложение делает два запроса на получение заказа с ID равным 100. 
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
using(var session = SessionFactory.OpenSession())
using(var transaction = session.BeginTransaction())
{
    var a = session.Get&amp;lt;Order&amp;gt;(100);
    var b = session.Get&amp;lt;Order&amp;gt;(100);
}
&lt;/pre&gt;

&lt;p&gt;
В данном случае приложение оперирует двумя объектами a и b, которые представляют одну и ту же запись в базе данных. Так вот, scope of object identity определяет границы, в которых a будет идентично b &lt;code&gt;(ReferenceEquals(a, b))&lt;/code&gt;. В коде выше это условие будет выполнятся, так как оба запроса находятся в рамках одного и того же object identity scope, который декларируется при помощи класса &lt;code&gt;ISession&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
Если бы запросы делались в разных сессиях, то условие идентичности a и b не выполнялось бы, несмотря на то, что оба объекта представляют одну и ту же запись в таблице базы данных.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
using(var session = SessionFactory.OpenSession())
using(var transaction = session.BeginTransaction())
{
    var a = session.Get&amp;lt;Order&amp;gt;(100);
}

using(var session = SessionFactory.OpenSession())
using(var transaction = session.BeginTransaction())
{
    var b = session.Get&amp;lt;Order&amp;gt;(100);
}

Assert.IsFalse(ReferenceEquals(a, b));
&lt;/pre&gt;

&lt;p&gt;
Различают такие подходы к определению scope of object identity:
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;no identity scope&lt;/strong&gt; - нет никаких гарантий, что если два раза сделан запрос на получение одной строки из БД, то приложение будет оперировать идентичными объектами.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;transaction-scoped identity&lt;/strong&gt;, условие идентичности гарантируется в рамках транзакции (имеется ввиду не database transaction).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;process-scoped identity&lt;/strong&gt;, условие идентичности гарантируется в рамках всего приложения, application domain, если быть конкретней.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;
В NHibernate реализована transaction-scoped identity, правильней даже сказать, user-defined scoped identity. Работа с сессией осуществляется при помощи классов &lt;code&gt;ISessionFactory, ISession&lt;/code&gt;.
&lt;/p&gt;

&lt;h3&gt;
ITransaction - a scope of database transaction
&lt;/h3&gt;

&lt;p&gt;
Scope ITransaction&#39;а полностью соответсвует транзакции на уровне БД. То есть, начало новой транзакции &lt;code&gt;session.BeginTransaction()&lt;/code&gt; приводит к началу новой транзакции на уровне базы данных. Соответсвенно, &lt;code&gt;transaction.Commit()&lt;/code&gt; и &lt;code&gt;transaction.Rollback()&lt;/code&gt; завершают транзакцию на уровне БД, открытую ранее.
&lt;/p&gt;

&lt;p&gt;
Так как работа с ITransaction приводит к открытию транзакции на уровне БД, то нужно стремится, чтобы scope ITransaction был настолько мал, насколько это действительно нужно. Длинные транзакции - это нечто, чего следует избегать, так как это приводит к удержанию блокировок и неэффективному разделению ресурсов. Обычно transaction scope соответсвует выполнению операции либо группы операций по взаимодействию с базой (выборка заказов, последовательная работа с балансом двух счетов).
&lt;/p&gt;

&lt;p&gt;
Transaction scope отнюдь не должен соответвовать session scope. В учебных примерах, где выполняется одна или две операции - скорее да. Но в реальных приложениях session scope обычно шире чем transaction scope. &lt;strong&gt;В scope одной сессии может быть осуществлено несколько транзакций.&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
Введем еще одно понятие: Conversation или user transaction.
&lt;/p&gt;

&lt;h3&gt;Conversation&lt;/h3&gt;

&lt;p&gt;
&lt;strong&gt;Conversation, или user transaction - это некая единица работы с системой с точки зрения пользователя.&lt;/strong&gt; Обычно она состоит из последовательности шагов, набора запросов к приложению, набора запросов по работе с БД. К примеру, это может быть процедура order placement, которая включает такие шаги: выбора единиц заказа, варианта доставки, варианта оплаты, и т.д. В рамках этих шагов приложению может быть необходимо осуществить N запросов к БД, и розумеется, в данном случае нельзя полагаться на database-level transaction для изоляции конкурентных conversation&#39;ов.
&lt;/p&gt;

&lt;p&gt;
Далее будут показаны различные варианты выбора уровня гранулярности session/transaction scope.
&lt;/p&gt;

&lt;h2&gt;
Level of session/transaction scope granularity 
&lt;/h2&gt;

&lt;h3&gt;
Session per request
&lt;/h3&gt;
&lt;p&gt;
Первый и самый простой вариант - это когда гранулярность transaction и session scope совпадают.
&lt;/p&gt;

&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjprJEWjFQCWCvWfp8sSRBzlAdGv8Q7jDpXUMH0EYdA7peQBYY_TaHJmG73jmKf5wsbljr37diqnGhNW6KSKB_9vFwoPeKH0kMzTLI-Y-wOHZvFnCUcbCZtVwfYKLpyHy1yXiDjoUv_sog/s800/session-per-request.png&quot; alt=&quot;session-per-request&quot; /&gt;

&lt;p&gt;
В данной ситуации нас не волнует понятие conversation, либо оно вообще не применимо. Зачастую этот подход применяется для обслуживания запросов приложения, например http request&#39;a в ASP.NET приложении, либо запроса веб-сервиса, либо просто выполнение кода по взаимодействию с БД в консольном приложении.
&lt;/p&gt;

&lt;p&gt;
Если в рамках session scope помимо взаимодействию с БД выполняется дополнительная работа, то ее длительность может стать неприемлимой для transaction scope, и мы прийдем к вариции session-per-request &quot;1 session - N транзакций&quot;. Этот вариант наиболее ярко проявляется, если в рамках session scope, происходит несколько независимых запросов к БД, между которыми есть некие потенциально длительные вычисления (получение заказа, подсчет цен, сохранение цен вместе с измененным заказом).
&lt;/p&gt;

&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDNOEgoSWfrz8FFmUgycj1xdLnLqY2OwJRxEnUq7A7PKhyzFe7aUuiZLFdrZG1Awv59lBuInmFuF4x9ABKWWfT4rXzx0sXjyar5tTgE0QztSlmB18A8ATtVErZrDoCCkDNxQGCl-r8dK4/s800/session-per-request-with-multiple-transactions.png&quot; alt=&quot;session-per-request-with-multiple-trasactions&quot; /&gt;

&lt;h3&gt;Session per request with detached objects&lt;/h3&gt;

&lt;p&gt;
В этой ситуации в рассмотрение входит понятие conversation. К примеру, пользователь делает запрос на получение первых 10 заказов, далее редактирует их, и потом делает запрос на обновление. Сколько времени займет редактирование - неизвестно. Вопрос: &quot;Какой должен быть scope сессии?&quot; Должен ли он охватывать всю user transaction, начиная с момента выборки ордеров, и заканчиваю их обновлением, либо же для каждого отдельного шага должен создаваться отдельный session scope?
&lt;/p&gt;

&lt;p&gt;
В данном подходе применяется последний вариант. А для того, чтобы обеспечить возможность повторного использования сущностей, которые были получены на первом этапе (получение первых 10 заказов) во время второго этапа (обновление заказов), применяется понятие NHibernate&#39;а &quot;detached objects&quot;. Это значит, что объекты, полученные в рамках одного session scope покидают его, становясь &quot;detached&quot;, и далее, прикрепляются к другому session scope. Такие &quot;detached&quot; объекты могут быть переданы в другой layer приложения, возможно даже в другой application domain, либо вообще за рамки системы, и более того, возвращены обратно позже (Silverlight UI layer).
&lt;/p&gt;

&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRKMpB4wpXlpoDLlehOhMAetVg1m5Oi6I7_YtokgAinXkWWG2wwDH5EdZsQT3L_Q-5xFj0buyMzPy8AQOqOBQnlcUflvzTJ_5Vm9c-7FMpZYhFfVPwSoCESrV6w6kkgYXvaMSVuGrjEYI/s800/Session-per-request-with-detached-objects.png&quot; alt=&quot;Session per request with detached objects&quot; /&gt;


&lt;h3&gt;Session per conversation&lt;/h3&gt;
&lt;p&gt;
Этот подход противоположен предыдущему, то есть session scope соответствует conversation scope. В интервале между запросами к приложению, session отсоединяется от database connection при помощи операции &lt;code&gt;session.Disconnect()&lt;/code&gt;. Таким образом, экземпляр &lt;code&gt;IDbConnection&lt;/code&gt; может быть возвращен в &lt;code&gt;connection pool&lt;/code&gt; и повторно использован для обслуживания другого запроса в то время, пока пользователь, скажем, редактирует заказы. На мой взгляд, этот подход находит применение в довольно экзотических случаях и редко. Я бы не рекомендовал его исопользование без видимых на то причин.
&lt;/p&gt;

&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjp4l0lm4m-uaPwayyODEtAR0IJP-Lduf__qB4cJcDbKxKlYlZNXylPCC5kAFX4CUnntMcbNyqw5bMXGkvDVOGHTSbXW_sQz1rjZ81eM_0ESt1wItjq1Om1RcxB8iS19rhy2vrfezFI1o/s800/session-per-conversation.png&quot; alt=&quot;Session-per-conversation&quot; /&gt;

&lt;p&gt;
Ну а так как использовать database transaction для изоляции конкуретного доступа в рамках long-running conversation мы не можем, то в действие вступают механизмы optimistic locking&#39;а. Хотя это уже тема для отдельной статьи.
&lt;/p&gt;

&lt;h2&gt;
Session management
&lt;/h2&gt;

&lt;h3&gt;
Why do we need anything else?
&lt;/h3&gt;

&lt;p&gt;
Для задач управления сессиями/транзакциями используются классы &lt;code&gt;ISessionFactory, ISession, ITransaction&lt;/code&gt;. Эти интерфейсы удобны, и можно было бы пользоваться ими напрямую. Однако, есть аргументы в пользу реализации некоторого слоя поверх этих интерфейсов, либо набора helper-class&#39;ов, если слово &quot;слой&quot; звучит слишком серьезно. Вот некоторые из этих аргументов.
&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
Клиенты data-access layer приложения не должны знать об использовании NHibernate.

&lt;p&gt;
Что касается меня, то я к этому отношусь довольно спокойно, и не вижу ничего криминального, если знания об использовании NHibernate вытекут наружу из data-access layer. Во-первых, скрыть полностью врятли удасться, во-вторых, это сложно, в-третьих, скорее всего ограничить себя и пожертвовать возможностью в полной мере использовать фичи NHibernate. Ну и самое главное: &quot;А нужно ли это?&quot;. Подобный вопрос возникает, когда задаются целью скрыть знание о том, что данные сохраняются в БД, а не в файле к примеру. Нужно ответить на вопрос: &quot;Насколько вероятно, что back-end storage поменяется с БД на файловую систему?&quot;. Скорее всего, 0.001%. Тоже самое касается механизма работы data-access layer. &quot;Как часто меняется реализация persistence layer? Как часто переезжаем с dataset&#39;ов на NHibernate, с NHibernate на data reader&#39;ы?&quot;. На текущем проекте, к примеру, через 2 года после старта, переезжаем с Entity Framework на NHibernate. Не думаю, что стоит вкладывать усилия на скрытие этой информации. Изменения маловероятны. Более того, думаю, что цель ограничения масштаба возможных изменений все равно достигнута не будет.
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
Объект ISession может использоваться в месте отличном от того, где он был создан.

&lt;p&gt;
Обычно, &lt;code&gt;ISession&lt;/code&gt; создается в одном месте, а использоваться может пятью методами ниже по call stack&#39;у. В таком случае придется передавать объект ISession через все промежуточные компоненты, и засорять их ненужным знанием. Необходимо понятие текущей открытой сессии, которую можно получить в том месте, где она нужна, без необходимости ее передачи.
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
Вероятно использование Repository либо DAO, которым необходим объект ISessionFactory/ISession.
&lt;/li&gt;

&lt;li&gt;
Применяются dependency injection практики. Было бы неплохо иметь возможность делать constructor injection текущей открытой сессии там, где она необходима (в том же Repository/DAO), и дать возможность контейнеру разрешить зависимости автоматически.
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
Session management helper classes
&lt;/h3&gt;

&lt;p&gt;
Вот что получилось в итоге сделать. 
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;IPersistenceContext&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
Предоставляет доступ к текущей открытой сессии при помощи свойства CurrentSession, а также объектам ISessionFactory, с помощью которой была создана сессия, ну и configuration&#39;у всего persistence layer&#39;а.
&lt;/p&gt;

&lt;pre class = &quot;brush: csharp&quot;&gt;
public interface IPersistenceContext
{
    Configuration       Configuration { get; }
    ISessionFactory     SessionFactory { get; }
    ISession            CurrentSession { get; }
}
&lt;/pre&gt;

&lt;p&gt;
Предполагается, что можно сделать constructor injection IPersistenceContext в те классы, где необходимо взаимодействие с persistent слоем, к примеру EntityRepository. Также IPersistenceContext может быть с легкостью подменен на fake object в тестах.
&lt;/p&gt;

&lt;pre class = &quot;brush: csharp&quot;&gt;
public class EntityRepository&amp;lt;T&amp;gt; : IEntityRepository&amp;lt;T&amp;gt;
{
    //constructor injection of IPersistenceContext
    public EntityRepository(IPersistenceContext persistenceContext)
    {
        ...
    }

    public T Get(object id)
    {
        return _persistenceContext.CurrentSession.Get&amp;lt;T&amp;gt;(id);
    }
    ...
}
&lt;/pre&gt;

&lt;p&gt;
&lt;strong&gt;
Implementation of IPersistentContext
&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
Реализация PersistentContext принимает Configuration, и в ее обязанности входит управление жизненным циклом ISessionFactory, а также предоставление доступа к текушей открытой сессии. Наверное, здесь - это единственный интересный момент.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
public class PersistenceContext : IPersistenceContext, IDisposable
{
    private readonly Configuration _configuration;
    private readonly ISessionFactory _sessionFactory;
 
    public PersistenceContext(Configuration configuration)
    {
        _configuration = configuration;
        _sessionFactory = _configuration.BuildSessionFactory();
    }

    public Configuration Configuration { get { return _configuration; }}
    public ISessionFactory SessionFactory { get { return _sessionFactory; } }

    public ISession CurrentSession
    {
        get
        {
            if (!CurrentSessionContext.HasBind(SessionFactory))
            {
                OnContextualSessionIsNotFound();
            }
            var contextualSession = SessionFactory.GetCurrentSession();
            if (contextualSession == null)
            {
                OnContextualSessionIsNotFound();
            }
            return contextualSession; 
        }
    }

    public void Dispose()
    {
     SessionFactory.Dispose();
    }

    private static void OnContextualSessionIsNotFound()
    {
        throw new InvalidOperationException(&quot;Ambient instance of contextual session is not found. Open the db session before.&quot;);
    }
}
&lt;/pre&gt;

&lt;p&gt;
Используется feature NHibernate&#39;а под названием contextual session. Таким образом, все, что ожидает этот класс, это то, что текущая открытая сессия будет ассоциирована с неким контекстом и доступна через &lt;code&gt;ISessionFactory.GetCurrentSession()&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;
Использование CallSessionContext в качестве стратегии хранения текущей открытой сессии.
&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
Доступны такие &lt;a href=&quot;http://knol.google.com/k/nhibernate-chapter-2-architecture#2%282E%293%282E%29%28C2%29%28A0%29Contextual_Sessions&quot;&gt;стратегии места хранения текущей сессии&lt;/a&gt;.
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ManagedWebSessionContext, WebSessionContext&lt;/strong&gt;, текущая сессия хранится в HttpContext. 
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CallSessionContext&lt;/strong&gt;, текущая сессия хранится в CallContext. 
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ThreadStaticSessionContext&lt;/strong&gt;, текущая сессия хранится в thread static.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;
Из различных вариантов выбора контекста сессии остановился на использовании call context. Call context предоставляет слоты, где можно сохранить объекты, которые будут доступны ниже по call stack, а также дочерним потокам. Более подробно o CallContext &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext.aspx&quot;&gt;здесь&lt;/a&gt;. Из ограничений использования этого подхода, это то, что нельзя делать вложенные session scope, но думаю это и не надо.
&lt;/p&gt;

&lt;p&gt;
Конфигурация выбора нужной реализации ICurrentSessionContext проста. Вот как это выглядит при использовании Fluent NHibernate.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
Fluently.Configure().Database(MsSqlConfiguration.MsSql2005
    .ConnectionString(&quot;bar&quot;)
    .CurrentSessionContext(typeof(CallSessionContext).FullName))
&lt;/pre&gt;

&lt;p&gt;
Или с помощью классической конфигурации NHibernate:
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
var cfg = new Configuration();
cfg.Properties[Environment.ProxyFactoryFactoryClass] = typeof(ProxyFactoryFactory).AssemblyQualifiedName;
cfg.Properties[Environment.CurrentSessionContextClass] = typeof(CallSessionContext).FullName;
&lt;/pre&gt;


&lt;p&gt;
&lt;strong&gt;
Демаркация unit of work&#39;а
&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
Последнее, что осталось сделать - это обозначить границы session scope, то есть наш unit of work. Просто создать новый ISession c помощью &lt;code&gt;ISessionFactory.OpenSession()&lt;/code&gt; по-старинке недостаточно. Нужно еще связать созданную сессию с &lt;code&gt;CurrentSessionContext&lt;/code&gt;.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
public class DbSession : IDisposable
{
    private readonly ISessionFactory _sessionFactory;

    public DbSession(IPersistentContext persistentContext)
    {
        _sessionFactory = persistentContext.SessionFactory;
        CurrentSessionContext.Bind(_sessionFactory.OpenSession());
    }
 
    public void Dispose()
    {
        var session = CurrentSessionContext.Unbind(_sessionFactory);
        if (session != null &amp;&amp; session.IsOpen)
        {
            try
            {
                if (session.Transaction != null &amp;&amp; session.Transaction.IsActive)
                {
                    session.Transaction.Rollback();
                }
            }
            finally
            {
                session.Dispose();
            }
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;
Основная обязанность класса это управление жизненным циклом сессии, привязывание/отвязывание текущей сессии от &lt;code&gt;CurrentSessionContext&lt;/code&gt;. Класс не знает о выбранной стратегии реализации ICurrentSessionContext. Получается, что DbSession и PersistenceContext напрямую друг о друге ничего не знают, но знают куда положить и где забрать текущую сессию, вся коммуникация осуществляется через CurrentSessionContext.
&lt;/p&gt;

&lt;h3&gt;Usability/testability&lt;/h3&gt;
&lt;p&gt;
Имея такие сервисы как &lt;code&gt;ISessionFactory, IPersistenceContext, IEntityRepository&lt;/code&gt; становится просто тестировать приложение, подменяя зависимости на fake в тестах. При использовании DI/IoC контейнера, благодаря использованию constructor injection, становится возможно зарегистрировать все сервисы в контейнере и дать ему возможность автоматически разрешить зависимости.
&lt;/p&gt;

&lt;p&gt;
Вот как выглядит использование описанных выше коснтрукций в различных сценариях.
&lt;/p&gt;

&lt;pre class=&quot;brush:csharp&quot;&gt;

// this routine is usually executed only one per application run
// configure persistent layer and create session factory
Configuration configuration = ConfigurePersistenceLayer();
IPersistentContext persistentContext = new PersistentContext(configuration);

// somewhere in application
// new DbSession opens new NH ISession and attach to CurrentSessionContext
using (new DbSession(persistenceContext))
{
     // repository usage
     var orderRepository = new EntityRepository&amp;lt;Order&amp;gt;(persistenceContext);
     var order = orderRepository.Get(1);
        
     // usage of transaction with scope different from session scope
     using(var transaction = persistenceContext.CurrentSession.BeginTransaction())
     {
         // direct using of current session
         var allOrders = persistenceContext.CurrentSession.Query&amp;lt;Order&amp;gt;().ToList();
          
         transaction.Rollback();
     }

     // do not use current session, create new stateless session
     var statelessSession = persistenceContext.SessionFactory.OpenStatelessSession();
}

// direct using session out of DbSession scope through SessionFactory
var session = persistenceContext.SessionFactory.OpenSession();
&lt;/pre&gt;

&lt;h3&gt;Resources&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://techiethings.blogspot.com/2010/04/nhibernate-session-management.html&quot;&gt;Session management in NHibernate&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href=&quot;http://fgheysels.blogspot.com/2008/07/nhibernate-session-management.html&quot;&gt;Топик с тем же названием&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href=&quot;http://blog.schuager.com/2009/03/rich-client-nhibernate-session.html&quot;&gt;Rich-client NHibernate session management&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href=&quot;http://lostechies.com/nelsonmontalvo/2007/03/30/simple-nhibernate-example-part-4-session-management/&quot;&gt;Anti-пример как делать не надо&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext.aspx&quot;&gt;CallContext class on MSDN&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href=&quot;http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html&quot;&gt;ThreadStatic, CallContext and HttpContext in ASP.Net &lt;/a&gt;. И на эту же тему &lt;a href=&quot;http://forum.springframework.net/showthread.php?t=572&quot;&gt;здесь&lt;/a&gt; и &lt;a href=&quot;http://stackoverflow.com/questions/273301/callcontext-vs-threadstatic&quot;&gt;здесь&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;
&lt;a href=&quot;http://knol.google.com/k/nhibernate-chapter-2-architecture#2%282E%293%282E%29%28C2%29%28A0%29Contextual_Sessions&quot;&gt;About contextual sessions&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
To be continued...
&lt;/h2&gt;
&lt;p&gt;
В следующем посте, в качестве продолжения текущего, планирую рассказать о следующем.
&lt;ul&gt;
&lt;li&gt;
Реализация паттерна Repository в контексте использования NHibernate.
&lt;/li&gt;
&lt;li&gt;
Возможности формирования запросов в NHibernate, и адаптация этих возможностей для реализации Repository паттерна. 
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/5655378328440718524/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/04/nhibernate-session-management.html#comment-form' title='13 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/5655378328440718524'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/5655378328440718524'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/04/nhibernate-session-management.html' title='NHibernate session management'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjprJEWjFQCWCvWfp8sSRBzlAdGv8Q7jDpXUMH0EYdA7peQBYY_TaHJmG73jmKf5wsbljr37diqnGhNW6KSKB_9vFwoPeKH0kMzTLI-Y-wOHZvFnCUcbCZtVwfYKLpyHy1yXiDjoUv_sog/s72-c/session-per-request.png" height="72" width="72"/><thr:total>13</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-6337366248856873504</id><published>2011-04-08T06:04:00.003+03:00</published><updated>2011-09-29T20:44:29.695+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AutoMapper"/><title type='text'>Mapping object hierarchies with AutoMapper</title><content type='html'>&lt;p&gt;
Сегодня, в процессе работы на текущем проекте, в который раз столкнулся с необходимостью отображать сущности друг на друга. Сущности из domain model отображаются в сущности, принадлежащие data model, а те уже в свою очередь отображаются на базу данных при помощи Entity Framework. Напрямую использовать сущности &quot;made by Entity Framework&quot; не получилось, а так как Entity Framework версии 1.0, и POCO там и не пахло, то возникла такая цепочка отображений: Domain Model &amp;lt;-&amp;gt; Data model &amp;lt;-&amp;gt; Persistent storage.
&lt;/p&gt; 

&lt;p&gt;
Однако этот пост не об Entity Framework и POCO, а о применении такой библиотеки, как &lt;a href=&quot;http://automapper.codeplex.com/&quot;&gt;AutoMapper&lt;/a&gt;, в целях облегчения рутинных операций отображения сущностей.
&lt;/p&gt;

&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;

&lt;h2&gt;Object model&lt;/h2&gt;

&lt;p&gt;
Проблема отображения усложняется еще и тем, что нужно не просто отобразить одну плоскую сущность на другую, нужно отобразить иерархии сущностей из разных слоев приложения, которые конечно же отличаются друг от друга, иначе было бы не интересно. Смотрим диаграмму классов исходных сущностей (&lt;strong&gt;domain model&lt;/strong&gt;).
&lt;/p&gt;

&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNPq1TCfer1ZtBLNFVQaUQbnneq-luegWjOtSka8yCFOWvCXJucPr9tqhwrXGPbwG_rfoh6Uj8V29ByUSEPfCoojTgxBm-EpbkE6xXJWg27QZWE5OFOM8W3lWLBMd5fdRPjfIJXmJsQn8/s800/domain_model.png&quot; alt=&quot;Иерархия сущностей(domain model)&quot; /&gt;

&lt;p&gt;
В двух словах - описание модели. Есть сущность &lt;code&gt;Album&lt;/code&gt;, которая представляет собой альбом с эксклюзивными фотографиями, который заказывается через интернет-магазин. Каждое событие, происходящее с альбомом в рамках его жизненного цикла, моделируется как сущность типа &lt;code&gt;IAlbumEvent&lt;/code&gt;. Обычно каждое такое событие моделируется отдельным классом, который наследуется от &lt;code&gt;IAlbumEvent&lt;/code&gt; + &lt;code&gt;AlbumEventBase&lt;/code&gt;, однако есть и исключения. Эти дочерние классы содержат данные, специфичные для этого события, а также логику, скажем, для обновления статуса альбома.
&lt;/p&gt;

&lt;p&gt;
С другой стороны есть иерархия сущностей, которая является целевой, и в которую необходимо переложить исходные сущности (&lt;strong&gt;data model&lt;/strong&gt;). Эта модель в свою очередь 1:1 отображается на объекты базы данных при помощи ORM.
&lt;/p&gt;

&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7OP6kIqYk-CuYaO5DQHVbZ4olP4PsKQ9CEzuS6ZrakEQC7HB05AnXz0L9K-krV183EJ82nL1-ekyctfmBBPrylIwI_9RT8YIGMnSPPFD_ijWEtnGQ-1I9gVXmvhmtC6hdLnZ9RhQcpFA/s800/data_model.png&quot; alt=&quot;Иерархия сущностей(data layer)&quot; /&gt;

&lt;p&gt;
Последняя модель отличается от исходной по следующим параметрам:
&lt;ol&gt;
&lt;li&gt;
Сущность моделируется в виде отдельного класса, только если есть специфичные данные. Таким образом, в этой модели нет таких классов как &lt;code&gt;CreatedAlbumEvent&lt;/code&gt;, &lt;code&gt;SentAlbumEvent&lt;/code&gt;, у которых нет собственных уникальных элементов данных.
&lt;/li&gt;

&lt;li&gt;
Используются элементарные типы данных. Например, вместо &lt;code&gt;Album&lt;/code&gt; используется только &lt;code&gt;AlbumID&lt;/code&gt;, вместо &lt;code&gt;enum EventType&lt;/code&gt; -  строковое значение. Вместо &lt;code&gt;DateTime&lt;/code&gt; снова же используется строковое значение.
&lt;/li&gt;

&lt;li&gt;
Именование элементов данных.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
А так как &lt;strong&gt;основная проблема при отображении - это отличия между исходной и целевой сущностями&lt;/strong&gt;, то наше решение должно уметь справляться с описанными case&#39;ами.
&lt;/p&gt;

&lt;h2&gt;Custom solution&lt;/h2&gt;

&lt;p&gt;
Как решается эта проблема в большинстве случаев? Пишется что-то в таком духе.
&lt;/p&gt;

&lt;pre class=&quot;brush: csharp&quot;&gt;
public class CustomMapper
{
    public AlbumEventTarget MapToDataRepresentation(IAlbumEvent albumEventSource)
    {
        AlbumEventTarget albumEvent;
        if(albumEventSource is AlbumFailedEvent)
        {
            albumEvent = MapFailureAlbumEvent(albumEventSource as FailureAlbumEvent);
        }
        else if(albumEventSource is ProcessingCancelledAlbumEvent)
        {
        albumEvent = MapCancellationAlbumEvent(albumEventSource as CancellationAlbumEvent);
        }
        else if(albumEventSource is RestartAlbumEvent)
        {
            albumEvent = MapReshipAlbumEvent(albumEventSource as RestartAlbumEvent);
        }
        else
        {
            albumEvent = new AlbumEventTarget();
            albumEvent.EventID = albumEventSource.EventID;
            albumEvent.EventType = albumEventSource.Type.ToString().ToUpper();
            albumEvent.AlbumStatus = albumEventSource.Status.ToString();
            albumEvent.Timestamp = albumEventSource.Timestamp.ToString(&quot;MM/dd/yyyy&quot;, CultureInfo.InvariantCulture);
        }
        return albumEvent;
    }
}
&lt;/pre&gt;

&lt;p&gt;
&lt;strong&gt;Рано или поздно механическое перекладывание данных из одной сущности в другую по крайней мере утомляет&lt;/strong&gt;, особенно если сущности побольше и посложнее. Также, этот подход ведет к дублированию кода. К примеру, значение &lt;code&gt;EventType&lt;/code&gt; отображается как строка в upper case. А если это system-wide правило, то нам врядли захочется везде дублировать эту логику. Частично проблему можно решить введением класса-конвертера.
&lt;/p&gt;

&lt;pre class=&quot;brush:csharp&quot;&gt;
public class EnumStringConverter
{
    private readonly Type _enumType;

    public EnumStringConverter(Type enumType)
    {
        _enumType = enumType;
    }

    public string ConvertEnumToString(object enumObject)
    {
        return enumObject.ToString().ToUpper();
    }

    public object ConvertStringToEnum(string value)
    {
        return Enum.Parse(_enumType, value, true);
    }
}
&lt;/pre&gt;

&lt;p&gt;
То же самое можно сделать и для преобразования даты в определенном формате (MM/dd/yyyy), а затем везде использовать эти конвертеры, однако рутина ручного перекладывания все еще утомительна, особенно когда сущности практически идентичны. Также, при ручном перекладывании высока вероятность ненамеренно ошибиться.
&lt;/p&gt;

&lt;h2&gt;AutoMapper&lt;/h2&gt;

&lt;p&gt;
Что такое &lt;a href=&quot;http://automapper.codeplex.com/&quot;&gt;AutoMapper&lt;/a&gt; и зачем он нужен?

&lt;blockquote&gt;
&lt;p&gt;
A convention-based object-object mapper.
&lt;/p&gt;

&lt;p&gt;
AutoMapper uses a fluent configuration API to define an object-object mapping strategy. AutoMapper uses a convention-based matching algorithm to match up source to destination values. Currently, AutoMapper is geared towards model projection scenarios to flatten complex object models to DTOs and other simple objects, whose design is better suited for serialization, communication, messaging, or simply an anti-corruption layer between the domain and application layer.
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;Object-object mapping&lt;/strong&gt; - то, что нам нужно. &lt;strong&gt;Convention-based, fluent API&lt;/strong&gt; - ну что ж, конфигурирование отображения обещает быть немногословным и читабельным. Ничего не сказано про меппинг иерархий сущностей - попробуем прикрутить.
&lt;/p&gt;

&lt;p&gt;
Посмотрим как можно с его помощью отобразить сущность &lt;code&gt;CreatedAlbumEvent&lt;/code&gt; на &lt;code&gt;AlbumEventTarget&lt;/code&gt;.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
    Mapper.CreateMap&amp;lt;EventType, string&amp;gt;().ConvertUsing&amp;lt;EnumStringConverterAdapter&amp;lt;EventType&amp;gt;&amp;gt;();
    Mapper.CreateMap&amp;lt;AlbumStatusEnum, string&amp;gt;().ConvertUsing&amp;lt;EnumStringConverterAdapter&amp;lt;AlbumStatusEnum&gt;&amp;gt;();

    Mapper.CreateMap&amp;lt;CreatedAlbumEvent, AlbumEventTarget&amp;gt;()
        .ForMember(t =&amp;gt; t.AlbumStatus, m =&amp;gt; m.MapFrom(s =&gt; s.Status))
        .ForMember(t =&amp;gt; t.EventType, m =&amp;gt; m.MapFrom(s =&gt; s.Type));
&lt;/pre&gt;

&lt;p&gt;
Отображение отдельного элемента данных описывается при помощи кострукции &lt;code&gt;ForMember&lt;/code&gt; только для тех элементов, чьё именование различается в исходной и целевой сущностях. Элементы с одинаковым именованием будут отображены автоматически (convention-based). Также указано, как отображать перечисления &lt;code&gt;EventType&lt;/code&gt;, &lt;code&gt;AlbumStatusEnum&lt;/code&gt; в значения строкового типа (&lt;strong&gt;custom type converters&lt;/strong&gt;). Более того, однажды описав &lt;code&gt;custom type converter&lt;/code&gt;, AutoMapper будет использовать его в любом другом отображении, в нашем случае, при отображении CreatedAlbumEvent в AlbumEventTarget. Таким образом, область действия custom type converter - application-wide scope. Если необходимо выборочное применение преобразования, используется такое понятие, как &lt;strong&gt;custom value resolver&lt;/strong&gt;.
&lt;/p&gt;

&lt;h3&gt;Custom value resolver&lt;/h3&gt;

&lt;p&gt;
Предположим, что AlbumStatus перечисление в качестве исключения не нужно преобразовывать в upper case, а просто использовать метод ToString().
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
     Mapper.CreateMap&amp;lt;CreatedAlbumEvent, AlbumEventTarget&amp;gt;()
        .ForMember(t =&amp;gt; t.AlbumStatus, m =&amp;gt; m.ResolveUsing&amp;lt;SimpleEnumResolver&amp;gt;().FromMember(s =&gt; s.Status))
        .ForMember(t =&amp;gt; t.EventType, m =&amp;gt; m.MapFrom(s =&gt; s.Type));
&lt;/pre&gt;

&lt;p&gt;
В данном случае для отображения &lt;code&gt;AlbumStatus&lt;/code&gt; применили custom value resolver. Конечно, отдельный класс для этих целей - это overhead. Было бы неплохо обойтись lambda выражением.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
public class SimpleEnumResolver : ValueResolver&amp;lt;AlbumStatusEnum, string&amp;gt;
{
    protected override string ResolveCore(AlbumStatusEnum source)
    {
        return source.ToString();
    }
}
&lt;/pre&gt;

&lt;h3&gt;Custom value formatter&lt;/h3&gt;

&lt;p&gt;
Value formatter - этот тот же type converter, только целевой тип - это всегда string. Применим его для отображения &lt;code&gt;Timestamp&lt;/code&gt; элемента данных (DateTime в string).
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
mapping.ForMember(t =&amp;gt; t.Timestamp, m =&amp;gt; m.AddFormatter&amp;lt;DateTimeFormatter&amp;gt;())
&lt;/pre&gt;

где DateTimeFormatter опять же отдельный класс:

&lt;pre class = &quot;brush:csharp&quot;&gt;
public class DateTimeFormatter : ValueFormatter&amp;lt;DateTime&amp;gt;
{
    protected override string FormatValueCore(DateTime value)
    {
        return value.ToString(&quot;MM/dd/yyyy&quot;, CultureInfo.InvariantCulture);
    }
}
&lt;/pre&gt;

&lt;h2&gt;
Отображение иерархий сущностей
&lt;/h2&gt;

&lt;p&gt;
Пока что все просто. Попробуем отобразить сущности, входящие в иерархию. Хочется в результате иметь возможность использовать API нашего самописного CustomMapper, переключив его реализацию на использование AutoMapper&#39;а. Ну или вообще отказаться от этого класса, главное чтобы API был выражен в терминах базовых сущностей из обеих иерархий: &lt;code&gt;IAlbumEvent, AlbumEventTarget&lt;/code&gt;.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
public class CustomMapper
{
    public AlbumEventTarget MapToDataRepresentation(IAlbumEvent albumEventSource)
    {
        return Mapper.Map&amp;lt;IAlbumEvent, AlbumEventTarget&amp;gt;(albumEventSource);
    }
}
&lt;/pre&gt;

&lt;p&gt;
В первом приближении конфигурация отображения выглядит так.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
Mapper.CreateMap&amp;lt;IAlbumEvent, AlbumEventTarget&amp;gt;()
    .ForMember(t =&amp;gt; t.AlbumStatus, m =&amp;gt; m.ResolveUsing&amp;lt;SimpleEnumResolver&amp;gt;().FromMember(s =&amp;gt; s.Status))
    .ForMember(t =&amp;gt; t.EventType, m =&amp;gt; m.MapFrom(s =&gt; s.Type))
    .Include&amp;lt;ProcessingCancelledAlbumEvent, AlbumEventCancelTarget&amp;gt;()
    .Include&amp;lt;FailedAlbumEvent, AlbumEventFailTarget&amp;gt;()
    .Include&amp;lt;CreatedAlbumEvent, AlbumEventTarget&amp;gt;()
    .Include&amp;lt;RestartAlbumEvent, AlbumEventRestartTarget&amp;gt;();

Mapper.CreateMap&amp;lt;ProcessingCancelledAlbumEvent, AlbumEventCancelTarget&amp;gt;();
Mapper.CreateMap&amp;lt;FailedAlbumEvent, AlbumEventFailTarget&amp;gt;();
Mapper.CreateMap&amp;lt;CreatedAlbumEvent, AlbumEventTarget&amp;gt;();
Mapper.CreateMap&amp;lt;ReshipAlbumEvent, AlbumEventReshipTarget&amp;gt;()
    .ForMember(s =&amp;gt; s.AlbumID, m =&amp;gt; m.MapFrom(s =&gt; s.SourceAlbum.AlbumID))
    .ForMember(s =&amp;gt; s.RestartReasonName, m =&amp;gt; m.MapFrom(s =&gt; s.Reason.Name));
&lt;/pre&gt;

&lt;p&gt;
Конфигурируется отображение базовых сущностей, далее идет описание отображения дочерних сущностей. А также, при помощи конструкции &lt;code&gt;Include&lt;/code&gt;, отображение базовых сущностей теперь знает о существовании дочерних сущностей.
&lt;/p&gt;

&lt;p&gt;
Однако, не все так гладко. Если попробовать отобразить ProcessingCancelledAlbumEvent в AlbumEventCancelTarget, то некоторые элементы данных в целевой сущности останутся неустановленными.
&lt;/p&gt;

&lt;pre class=&quot;brush:csharp&quot;&gt;
var albumEvent = new ProcessingCancelledAlbumEvent (&quot;actor&quot;)
{
    EventID = 1,
    Status = AlbumStatusEnum.Created,
    Timestamp = new DateTime(2010, 1, 1),
    Type = EventType.Cancelled,
    Comment = &quot;comment&quot;
};
var target = (AlbumEventCancelTarget) Mapper.Map&amp;lt;IAlbumEvent, AlbumEventTarget&amp;gt;(albumEvent);
&lt;/pre&gt;

&lt;img src = &quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEib7Q3-dZ1ki0BWJ3d5RFJvuwYOzPQb0Ww4_CKBfcjMpJBUZKDc5LDemWIUQHzaZQYDLa_jMj3YU8Z4OBkLAEvNvG_6TgJ9zLHmXwlSDpVhetH20beremfsEL5v4knT7foL220Xmoy89bg/s800/CancellationEventIsNotMapped.png&quot; /&gt;

&lt;p&gt;
Итак, собственные элементы данных сущности &lt;code&gt;ProcessingCancelledAlbumEvent &lt;/code&gt; такие как, Actor и Comment установлены. Отображение явно для них не конфигурировалось, однако ввиду одинакового именования элементов данных, они были отображены автоматически. Аналогичным образом были установлены свойства Tag, EventID, Timestamp, а вот Status и Type остались пустыми. То есть меппинг, описанный для связки IAlbumEvent-AlbumEventTarget ни коим образом не оказывает влияние на связку ProcessingCancelledAlbumEvent -AlbumEventCancelTarget. Приходим к решению повторять меппинг базовых элементов данных для каждой дочерней сущности.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
Mapper.CreateMap&amp;lt;ProcessingCancelledAlbumEvent , AlbumEventCancelTarget&amp;gt;()
    .ForMember(t =&amp;gt; t.AlbumStatus, m =&amp;gt; m.ResolveUsing&amp;lt;SimpleEnumResolver&amp;gt;().FromMember(s =&amp;gt; s.Status))
    .ForMember(t =&amp;gt; t.EventType, m =&amp;gt; m.MapFrom(s =&amp;gt; s.Type))
&lt;/pre&gt;

&lt;p&gt;
Однако, повторение меппинга базовых элементов данных для каждой дочерней сущности делает это решение еще хуже, чем наша самописная реализация. Как вариант, можно выделить повторяющийся код меппинга в виде extension метода.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
public static class AlbumEventMappingExtensions
{
    public static IMappingExpression&amp;lt;TSource, TDestination&amp;gt; MapGenericAlbumEvent&amp;lt;TSource, TDestination&amp;gt;(
        this IMappingExpression&amp;lt;TSource, TDestination&amp;gt; mapping)
            where TSource : IAlbumEvent
            where TDestination : AlbumEventTarget
    {
        return mapping
            .ForMember(t =&amp;gt; t.AlbumStatus, m =&amp;gt; m.ResolveUsing&amp;lt;SimpleEnumResolver&amp;gt;().FromMember(s =&amp;gt; s.Status))
            .ForMember(t =&amp;gt; t.EventType, m =&amp;gt; m.MapFrom(s =&amp;gt; s.Type))
            .ForMember(t =&amp;gt; t.Timestamp, m =&amp;gt; m.AddFormatter&amp;lt;DateTimeFormatter&amp;gt;());
    }
}
&lt;/pre&gt;

&lt;p&gt;
Окончательная версия конфигурации меппинга для всей иерархии будет выглядеть так.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
Mapper.CreateMap&amp;lt;EventType, string&amp;gt;().ConvertUsing&amp;lt;EnumStringConverterAdapter&amp;lt;EventType&amp;gt;&amp;gt;();

Mapper.CreateMap&amp;lt;IAlbumEvent, AlbumEventTarget&amp;gt;()
    .MapGenericAlbumEventData()
    .Include&amp;lt;ProcessingCancelledAlbumEvent , AlbumEventCancelTarget&amp;gt;()
    .Include&amp;lt;FailedAlbumEvent, AlbumEventFailTarget&amp;gt;()
    .Include&amp;lt;CreatedAlbumEvent, AlbumEventTarget&amp;gt;()
    .Include&amp;lt;RestartAlbumEvent, AlbumEventRestartTarget&amp;gt;();

Mapper.CreateMap&amp;lt;ProcessingCancelledAlbumEvent , AlbumEventCancelTarget&amp;gt;().MapGenericAlbumEventData();
Mapper.CreateMap&amp;lt;FailedAlbumEvent, AlbumEventFailTarget&amp;gt;().MapGenericAlbumEventData();
Mapper.CreateMap&amp;lt;CreatedAlbumEvent, AlbumEventTarget&amp;gt;().MapGenericAlbumEventData();
Mapper.CreateMap&amp;lt;RestartAlbumEvent, AlbumEventReshipTarget&amp;gt;().MapGenericAlbumEventData()
    .ForMember(s =&amp;gt; s.AlbumID, m =&amp;gt; m.MapFrom(s =&amp;gt; s.SourceAlbum.AlbumID))
    .ForMember(s =&amp;gt; s.ReshipReasonID, m =&amp;gt; m.MapFrom(s =&amp;gt; s.Reason.ReasonID));
&lt;/pre&gt;

&lt;h3&gt;Polymorphic element types in collections&lt;/h3&gt;
Особая ценность меппинга иерархии сущностей проявляется тогда, когда работа с сущностями происходит полиморфно. К примеру, у нас может быть коллекция сущностей IAlbumEvent[] (все события в жизненном цикле отдельного заказа), которую мы хотим отобразить на коллекцию AlbumEventTarget[]. При этом все дочерние сущности должны быть также корректно отображены.

&lt;pre class = &quot;brush:csharp&quot;&gt;
var album = new Album();
album.Events.Add(createdEvent);
album.Events.Add(failedEvent);
album.Events.Add(cancellationEvent);

var eventsMapped = Mapper.Map&amp;lt;IList&amp;lt;IAlbumEvent&amp;gt;, IList&amp;lt;AlbumEventTarget&amp;gt;&amp;gt;(album.Events);
&lt;/pre&gt;

&lt;h2&gt;Mapping hierarchy to DTO&lt;/h2&gt;

&lt;p&gt;
Рассмотрим такой case, как отображение иерархии в DTO (Data Transfer Object). С одной стороны уже знакомая иерархия объектов (domain model).
&lt;/p&gt;

&lt;img src=https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNPq1TCfer1ZtBLNFVQaUQbnneq-luegWjOtSka8yCFOWvCXJucPr9tqhwrXGPbwG_rfoh6Uj8V29ByUSEPfCoojTgxBm-EpbkE6xXJWg27QZWE5OFOM8W3lWLBMd5fdRPjfIJXmJsQn8/s800/domain_model.png&quot; alt=&quot;Иерархия сущностей(domain model)&quot; /&gt;

&lt;p&gt;
С другой стороны, простой плоский DTO.
&lt;/p&gt;

&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKHbCvgTS-crepp-sM9-Lw-NIJSDZqoYp-Nq85O2ibfTKajckU9_4zygqZroQtklFcmpItjpqn2JbbcYKcfCzPlnbWwpeYspzCLGy6kvYSjeQjHV2J7-FjZzMI9Ql5TdpGvG3g-JE5nws/s800/DTO.png&quot; /&gt;

&lt;p&gt;
Предполагается, что все специфические поля таких событий, как Cancellation, Failure, Restart, будут отображаться на один элемент данных Details. За счет этого иерархия &quot;схлопнется&quot; в плоский DTO.
&lt;/p&gt;

&lt;pre class = &quot;brush:csharp&quot;&gt;
Mapper.CreateMap&amp;lt;IAlbumEvent, DTO&amp;gt;()
    .MapGenericAlbumEventDataToDTO()
    .Include&amp;lt;ProcessingCancelledAlbumEvent, DTO&amp;gt;()
    .Include&amp;lt;FailedAlbumEvent, DTO&amp;gt;()
    .Include&amp;lt;RestartAlbumEvent, DTO&amp;gt;();

Mapper.CreateMap&amp;lt;ProcessingCancelledAlbumEvent, DTO&amp;gt;()
    .MapGenericAlbumEventDataToDTO()
    .ForMember(s =&amp;gt; s.Details, m =&amp;gt; m.MapFrom(s =&gt; &quot;Actor: &quot; + s.Actor + &quot;; Comment: &quot; + s.Comment));
Mapper.CreateMap&amp;lt;FailedAlbumEvent, DTO&amp;gt;()
    .MapGenericAlbumEventDataToDTO()
    .ForMember(s =&amp;gt; s.Details, m =&amp;gt; m.MapFrom(s =&gt; &quot;Reason: &quot; + s.Reason + &quot;; Origin: &quot; + s.Origin));
Mapper.CreateMap&amp;lt;RestartAlbumEvent, DTO&amp;gt;()
    .MapGenericAlbumEventDataToDTO()
    .ForMember(s =&amp;gt; s.Details, m =&gt; m.MapFrom(s =&amp;gt; &quot;Reason: &quot; + s.Reason));
                

var albumEvent = new ProcessingCancelledAlbumEvent(&quot;actor&quot;)
{
    EventID = 1,
    Status = AlbumStatusEnum.Created,
    Timestamp = new DateTime(2010, 1, 1),
    Type = EventType.Restart,
    Comment = &quot;comment&quot;
};

var target = Mapper.Map&amp;lt;IAlbumEvent, DTO&amp;gt;(albumEvent);
&lt;/pre&gt;

&lt;p&gt;
&lt;strong&gt;К сожалению, этот код не работает как ожидается.&lt;/strong&gt; Вместо меппинга связки &quot;ProcessingCancelledAlbumEvent-DTO&quot; происходит меппинг связки &quot;IAlbumEvent-DTO&quot;. По всей видимости &lt;code&gt;Include&lt;/code&gt; ожидает, что оба и &lt;code&gt;TSource&lt;/code&gt; и &lt;code&gt;TDestination&lt;/code&gt; будут дочерними сущностями. В нашем же случае CancellationAlbumEvent наследуется от IAlbumEvent, а DTO один единственный, и тут просто нет иерархии и нет дочерних сущностей.
&lt;/p&gt;

&lt;p&gt;
На мой взгляд, меппинг &quot;Hierarchy - DTO&quot; более распространенный case, чем &quot;Hierarchy - Hierarchy&quot;, а он, к сожалению, не поддерживается.
&lt;/p&gt;

&lt;h2&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;http://automapper.codeplex.com/&quot;&gt;AutoMapper website on Codeplex&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://lostechies.com/jimmybogard/2009/01/23/automapper-the-object-object-mapper/&quot;&gt;AutoMapper overview article&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://www.codeproject.com/KB/library/AutoMapper.aspx&quot;&gt;Article about AutoMapper on CodeProject&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/6337366248856873504/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/04/mapping-object-hierarchies-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/6337366248856873504'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/6337366248856873504'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/04/mapping-object-hierarchies-with.html' title='Mapping object hierarchies with AutoMapper'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNPq1TCfer1ZtBLNFVQaUQbnneq-luegWjOtSka8yCFOWvCXJucPr9tqhwrXGPbwG_rfoh6Uj8V29ByUSEPfCoojTgxBm-EpbkE6xXJWg27QZWE5OFOM8W3lWLBMd5fdRPjfIJXmJsQn8/s72-c/domain_model.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-5175459504163921508</id><published>2011-03-15T03:22:00.002+02:00</published><updated>2011-03-15T03:31:22.327+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="TDD/BDD"/><title type='text'>Тестирование поведения приложений: эволюция подходов. От debugging к unit testing, TDD и BDD.</title><content type='html'>&lt;p&gt;
Каждый software developer помнит то время, с которого началась его, ставшая теперь уже профессиональной, деятельность. Это не отдельный момент времени, а скорее период, когда появилось увлечение компьютерами, информационными технологиями, а затем была первая программа, затем следующая, затем еще и еще. Сейчас сложно даже их назвать программами, хотя тогда они определенны были таковыми.
&lt;/p&gt;

&lt;p&gt;
В этой статье вспомним что делалось тогда, чтобы убедиться в отсуствии ошибок, чтобы довести программу до работоспособного состояния, в конце концов, чтобы понять что она делает то, для чего задумывалась, и ведет себя корректно. И посмотрим, что делается для этого сейчас, уже когда программирование перестало быть лишь увлечением и переросло в профессиональную разработку ПО. Попытаюсь изложить этапы понимания и развития обеспечения корретного поведения приложений, через которые прошел я. Этапы исключительно субъективны, однако возможно Вы найдете себя в одном из них сейчас или в прошлом.
&lt;/p&gt;

&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;

&lt;h2&gt;Debugging hell&lt;/h2&gt;
&lt;p&gt;
Все из нас знают такой инструмент, как debugging. Сейчас очень редко приходится прибегать к нему, однако это не всегда было так. После написания программы, обычно довольно тривиальной, возникает желание убедиться в том, что то, что написано, это то, что мы хотели написать, а не то, что у нас получилось. Единственный инструмент, который имеется в наличии, или о которым мы знаем - это debugging. Ставим breakpoint в entry point программы, запускаем ёё, и начинаем отслеживать ход исполнения и верифицировать результаты. 
&lt;/p&gt;

&lt;p&gt;
Даже для программы с одной тривиальной задачей и одним сценарием, например, реализации алгоритма быстрой сортировки, процесс отладки может быть трудоемок и длителен, и занимать собственно больше времени, чем ушло на кодирование. По мере добавления новых возможностей в программу, сложность и количество возможных сценариев, которые нужно проверить, увеличивается экспоненциально, если не быстрее. Если вы помните бессоную ночь, проведенную за debugging, то это как раз тот случай. Иногда и такого инструмента как debugging в начилии нет, тогда все становится еще печальней.
&lt;/p&gt;

&lt;p&gt;
Потратив массу времени на отладку и проверив все сценарии, к конце концов мы получаем заряд уверенности, что все ОК. Стоит отметить, что у другого человека, который не провел night full of debugging, такого заряда нет, и он вряд ли передастся ему от Вас. Что произойдет когда нужно будет внести изменения в поведение программы? Наша уверенность испарится немедленно. И здесь выбор невелик, либо снова засесть за debugging и пепроверить все сценарии, либо просто убедить себя, что все ОК. Если поджимают сроки, к примеру, сегодня защита курсового, то на первый вариант элементарно нет времени, даже если есть желание (хотя это вряд ли), и остается второй вариант. Что дальше? А дальше программа падает в самый не подходящий момент, например на презентации при защите перед комиссией.
&lt;/p&gt;

&lt;h2&gt;Ручной запуск приложения в разных сценариях&lt;/h2&gt;
&lt;p&gt;
Если приложение хоть немного сложнее, чем вывод на консоль &quot;Hello %username%&quot;, то нам нужен код, который будет имитировать различные сценарии, запускать приложение, передавая ему подготовленные входные данные и зависимости. Обычно создается отдельное консольное приложение, либо меняется &lt;code&gt;entry point&lt;/code&gt; самого приложения, чтобы работать как в &lt;code&gt;production&lt;/code&gt;, так и в &lt;code&gt;testing&lt;/code&gt; режиме. 
&lt;/p&gt;

&lt;pre class=&quot;brush: csharp&quot;&gt;
public class Program
{
    public static void Main(string[] args)
    {
        var mode = (args != null &amp;&amp; args.Length &gt; 0 ? args[0] : &quot;prod&quot;).ToUpper();
        if(mode == &quot;PROD&quot;)
        {
            LaunchProgram();
        }
        else if(mode == &quot;TEST&quot;)
        {
            TestProgramInScenarioA();
            //TestProgramInScenarioB();
            //TestProgramInScenarioC();
            //TestProgramInScenarioD();
            //TestProgramInScenarioE();
        }
        else
        {
            Console.WriteLine(&quot;Only prod and test modes are supported&quot;); 
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;
Запуск происходит в ручном режиме, происходит воссоздание отдельно взятого сценария, а дальше происходит погружение в &quot;старый добрый&quot; &lt;code&gt;debugging&lt;/code&gt;. Этот этап мало чем отличается от предущего, за тем лишь отличием, что перед &lt;code&gt;debugging&lt;/code&gt; добавлена некоторая подготовительная работа по имитации сценария.
&lt;/p&gt;

&lt;h2&gt;Интеграционные тесты в ручном режиме&lt;/h2&gt;
&lt;p&gt;
В методы, которые на предыдущем этапе лишь имитировали сценарий для дальнейшей отладки, добавляется также проверка конечного результата работы программы на корректность. Эти, так называемые тесты, запускаются последовательно в ручном режиме. Если же программа падает на каком-то тесте, то дальнейшие тесты не запускаются, так как нету изоляции запуска тестов. В случае упавшего теста снова возвращаемся к debugging, до тех пор пока все тесты не будут успешно выполняться.
&lt;/p&gt;

&lt;pre class = &quot;brush: csharp&quot;&gt;
private void TestProgramInScenarioA()
{
    var sorter = new QuickSortAlgorithm();
    var sortedList = sorter.Sort(new[] {1, 2, 3, 4, 5, 6, 7}, true);

    VerifyEquality(sortedList, new[] {1, 2, 3, 4, 5, 6, 7});
}

private void TestProgramInScenarioB()
{
    var sorter = new QuickSortAlgorithm();
    var sortedList = sorter.Sort(new[] { 1, 2, 3, 4, 5, 6, 7 }, false);

    VerifyEquality(sortedList, new[] { 7, 6, 5, 4, 3, 2, 1});
}

private void TestProgramInScenarioC()
{
    var sorter = new QuickSortAlgorithm();
    var sortedList = sorter.Sort(new[] { 2, 5, 1, 4, 6, 3, 7 }, true);

    VerifyEquality(sortedList, new[] { 1, 2, 3, 4, 5, 6, 7 });
}
&lt;/pre&gt;

&lt;p&gt;
Из плюсов, добавилась автоматическая проверка результатов. Однако минусов по прежнему много.

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Результаты не являются повторяемыми (repeatable).&lt;/strong&gt;
&lt;br /&gt;
Для того, чтобы автоматическая проверка стала возможной, конечный результат должен обладать свойством повторяемости, то есть оставаться неизменным в зависимости от времени запуска. Так как приложение тестируется в целом, то есть тест интеграционный,  то на конечный результат влияет множество факторов, как внутренних, так и внешних по отношению к приложению. Однако в отличии от внутренних факторов, которые можно контролировать, на внешние факторы повлиять зачастую не представляется возможным, что приводит к неповторяемым результатам. 
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;Приложение вообще может не иметь осязаемого результата работы, который можно проверить в детерминированной манере.&lt;/strong&gt; 
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;Неудобство ручного запуска тестов.&lt;/strong&gt;
&lt;br /&gt;
Приходиться заниматься вопросами ручного запуска, организации, поддержки набора тестов, и каждый раз создавать для этого доморощенную инфраструктуру.
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;Debugging активность по прежнему доминирует в большом объеме.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;
Обозначенные недостатки делают этот подход частичным, и его применение не гарантирует корректного поведения приложения. Падающие тесты не гарантируют, что корень проблемы в приложении, так как результат неповторяем и может зависеть от внешних факторов. В свою очередь успешные тесты не гарантируют, что проблем нет.
&lt;/p&gt;

&lt;h2&gt;
Интеграционные тесты + unit testing framework
&lt;/h2&gt;

&lt;p&gt;
На этом этапе применяется один из unit-testing framework&#39;ов, такой как &lt;a href=&quot;http://www.nunit.org/&quot;&gt;NUnit&lt;/a&gt;, &lt;a href=&quot;http://www.testdriven.net/&quot;&gt;TestDriven.NET&lt;/a&gt;, &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ms243147%28v=vs.80%29.aspx&quot;&gt;MSTest&lt;/a&gt;, &lt;a href=&quot;http://xunit.codeplex.com/&quot;&gt;xUnit&lt;/a&gt;, ну и &lt;a href = &quot;http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks&quot;&gt;так далее&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
Однако его применение ограничено использованием инфраструктуры, которую предоставляет framework для автоматического запуска набора тестов в изолированном режиме. Подход к тестированию остается прежним: интеграционные тесты, и проблемы, обозначенные в предыдущей главе, остаются.
&lt;/p&gt;

&lt;p&gt;
На этом этапе может поменяться подход к именованию тестов. Тестов становится больше, и названия типа &lt;code&gt;TestApplicationInScenario111&lt;/code&gt; начинают вызывать отвращение.
&lt;/p&gt;

&lt;pre class=&quot;brush: csharp&quot;&gt;
[Test]
private void TestQuickSortInAscOrderOnAlreadyPresortedData()
{
    var sorter = new QuickSortAlgorithm();
    var sortedList = sorter.Sort(new[] {1, 2, 3, 4, 5, 6, 7}, true);

    VerifyEquality(sortedList, new[] {1, 2, 3, 4, 5, 6, 7});
}

[Test]
private void TestQuickSortInDescOrderOnReversedData()
{
    var sorter = new QuickSortAlgorithm();
    var sortedList = sorter.Sort(new[] { 1, 2, 3, 4, 5, 6, 7 }, false);

    VerifyEquality(sortedList, new[] { 7, 6, 5, 4, 3, 2, 1});
}

[Test]
private void TestQuickSortInDescOrderOnRandomInputData()
{
    var sorter = new QuickSortAlgorithm();
    var sortedList = sorter.Sort(new[] { 2, 5, 1, 4, 6, 3, 7 }, true);

    VerifyEquality(sortedList, new[] { 1, 2, 3, 4, 5, 6, 7 });
}
&lt;/pre&gt;

&lt;p&gt;
На самом деле подобное именование далеко от идеала, и это мягко сказано. Другие &lt;a href=&quot;http://devlicio.us/blogs/derik_whittaker/archive/2009/10/07/testing-only-the-code-of-value.aspx&quot;&gt;выражаются в более резкой форме&lt;/a&gt;.
&lt;blockquote&gt;
The name of this tests SUCKS BALLS and does not clearly convey the intent of the test.
&lt;/blockquote&gt;
&lt;/p&gt;

&lt;p&gt;
Также можно &lt;a href=&quot;http://en.wikipedia.org/wiki/Continuous_integration#Make_the_build_self-testing&quot;&gt;интегрировать запуск тестов&lt;/a&gt; в &lt;a href=&quot;http://martinfowler.com/articles/continuousIntegration.html&quot;&gt; continuous integration&lt;/a&gt; процесс, если конечно на данном этапе мы уже практикуем &lt;code&gt;continuous integration&lt;/code&gt;. А если учесть что тесты интеграционные, и их результаты непредсказуемы, то применение этого подхода не несет никакой пользы.
&lt;/p&gt;

&lt;h2&gt;Переход от интеграционных тестов к unit тестам&lt;/h2&gt;
&lt;p&gt;
Здесь приходит понимание, что тестирование конечного результата работы приложения либо какой-то из его подсистем не является гарантией корректного поведения приложения. Область тестирования сужается от уровня системы либо подсистемы к отдельным ее модулям и отдельным  аспектам их работы. 
&lt;/p&gt;

&lt;h3&gt;Свойства хорошего unit-test&#39;а&lt;/h3&gt;
&lt;p&gt;
При написании тестов нужно стремиться, чтобы тесты характеризовались следующими свойствами.
&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Repeatable&lt;/strong&gt;
&lt;p&gt;
Для того, чтобы автоматическое тестирование стало возможным, надо обеспечить повторяемость результатов тестов (repeatable test). &lt;strong&gt;В этом состоит главное отличие от интеграционных тестов, результат работы которых непредсказуем.&lt;/strong&gt; Если мы написали модульный тест, который не обладает свойством повторяемости, то это просто маленький интеграционный тест.
&lt;/p&gt;

&lt;p&gt;
Вне зависимости от выбора уровня детализации модуля, если модуль в свою очередь зависит от других модулей (подсистем, систем), которые в данном случае играют роль внешних факторов, на которые мы повлиять не можем, но которые в то же время влияют на результат, то результат становится недетерминированным и неповторяемым. Следовательно, &lt;strong&gt;логика модуля должна тестироваться в изоляции от внешних зависимостей и факторов&lt;/strong&gt;. Внешние зависимости заменяются искусственными сущностями, на которые можно повлиять. Приходим к идее использования &lt;a href=&quot;http://xunitpatterns.com/Fake%20Object.html&quot;&gt;fakes&lt;/a&gt;, в форме &lt;a href=&quot;http://martinfowler.com/articles/mocksArentStubs.html&quot;&gt;stubs and mocks&lt;/a&gt;.
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;Independent&lt;/strong&gt;
&lt;p&gt;
&lt;strong&gt;Выполнение тестов не должно быть зависимо друг от друга&lt;/strong&gt;, от порядка вызова тестов, от результата выполнения других тестов. Иначе их результаты не будут repeatable.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;Тест должен фокусироваться на одном аспекте работы модуля.&lt;/strong&gt; Не нужно стараться в одном тесте покрыть множество различных аспектов и проверить сразу все. Иначе это будет шагом назад в сторону интеграционных тестов.
&lt;/p&gt;

&lt;p&gt;
Идея состоит в том, чтобы &lt;strong&gt;в случае упавшего теста, можно было уверенно указать на проблему&lt;/strong&gt;, не прибегая к ручной отладке. В идеале для понимания проблемы должно быть достаточно взглянуть на название теста. Если не помогло, далее на код теста. Отсюда следует, что тест должен быть написан профессионально &lt;strong&gt;(professional)&lt;/strong&gt;, быть читабельным &lt;strong&gt;(readable)&lt;/strong&gt;, лаконичным, в написании теста должны применяться те же практики и принципы, что и при написании &lt;code&gt;production&lt;/code&gt; кода. Если и названия кода теста для понимания проблемы  недостаточно, то это весьма плохо, в этом случае приходится заглядывать в &lt;code&gt;code under test&lt;/code&gt;, либо что еще хуже, прибегать к &lt;code&gt;debugging&lt;/code&gt;.
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;Thorough&lt;/strong&gt;
&lt;/li&gt;
&lt;p&gt;
&lt;a href=&quot;http://en.wikipedia.org/wiki/Code_coverage&quot;&gt;Code coverage&lt;/a&gt; аспектов работы модулей должно стремиться к 100%.
&lt;/p&gt;


&lt;li&gt;
&lt;strong&gt;Maintainable&lt;/strong&gt;
&lt;/li&gt;
&lt;p&gt;
Так как уровень детализации тестов переместился на отдельные модули системы, то количество тестов значительно вырасло. А значит &lt;strong&gt;уровень &lt;code&gt;maintainability&lt;/code&gt; становится жизненно важным фактором&lt;/strong&gt;. В противном случае можно создать массу кода, покрыть ёё не меньшей массой тестов, радоваться 100% покрытию. А при добавлении новой функциональности либо изменении уже существующей, придется выкинуть написанные тесты и переписать заново, так как уровень их поддержки равен нулю. Нужно &lt;strong&gt;применять профессиональный подход в написании теста, обеспечивать &lt;code&gt;readability&lt;/code&gt;, &lt;code&gt;brevity&lt;/code&gt;, контролировать количество тестов, решать вопросы организации тестов, поддерживать их в актуальном состоянии.&lt;/strong&gt; Тесты, которые не поддерживаются в актуальном состоянии, становятся балластом, а от балласта рано или поздно избавляются. В таком случае написание тестов - это пустая трата времени и сил.
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;Fast&lt;/strong&gt;
&lt;p&gt;
Так как количество тестов потенциально велико, то они должны выполнятся быстро, иначе не будет возможным запускать их часто. Если есть тесты, которые выполняются медленно, то нужно выделить их в отдельную категорию. К примеру, быстрые тесты запускать на постоянной основе в процессе кодирования, медленные тесты запускать только перед коммитом изменений.
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;Automatic&lt;/strong&gt;
&lt;p&gt;
Весь набор тестов должен запускаться автоматически. Максимальные усилия, которые нужно приложить для запуска тестов, должны ограничиваться нажатием одной кнопки. В идеале запуск должен быть автоматизирован и интегрирован в процесс билда в рамках continuous integration процесса. 
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Professional&lt;/strong&gt;&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Readable&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;Benefits&lt;/h3&gt;
&lt;p&gt;
Проанализируем выгоды полученные от применения модульного тестирования.
&lt;ol&gt;
&lt;li&gt;
Набор тестов позволяет с большей долей уверенности гарантировать корректное поведение приложения.
&lt;/li&gt;
&lt;li&gt;
Набор тестов позволяет доказать, что код работает.
&lt;/li&gt;
&lt;li&gt;
Наличие постоянной обратной связи от системы. Последствия внесения изменений становятся предсказуемыми.
&lt;/li&gt;
&lt;li&gt;
Написание тестов поощряет и принуждает к хорошему дизайну системы.
&lt;/li&gt;
&lt;li&gt;
Облегчение регресионного тестирования, уменьшение количества дефектов в production коде.
&lt;/li&gt;
&lt;li&gt;
Тесты служат в качестве up-to-date документации кода модулей.
&lt;/li&gt;
&lt;li&gt;
Обретение чувства спокойствия, уверенности.
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;
При внесении изменений, сразу можно оценить последствия внесенных изменений, понять что и где сломалось и сломалось ли вообще. Не нужно для этого проводить многочасовой debugging session. Исчезает страх внесения изменения, страх сломать случайно что-то в другом конце системы. Появляется уверенность в корректной работе системы. Уменьшается количество дефектов в production коде. А если дефект все-таки обнаруживается, то нету чувства паники и беспокойства, взамен приходит чувство спокойствия и уверенности.
&lt;/p&gt;

&lt;p&gt;
Для обеспечения независимости и повторяемости теста, нужно чтобы модуль был спроектирован таким образом, чтобы допускать тестирование его логики в изоляции от других компонент. Таким образом поощряется применение &lt;a href=&quot;http://en.wikipedia.org/wiki/Solid_%28object-oriented_design%29&quot;&gt;SOLID&lt;/a&gt; принципов, &lt;a href=&quot;http://www.objectmentor.com/resources/articles/srp.pdf&quot;&gt;single responsibility principle&lt;/a&gt;, dependency isolation, &lt;a href=&quot;http://martinfowler.com/articles/injection.html&quot;&gt;dependency injection&lt;/a&gt;. В прошлой своей статье я описывал вопросы, посвященные &lt;a href=&quot;http://samoshkin.blogspot.com/2011/02/blog-post.html&quot;&gt;управлению зависимостями между модулями системы&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
Тесты, которые обладают описанными свойствами могут служить в качестве документации кода и поведения системы. Документация, внедренная в код модулей, зачастую устаревает, и при внесении изменений в модуль, документацию, например в виде комментариев, лень обновлять или нет времени. Так как тесты поддерживаются в актуальном состоянии, то такая документация никогда не устаревает и всегда находится up-to-date. В будущем, для того, чтобы понять что делает код, не нужно смотреть на код, достаточно посмотреть на тесты, а в идеале, лишь на названия тестов.
&lt;/p&gt;

&lt;h3&gt;Вопросы, на которые нет ответов&lt;/h3&gt;
&lt;p&gt;
При процессе применения unit-testing незамедлительно возникают такие вопросы.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Что тестировать?&lt;/li&gt;
&lt;li&gt;Что не тестировать?&lt;/li&gt;
&lt;li&gt;Каков должен быть уровень детализации тестов?&lt;/li&gt;
&lt;li&gt;Как именовать тесты?&lt;/li&gt; 
&lt;li&gt;С чего начинать написание тестов?&lt;/li&gt; 
&lt;li&gt;С чего начинать написание кода модулей?&lt;/li&gt; 
&lt;/ul&gt;

&lt;p&gt;
Модульное тестирование, будучи инструментом, а не  методологией, не в состоянии дать ответы на эти вопросы. Инструмент не знает сам по себе, как правильно он должен применяться. &lt;strong&gt;Ответы на эти вопросы дают уже такие методологии, как test-driven development, behavior-driven development.&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
Обычно тех, кто находится на этом этапе познания, можно охарактеризовать следующими утверждениями:
&lt;ul&gt;
&lt;li&gt;
Задаются вопросом, как можно писать тесты до того, как написан код модуля (test-first), и пытаются это делать.
&lt;/li&gt;
&lt;li&gt;
Количество тестов, уровень покрытия и уровень их детализации определяют интуитивно. Обычно это приводит к увеличению массы тестов и уменьшению их сопровождаемости.
&lt;/li&gt;
&lt;li&gt;
Думают, что то, чем они сейчас занимаются - это и есть test-driven development. Нужно заметить, что на это убеждение большое влияние оказывает сбивающее с толку название этой методологии, а именно, наличие слова &quot;test&quot;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;h2&gt;Test-first development&lt;/h2&gt;
&lt;p&gt;
Сюда переходят те, кто понял, что значит написание теста до того, как написан код. А поняв это, они понимают еще много других вещей, хотя возможно не сразу. Основные идеи таковы.
&lt;/p&gt;

&lt;p&gt;
Основное отличие от unit-testing практики состоит в том, что вместо концентрации на тестировании функциональности модуля, разработчик концентрируется на дизайне модуля и осмыслении его поведения до реализации логики модуля. Применение test-first подхода заставляет &lt;strong&gt;сначала задуматься над требованиями, осмыслить их, понять, затем преобразовать требования в набор тестов, и только затем приступить к реализации логики модуля.&lt;/strong&gt; Разработчик вынужден проводить глубокий анализ требований перед тем, как приступить к кодированию. Это уменьшает вероятность того, что модуль, будучи реализованным, потребует изменения из-за недопонимания требований.
&lt;/p&gt;

&lt;p&gt;
После того как требования ясны и поняты, они описываются в виде тестов. Код тестов становится первым клиентом кода модуля и вносит ясность в каких сценариях используется модуль и описывает различные варианты использования модуля. Вследствие этого &lt;strong&gt;дизайн модуля может быть спроектирован на основании конкретных вариантов, сценариев использования, требований, а не на основании абстрактных предположений разработчика.&lt;/strong&gt; Фокус смещается на проектирование дизайна модуля. Test-first подход позволяет создавать удобный для использования API модулей.
&lt;/p&gt;

&lt;p&gt;
Test-first подход поощряет такой важный принцип, как &lt;a href=&quot;http://henko.net/imperfection/simplicity-in-software-design/&quot;&gt;simplicity&lt;/a&gt;. &lt;strong&gt;Нужно написать ровно столько кода модуля, чтобы  выполнились все тесты&lt;/strong&gt;, а так как тесты - это воплощение требований к модулю, то выполнение всех тестов - означает, что модуль удовлетворяет всем предъявленным к нему требованиям. Это позволяет не удариться в over-engineering и поощряет принципы &lt;a href=&quot;http://en.wikipedia.org/wiki/KISS_Principle&quot;&gt;KISS&lt;/a&gt; (keep it simple, stupid) и &lt;a href=&quot;http://en.wikipedia.org/wiki/You_ain%27t_gonna_need_it&quot;&gt;YAGNI&lt;/a&gt; (You ain&#39;t gonna need it), то есть делать только то, что действительно нужно в данный момент и делать это максимально просто.
&lt;/p&gt;

&lt;p&gt;
Если подытожить, то &lt;strong&gt;test-first подход смещает фокус от верификации корректного функционирования кода в сторону осмысления требований и поведения, построения дизайна системы&lt;/strong&gt;.
&lt;/p&gt;

&lt;h2&gt;Test-driven development&lt;/h2&gt;
&lt;p&gt;
TDD базируется на TFD практике. Нельзя сказать, что Вы используете TDD, если не практикуете TFD, то есть пишете тесте после того как модул и спроектированы и реализованы. В таком случае - это просто написание модульных тестов, ставящее перед собою целью тестирование функциональности.
&lt;/p&gt;

&lt;p&gt;
TDD не является техникой тестирования, это скорее development техника, которая фокусируется на представлении требований и поведения системы в виде тестов и хорошем дизайне системы. Целью теста перестает быть собственно верификация корректного функционирования кода, вместо это тест стает утверждением требования, предъявляемого к системе, и ее поведения. Верификация функциональности становится побочным эффектом написания теста, хотя и очень важным.
&lt;/p&gt;

&lt;h3&gt;Changes in development process &lt;/h3&gt;
&lt;p&gt;
Основное отличие TDD от TFD состоит в том, что TDD привносит изменения в процесс разработки. Грубо говоря:
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;TDD = TFD + Changes in development process.&lt;/strong&gt;
&lt;/p&gt; 
&lt;p&gt;
Хотя используя TFD в полном объеме, процесс разработки меняется так или иначе. Так что надо заметить, что разделение TDD и TFD здесь несколько искусственно. 
&lt;/p&gt;

&lt;img src = &quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyoy5byeTn31zftadgSyr-fdrVApQ-qvn64m_Nb0hmNxpuOoyT3tWTrVRtSMua5_RktchpMPnHrTmgN6g52SUN0raXSqSnFcR7vUrIYUaaA52G-SIcbODd0PqRfe9FjJhHeR-HHKdt-QE/s800/Test-driven_development.PNG&quot;&gt;
&lt;/img&gt;

&lt;p&gt;
Проанализируем этапы, приведенные на диаграмме:
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
Собрать и проработать набор требований.
&lt;/li&gt;
&lt;li&gt;
Написать тест, отражающий одно из требований.
&lt;br/&gt;
Ни единой строчки кода реализации еще не написано. По мере написании тестов начинает вырисовываться API модуля. К концу написания тестов API модуля готов настолько, насколько нужно, чтобы код скомпилировался.
&lt;/li&gt;
&lt;li&gt; 
Убедиться что тесты падают.
&lt;/li&gt;
&lt;li&gt;
Написать код модуля.
&lt;/li&gt;
&lt;li&gt; 
Запустить тесты, убедиться что они выполняются, иначе вернуться к предыдущему пункту.
&lt;/li&gt;
&lt;li&gt;
Провести рефакторинг кода.
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;Место рефакторинга в TDD&lt;/h3&gt;
&lt;p&gt;
&lt;strong&gt;Рефакторинг проводится после того, как тесты написаны и выполняются.&lt;/strong&gt; Это значит, что модуль не проектируется и не реализуется сразу красивым и чистым. Вместо этого, API проектируется настолько хорошо, чтобы можно было предъявить к нему требования в виде тестов, а реализация пишется настолько хорошо, чтобы эти тесты выполнялись. А далее начинается процесс рефакторинга, который причесывает дизайн и реализацию. 
&lt;/p&gt;

&lt;p&gt;
Место рефакторинга в этой схеме не случайно. Что понять почему все именно так, нужно вспомнить что такое рефакторинг. Правильно, это такое изменение внутренней реализации, которое не меняет поведение. Главное здесь - это фраза &quot;не меняет поведения&quot;. А что представляет из себя тест в TDD? Декларацию поведения. Из этого следует, что рефакторинг не должен потребовать изменение тестов. Розумеется, рефакторинг API модуля потребует внести соответствующие изменения в реализацию теста, но лишь в реализацию, декларация поведения останется неизменной. &lt;strong&gt;Основная функция тестов во время проведения рефакторинга - проверка того, что его проведение не изменило поведения.&lt;/strong&gt;
&lt;/p&gt;

&lt;h3&gt;TDD essential&lt;/h3&gt;

&lt;p&gt;
&lt;strong&gt;Еще одна функция тестов в TDD - это мерило выполненной работы.&lt;/strong&gt; Тесты, будучи декларацией поведения, представляют business value. Количество успешно выполняющихся тестов относительно общего их количества представляет процент выполненной работы. И когда Вам задают вопрос &quot;Сколько работы сделано?&quot;, &quot;Сколько еще осталось сделать?&quot;, &quot;Когда будет закончена задача?&quot;, то можно больше не мычать, а достаточно взглянуть на test report, оценить количество выполняюших тестов, и дать конкретный ответ.
&lt;/p&gt;


&lt;p&gt;
Очень важно понять, что &lt;strong&gt;изначальная функция тестирования, как верификации корректного функционирования модуля, отошла на второй план. Ее место заняла функция декларации требований и поведения, и верификация поведения.&lt;/strong&gt; Нужен shift in mind, чтобы понять это, и что еще важнее, чтобы осознать и принять это, начать использовать и пожинать плоды.
&lt;/p&gt;

&lt;h3&gt;Вопросы, на которые есть ответы&lt;/h3&gt;
&lt;p&gt;
После того, как Вы приняли эту идею, можно получить ответы на те вопросы, которые ранее были неизвестны.
&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Что тестировать?, Что не тестировать?&lt;/strong&gt;
&lt;p&gt;
Вспомнив, что цель теста - не тестирование, а декларация поведения, вопрос отпадает сам собой. TDD не занимается тестированием, значит вопросы &quot;Что тестировать?&quot; и &quot;Что не тестировать?&quot; просто неуместны. Если задать вопрос &quot;Какое поведение описывать?&quot;, то ответ на этот вопрос понятен: &quot;Поведение определяется требованиями и становится известно после анализа.&quot;. Behavior driven development уточняет ответ на этот вопрос еще больше.
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;Как именовать тесты?&lt;/strong&gt;
&lt;br /&gt;
&lt;p&gt;
Именование теста должно отражать поведение, которое он описывает. Глядя на название теста, можно назвать и понять описанный аспект поведения, не заглядывая в код теста. 
&lt;/p&gt;

&lt;pre class = &quot;brush: csharp&quot;&gt;
[Test]
public void When_data_ordered_in_asc_order_should_be_able_to_sort_in_desc_order()
{
    var sorter = new QuickSortAlgorithm();
    var sortedList = sorter.Sort(new[] { 1, 2, 3, 4, 5, 6, 7 }, false);

    VerifyEquality(sortedList, new[] { 7, 6, 5, 4, 3, 2, 1 });
}
&lt;/pre&gt;

&lt;p&gt;
Обобщим это утверждение на группу тестов определенного модуля. В идеале, &lt;strong&gt;набор названий тестов должен позволять воссоздать модуль и его реализацию заново&lt;/strong&gt;. Множество тестов полно и недвусмысленно описывает поведение модуля, и этого должно быть достаточно для воссоздания модуля, как если бы мы потеряли его код, но остались бы тесты.
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;С чего начинать написание тестов?&lt;/strong&gt;
&lt;p&gt;
Выделить наиболее важное поведение и выразить его в виде теста. Двигаться далее в порядке важности.
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;С чего начинать написание production кода?&lt;/strong&gt;
&lt;p&gt;
К концу написания набора тестов, API модуля готов. Далее берем тест, отражающий наиболее важное поведение, и пишем production code до тех пор, пока тест не начинает выполняться успешно. Далее, переходим к следующему требованию в порядке важности.
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;Why TEST-driven development?&lt;/h3&gt;

&lt;p&gt;
&quot;А почему TDD называется так как он называется? Что там делает слово test?&quot;. Если у Вас возник этот вопрос, значит Вы постигли TDD, и для вас TDD перестало быть еще одним &lt;a href=&quot;http://en.wikipedia.org/wiki/Buzzword&quot;&gt;buzzword&lt;/a&gt; в software development. Слово &quot;test&quot; в названии только сбивает с толку, и вносит  неопределенность и путаницу. Слово &lt;strong&gt;&quot;behavior&quot;&lt;/strong&gt; было бы более уместно.
&lt;/p&gt;

&lt;h2&gt;Behavior-driven development&lt;/h2&gt;

&lt;p&gt;
По правде говоря, правильней было бы TDD переименовать в BDD и на этом успокоиться. Однако понятие BDD - это не просто синоним TDD. BDD базируется на TDD, расширя его, и еще более акцентируя внимание на первичности поведения.
&lt;/p&gt;

&lt;h3&gt;
BDD масштабирует принципы TDD на фазы анализа требования, дизайна и реализации.
&lt;/h3&gt;

&lt;p&gt;
Что это значит? Попробую объяснить. Простите мой paint.
&lt;/p&gt;

&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtaIbFJk7FfrHF8zsjA6Czdj-jcO4mjg3yrC1tTjosHfUBBW_hxAeuM70swsgIweRhSOCi66ybe9kitFwmyUsvkhqypPoC0_jvLpFrJQfH8oZ-FgJQnsxeqd79ASYNbKVPo0rlXPUlnKY/s800/download.png&quot;&gt;

&lt;p&gt;
TDD предполагает, что разработчик занимается спецификацией поведения системы в виде набора тестов, что позволяет сформировать дизайн, после чего можно перейти к этапу реализации логики. Также предполагается, что требования уже известны на нужном уровне детализации (скажем отдельного класса). Однако эта информация не является первичной. &lt;strong&gt;Требования к системе уже описаны заказчиком до того, как разработчик приступил к кодированию.&lt;/strong&gt; 
&lt;/p&gt;

&lt;p&gt;
В TDD единственная роль - это разработчик. ТDD сфокусирован на этапах дизайна системы и ее реализации. BDD помимо фазы дизайна и реализации, охватывает также фазу анализа требований и вопросы коммуникации, устанавливает связь между top-level требованиями уровня заказчика, и low-level набора поведений, которыми оперирует тестировщик либо разработчик. Другими словами, &lt;strong&gt;BDD расширяет методологию анализа требований и спецификации поведения на весь процесс и команду и унифицирует ёё.&lt;/strong&gt; 
&lt;/p&gt;

&lt;p&gt;
И заказчик, и тестировщик, и разработчик занимаются описанием требований и поведения системы на разных уровнях ёё детализации. Другие участники процесса разработки эти требования потребляют. Чтобы добиться связи между требованиями и поведениями, сформированных разными участиками, на различном уровне, в различные моменты времени, и обеспечить коммуникацию участников процесса, применяется единый язык анализа и формализации требований и поведения. Единый язык поощряет взаимодействие членов команды в процессе сбора и понимания требований.
&lt;/p&gt;

&lt;h3&gt;
Унификация языка анализа требований
&lt;/h3&gt;

&lt;p&gt;
Требования заказчика выражаются в виде историй. BDD унифицирует механизм описания истории, определяя такие структурные элементы:
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Feature/Goal&lt;/strong&gt;, то есть та функциональная возможность, которая по тем или иным причинам нужна заказчику.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role&lt;/strong&gt;, субъект системы, которому необходима эта возможность, тот, кто будет использовать feature.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benefit/motivation&lt;/strong&gt;. Выгода, полученная от использовании feature, а также мотивация заказчика, которая объясняет, почему возникла необходимость в feature. Мотивация фокусируются на первичной цели, обеспечивает более глубокое понимание потребностей заказчика в целом, и направляет анализ требований в нужное русло.
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;
Использование этих элементов ничуть не ограничивает нас в описании требований, а лишь слегка их формализует, определяя что-то вроде довольно свободного и гибкого шаблона. Конкретный пример шаблон может быть таковым: &lt;strong&gt;&quot;As a [role] I want [feature] so that [benefit]&quot;&lt;/strong&gt;. Главное здесь конечно же не wording или то, как построено предложение. 
&lt;/p&gt;

&lt;p&gt;
Сформированная история сохраняется в backlog&#39;e до тех пор, пока не настанет пора ее воплощения в жизнь. C наступлением этого момента проводится сбор и анализ требований, обсуждения как внутри команды, так и с заказчиком, оценивается объем работы. История и набор собраной информации в целом определяют поведение, которое формализуется в виде набора критериев приемки (&lt;strong&gt;acceptance criteria&lt;/strong&gt;). Удовлетворение всех критериев приемки со стороны системы означает корректность поведения системы. 
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;Acceptance criteria - это спецификация поведения.&lt;/strong&gt; Язык его описания должен быть гибким и свободным, и не ограничиваться применением в одной или нескольких предметных областях. BDD предлагает использование следующих элементов:
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Given&lt;/strong&gt; initial context, определяет исходный контекст, сценарий.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When&lt;/strong&gt; event occurs, определяет совершаемое действие.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then&lt;/strong&gt; ensure outcomes, верифицирует последствия совершенного действия.
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;h3&gt;
Making acceptance criteria executable
&lt;/h3&gt;

&lt;p&gt;
Имея в качестве исходной информации набор спецификаций поведения (acceptance criteria), разработчик может начать переносить их в код, то есть превращать их в форму выполняемых спецификаций (executable specification). Розумеется, это будут не юнит-тесты. Спецификации служат в качестве starting point для начала работы. По мере кодирования спецификаций формируется дизайн системы. Для модулей, появившихся в результате этого дизайна, определяются дочернии истории и спецификация поведения этих модулей, которые в свою очередь тоже переносятся в код. Процесс итеративно продолжается до тех пор, пока дальнейшее разбиение модулей станет нецелесообразным. В таком случае, спецификации нижнего уровня будут представлять собой юнит-тесты, а более высоких - интеграционные тесты, если проведение параллели с тестами здесь вообще уместна.
&lt;/p&gt;

&lt;p&gt;
Если обобщить сказанное выше, то &lt;strong&gt;BDD подготавливает информацию, которая используется как исходная для применения TDD&lt;/strong&gt;.
&lt;/p&gt;

&lt;p&gt;
Вот что пишет по этому поводу Scott Bellware в &lt;a href=&quot;http://www.code-magazine.com/article.aspx?quickid=0805061&amp;page=1&quot;&gt;своей статье&lt;/a&gt;.
&lt;blockquote&gt;
&lt;p&gt;
Usually, the acceptance criteria are taken directly from the form written by the customer in the customer&#39;s comforable language, and translated directly into code by the developer who uses the customer&#39;s specified criteria and any notes that he&#39;s made in the conversation with the customer to get into any greater details.
&lt;/p&gt;

&lt;p&gt;
You aren&#39;t trying to achieve seamless traceability of requirements with user stories and acceptance criteria. You&#39;re capturing just enough specification to get the work started.
&lt;/p&gt;

&lt;p&gt;
Any new criteria that surface in the process of elaborating on a user story in code are captured directly in code, and can be communicated back to the customer later if necessary.
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/p&gt;

&lt;h3&gt;
Describing acceptance criteria in natural language
&lt;/h3&gt;

&lt;p&gt;
Acceptance criteria пишутся людьми на простом английском языке. Будучи написанными, они переносятся в код при помощи unit testing либо specification framework. &lt;strong&gt;При переносе в код, язык описания спецификации не изменяется, используются те же языковые элементы такие, как given, when, then.&lt;/strong&gt; Также применяется словарь предметной области, полученный в результате &lt;a href=&quot;http://domaindrivendesign.org/resources/what_is_ddd&quot;&gt;domain driven design&lt;/a&gt;. 
&lt;/p&gt;

&lt;p&gt;
К примеру, поведениt калькуляции расходов на доставку заказов, описанная при помощи &lt;a href=&quot;https://github.com/machine/machine.specifications&quot;&gt;MSpec&lt;/a&gt;. 

&lt;pre class=&quot;brush:csharp&quot;&gt;
[Subject(typeof(ShippingTablePostageCalculator))]
public class when_table_contains_record_with_matching_mail_class_and_distance 
    : given_shipping_table_postage_calculator
{
    static Money PostagePrice;

    Establish postage_calculator = () =&gt; WithShippingTable(
        new PostageRecord {MailClass = &quot;FIRST_CLASS_MAIL&quot;, Distance = 1.00, Price = new Money(1.0m, &quot;USD&quot;)}, 
        new PostageRecord {MailClass = &quot;FIRST_CLASS_MAIL&quot;, Distance = 2.00, Price = new Money(2.0m, &quot;USD&quot;)},
        new PostageRecord {MailClass = &quot;PRIORITY_MAIL&quot;, Distance = 1.00, Price = new Money(5.0m, &quot;USD&quot;)});

    Because of = () =&gt; { PostagePrice = Calculator.GetPostagePrice(&quot;FIRST_CLASS_MAIL&quot;, 1.00); };

    It should_get_postage_price_from_the_that_record = 
        () =&gt; PostagePrice.ShouldEqual(new Money(1.0m, &quot;USD&quot;));
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;span style=&quot;color: red&quot;&gt;&lt;strong&gt;Что за WTF!!??? Это что такое!!???&lt;/strong&gt;&lt;/span&gt; Если это было вашей первой мыслью при просмотре этого кодf, то могу сказать, что я подумал бы то же самое на Вашем месте. И не спрашивайте меня: &lt;strong&gt;&quot;А что, для каждого теста нужно создавать новый класс?&quot;&lt;/strong&gt;. Не будем сейчас углубляться в детали реализации конктреного framework. Это уже тема для отдельного поста.
&lt;/p&gt;

&lt;p&gt;
С точки зрения написания теста (по уровню детализации в примере это таки юнит-тест) произошли следующие изменения по сравнению с TDD. Произошел переход от понятия теста к понятию спецификации, как элемента описания и верификации поведения. &lt;strong&gt;Поведение декларируется не только в имени теста, но и в его теле&lt;/strong&gt;. Тело теста структурировано таким же образом, как и acceptance criteria, который оно представляет. Слово тест больше нигде не упоминается, ни в названии, ни в элементах framework, нету атрибутов Test, TestFixture, нету конструкций типа Assert.AreEqual(), на их место пришли поняти given/when/then(should). 
&lt;/p&gt;

&lt;p&gt;
Обощив, можно сказать, что BDD/specification framework не позволит писать тесты/спецификации и не быть ориентированным на поведение, она не просто поощряет, она принуждает отталкиваться от поведения. Это противоположно тому, как можно было писать тесты при помощи unit-testing framework не до, а после написания кода модулей, и говорить что вы занимаетесь test-driven development&#39;ом. BDD буквально пропитывает понятием &quot;поведения&quot; все, начиная от анализа требований и заканчивая тестированием. &lt;strong&gt;Явная фокусировка на поведение - это то в чем TDD испытывает недостаток.&lt;/strong&gt;
&lt;/p&gt;

&lt;h3&gt;
И напоследок...
&lt;/h3&gt;
&lt;p&gt;
Если BDD кажется Вам туманной материей, или Вас часто посещали вопросы типа &quot;WTF?&quot; при прочтении последней главы, то это нормально. Что касается меня, то хотя идея BDD мне и понятна, однако остается довольно много вопросов практического применения, на которые у меня пока нет ответов. В любом случае, думаю не стоит быть категоричным и отклонять BDD, только потому что класс с названием &lt;code&gt;&quot;when_table_contains_record_with_matching_mail_class_and_distance&quot;&lt;/code&gt; выглядит ну по меньшей мере странно.
&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;
Также, стоит задать себе вопрос, на самом ли деле мы практикуем test-driven development, применяя практику юнит тестирование? Или мы занимаемся лишь покрытием кода модульными тестами, используя при этом unit testing и mocking frameworks? Иногда я ловлю себя на этом.
&lt;/p&gt;

&lt;h2&gt;Полезные ссылки&lt;/h2&gt;
&lt;p&gt;
&lt;strong&gt;Книги по unit-testing&lt;/strong&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;http://artofunittesting.com/&quot;&gt;The art of unit testing&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://www.amazon.com/Pragmatic-Unit-Testing-NUnit-Programmers/dp/0974514020&quot;&gt;Pragmatic unit testing&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;
&lt;strong&gt;Test driven development&lt;/strong&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;http://geekswithblogs.net/thomasweller/archive/2009/11/03/tdd-is-not-about-testing-its-about-how-we-develop.aspx&quot;&gt;TDD is not about testing, it&#39;s about how we develop software&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://www.slideshare.net/jonkruger/testdriven-development-in-action-4884152&quot;&gt;Презентация &quot;TDD in action&quot;&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://techniquesofdesign.com/2011/01/12/the-tdd-zealot/&quot;&gt;The TDD Zealot&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://blog.jtimothyking.com/2006/07/11/twelve-benefits-of-writing-unit-tests-first?t=xp&quot;&gt;Twelve Benefits of Writing Unit Tests First&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
&lt;strong&gt;Behavior driven development&lt;/strong&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;http://dannorth.net/introducing-bdd/&quot;&gt;Introducing BDD by Dan North&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://www.code-magazine.com/article.aspx?quickid=0805061&amp;page=1&quot;&gt;Behavior-Driven Development&lt;/a&gt;. Статья must-read.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://en.wikipedia.org/wiki/Behavior_driven_development&quot;&gt;BDD on wiki&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://haacked.com/archive/2008/08/24/introducing-subspec.aspx&quot;&gt;Streamlined BDD Using SubSpec for xUnit.NET&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://dannorth.net/whats-in-a-story/&quot;&gt;What&#39;s in a story&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://blog.wekeroad.com/blog/make-bdd-your-bff-2&quot;&gt;Здесь&lt;/a&gt; и &lt;a href=&quot;http://elegantcode.com/2010/03/01/an-evolution-of-test-specification-styles-my-journey-to-mspec/&quot;&gt;здесь&lt;/a&gt; уже с упором на использования конкретного framework&#39;a.
&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/5175459504163921508/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/03/testing-application-behavior-evolution.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/5175459504163921508'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/5175459504163921508'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/03/testing-application-behavior-evolution.html' title='Тестирование поведения приложений: эволюция подходов. От debugging к unit testing, TDD и BDD.'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyoy5byeTn31zftadgSyr-fdrVApQ-qvn64m_Nb0hmNxpuOoyT3tWTrVRtSMua5_RktchpMPnHrTmgN6g52SUN0raXSqSnFcR7vUrIYUaaA52G-SIcbODd0PqRfe9FjJhHeR-HHKdt-QE/s72-c/Test-driven_development.PNG" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-3600940394400118646</id><published>2011-02-28T05:13:00.006+02:00</published><updated>2011-09-29T19:41:06.076+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DI/IoC"/><title type='text'>Управление зависимостями между компонентами в приложении</title><content type='html'>&lt;p&gt;
Статья посвящена управлению зависимостями между компонентами в приложении, рассматриваются такие концепции как &lt;a href=&quot;http://en.wikipedia.org/wiki/Dependency_injection&quot;&gt;Dependency Injection&lt;/a&gt;, &lt;a href=&quot;http://martinfowler.com/articles/injection.html&quot;&gt;Investion of Control container&lt;/a&gt;, &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ff921142.aspx&quot;&gt;Service Locator&lt;/a&gt;. Рассматривается существующий подход к управлению зависимостями между компонентами в приложении, анализируются проблемы которые несет в себе этот подход. Далее, путем последовательных шагов делается попытка решить эти проблемы, в результате чего приходим к таким концепциям как &lt;code&gt;Service Locator&lt;/code&gt;, &lt;code&gt;Dependency Injection&lt;/code&gt;, &lt;code&gt;Inversion of Control Container&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
Основной упор в статье делается на вопросы &lt;strong&gt;&quot;Зачем?&quot;&lt;/strong&gt;, &lt;strong&gt;&quot;Почему?&quot;&lt;/strong&gt;, а не &quot;Как?&quot;.  
Обычно этим вопросам не уделяется должного внимания, и упор делается на детали реализации, однако понимание глубинных причин использования тех или иных вещей критично для их правильного и своевременного употребления.
&lt;/p&gt;

&lt;p&gt;
Статья ориентирована на разработчиков, которые:
&lt;ul&gt;
&lt;li&gt;
не слышали об &lt;code&gt;DI/IoC&lt;/code&gt; и хотят понять зачем нужно использовать эти новинки, если и &quot;так все хорошо&quot;.
&lt;/li&gt;
&lt;li&gt;
знают что это такое, но пока не использовали в реальных приложения, и хотели бы более глубже понять необходимость использования &lt;code&gt;DI/IoC&lt;/code&gt;, и какие преимущества несет эта концепция.
&lt;/li&gt;
&lt;li&gt;
те кто, используют &lt;code&gt;DI/IoC&lt;/code&gt;, но хотели бы лучше понять какие проблемы решает &lt;code&gt;DI/IoC&lt;/code&gt; и какие benefits несет.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;
Содержание:
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#dependency-management-question&quot;&gt;Вопрос управления зависимостями между компонентами в приложении&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#dependency-management-in-application&quot;&gt;Классический подход к управлению зависимостями внутри компонент приложения&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#classic-way-problem-analysis&quot;&gt;Анализ проблем&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#service-locator&quot;&gt;Использование Service Locator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#di-ioc&quot;&gt;Использование Dependency Injection / Inversion of control container&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#di-options&quot;&gt;Dependency injection options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#component-lifecycle-and-lifestyle&quot;&gt;Lifecycle and lifestyle explained&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#service-locator-vs-di-ioc&quot;&gt;Service Locator vs DI/IoC Container&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#common-service-locator&quot;&gt;Common service locator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#ioc-container-implementation&quot;&gt;Реализации IoC container&#39;ов&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#resources&quot;&gt;Полезные ресурсы&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;

&lt;h2&gt;
&lt;a name=&quot;dependency-management-question&quot;&gt;Вопрос управления зависимостями между компонентами в приложении&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;
Объектно-ориентированный подход предписывает использовать объект как единицу повторного использования, а приложение строить в виде набора компонент, которые объединяются в систему, между которыми устанавливаются различного рода связи и зависимости, и как результат, эта структура позволяет реализовать определенные use case&#39;ы. С эволюцией приложения количество компонент растет, а вместе с ними растет и количество зависимостей между ними, таким образом структура приложения усложняется, и ей становится сложно управлять. 
&lt;/p&gt;

&lt;/p&gt;
Чтобы не быть голословным, в качестве примера рассмотрим гипотетическую cистему упаковки заказов для дальнейшей отправки их конечному пользователю интернет-магазина, который специализируется на электронной технике.

&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYcUcw8MDJ6uvcIKT_yTwpDyKX5XSg7LSzXji1bKlDm9aYI5gmyMayq2mcnwulrBSgqH8Wh6uqZQLMnbtc7Ozfeng2yNwMJZX82JpKbhrhGFpLqMEsB5B-ZgtdAO_u8psbkGrdQPqWd1k/s800/dependency-graph.png&quot; alt=&quot;Component Structure&quot;&gt;
&lt;/img&gt;

Описание некоторых компонент:
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ShippingMethodCapabilities&lt;/code&gt;&lt;/strong&gt; - позволяет определить может ли упакованный заказ быть отправлен конкретной службой доставки, в качестве критериев могут выступать размер, вес, содержимое упаковки, тип тары.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IPostalService&lt;/code&gt;&lt;/strong&gt; - адаптер к сервису служб доставки (USPS, FedEx, UPS), знает как разговаривать с этими сервисами, отправляет им запросы на получение различной информации и получает от них ответы.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IPackingAlgorithm&lt;/code&gt;&lt;/strong&gt; - представляет алгоритм упаковки предметов заказа. Различные алгоритмы смоделированы в виде таких классов, как &lt;code&gt;TightPackingAlgorithm&lt;/code&gt;, &lt;code&gt;SafePackingAlgorithm&lt;/code&gt;, &lt;code&gt;TaggedPackingAlgorithm&lt;/code&gt;, &lt;code&gt;WeightLimitedPackingAlgorithm&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IBox&lt;/code&gt;&lt;/strong&gt; - предоставляет разные тип упаковки (в нашем примере: Small, Medium, Large).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DeviceWeightCalculator&lt;/code&gt;&lt;/strong&gt; - считает вес контейнера, включая его содержимое, либо отдельно вес каждого устройства.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;
Предположим, разработчик воплотил в жизнь &lt;a href=&quot;http://en.wikipedia.org/wiki/Single_responsibility_principle&quot;&gt;Single Responsibility Principle&lt;/a&gt; и спроектировал компоненты таким образом, что каждый занимается своим делом и делает это хорошо, и не знает больше чем ему это надо. Но редкий компонент живет в изоляции, он использует другие компоненты (зависимости), те в свою очередь используют свои, и т.д. Выстраивается структура приложения, причем структура эта далеко не статична, она динамична, она изменчива в зависимости от сценария использования приложения. На то компоненты и являются еденицами повторного использования, чтоб их использовать в разных сценариях. 
&lt;/p&gt;

&lt;p&gt;
В таком случае возникает необходимость помимо реализации &quot;полезной&quot; логики компонент, еще заниматься и управлением зависимостями между компонентами, чтобы построить структуру приложения. В понятие управления зависимостями входят такие активности:
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Определение нужных реализаций компонент исходя из некой логики.&lt;/strong&gt; В нашем примере, это может быть выбор нужной реализации &lt;code&gt;IPackingAlgorithm&lt;/code&gt; в зависимости от настроек клиента либо специфики заказа.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Управление временем жизни зависимостей, а именно создание, уничтожение зависимостей в нужный момент времени.&lt;/strong&gt; Для этого может потребоваться где-то хранить созданные компоненты как для использования, так и для последующего уничтожения.
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;
И тут разработчик сталкивается с вопросом: &lt;strong&gt;&quot;Где в приложении разместить всю эту логику управления зависимостей?&quot;&lt;/strong&gt;. И это важнее, чем вопрос: &quot;Как реализовать эту логику?&quot;. Это и есть вопрос управления зависимостями. Далее будут рассматриваться возможные варианты решения этого вопроса.
&lt;/p&gt;

&lt;h2&gt;&lt;a name=&quot;dependency-management-in-application&quot;&gt;Управление зависимостями внутри компонент приложения&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;
Если особо не задумываться над поставленным вопросом, то на первый взгляд кажется, что управлять зависимостями должен тот компонент, которому собственно и нужны эти зависимости. Такой подхож кажется даже логичным. Получаем что-то в этом духе.
&lt;pre class=&quot;brush: csharp&quot;&gt;
public class DevicePacker
{
    private IPackingAlgorithm _algorithm;
    private ShippingMethodCapabilities _shippingMethodCapabilities;
    private BoxTracker_itemTracker;

    public DevicePacker()
    {
        // creation of required dependencies,  dependency managing logic
        _algorithm = new WeightLimitedPackingAlgorithm();
        _shippingMethodCapabilities = new ShippingMethodCapabilities(
            new FedexPostalService());
        _itemTracker = new BoxTracker();
    }

    public Package PackOrder(Order order)
    {
        //component own useful logic
        throw new NotImplementedException();
    }
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;classic-way-problem-analysis&quot;&gt;Анализ проблем этого подхода.&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Проанализируем различные use case, в которых может использоваться компонент приложения, и как это повлияет на логику управления зависимостей внутри этого компонента.
&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;
Определение типа нужной зависимости может зависеть не только от внутренней логики компонента, а и от внешних для компонента факторов.
&lt;/strong&gt; 

&lt;p&gt;
Как результат, компоненту приходится передавать лишние знания, чтобы он смог создать все нужные ему зависимости. Нарушается распределение знаний в системе, усложняется статическая структура приложения, потоки данных в системе.
&lt;/p&gt;

&lt;p&gt;
Для нашего примера это может означать, что правила упаковки для разных заказчиков могут предписывать использовать разные типы упаковк. А &lt;code&gt;WeightLimitedPackingAlgorithm &lt;/code&gt;, который использует &lt;code&gt;IBox&lt;/code&gt; не располагает знаниями, чтобы принять решение какую реализацию выбрать. В итоге приходится внутрь компонента передавать лишние данные для того, чтобы он мог создать все нужные ему зависимости. Стоит отметить, что лишние данные передаются через несколько слоев приложения, несмотря на то, что промежуточным слоям они вовсе не нужны. Смотрим код.
&lt;pre class=&quot;brush: csharp&quot;&gt;
public class WeightLimitedPackingAlgorithm : IPackingAlgorithm
{
    private ItemInfoProvider _infoProvider;
    public WeightLimitedPackingAlgorithm(PackagingRuleProvider rules, Client client)
    {
        // this class does not use PackagingRuleProvider and Client at all
        // only pass them to the deeper layer
        _boxProvider = new BoxProvider(rules, client);
    }
}

public class BoxProvider
{
    private IList&amp;lt;IBox&amp;gt; _itemPackers;

    public ItemInfoProvider(
        PackagingRuleProvider packagingRuleProvider,
        Client client)
    {
        //composite dependency creation logic which depends on outer context data
        if(client.IsVIPClient &amp;&amp; packagingRuleProvider.DoesClientSupportBigBoxes)
        {
            _boxes.Add(new BixBox());
        }
        else if(packagingRuleProvider.GetBoxWeightLimit &lt; 20.Ounces())
        {
            _boxes.Add(new MediumBox());
        }
        else
        {
            _boxes.Add(new SmallBox());
        }
    }
}
&lt;/pre&gt;
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;
Определение типа зависимости зависит от сценария использования либо от режима работы.
&lt;/strong&gt;

&lt;p&gt;
Компонент вынужден знать о конкретном use case, в рамках которого он выполняется, и учитывать это при разрешении зависимостей. В итоге теряется гибкость и возможность повторного использования компонента в других use case&#39;ах. Также, при добавлении нового либо удалении старого use case&#39;a приходится обновлять все компоненты, которые знают о них.
&lt;/p&gt;

&lt;p&gt;
К примеру, на production environment правила упаковки хранятся в базе данных, а на test environment - в XML файле, который расположен локально на файловой системе. В таком случае конкретная реализация IRuleRepository будет варьироваться.
&lt;pre class = &quot;brush: csharp&quot;&gt;
    public DevicePacker(Client client)
    {
        // dependency resolve depends on deployment scenario
        if(Configuration.IsTestMode)
        {
            var packagingRuleProvider = new XmlRuleProvider();
        }else
        {
            var packagingRuleProvider = new SqlRuleProvider();
        }
    }
&lt;/pre&gt;
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;
Невозможность покрытия компонентов юнит тестами, которые бы верифицировали определенный аспект работы компонента в изоляции от его зависимостей.
&lt;/strong&gt;

&lt;p&gt;
Если компонент сам создает необходимые ему зависимости, то заменить эти зависимости на &quot;fakes&quot; в целях тестирования становится невозможным. В итоге можно прийти к такому workaround, то есть делать инъекцию зависимостей через read-write свойства.
&lt;pre class = &quot;brush: csharp&quot;&gt;
[TestFixture]
public class TestPackagingCreator
{
    [Test]
    public void Should_pack_order_with_two_discs_in_single_mailer_if_rules_are_configured_as_not_vip()
    {
        var devicePacker= MockRepository.GenerateMock&amp;lt;DevicePacker&amp;gt;();
        var packagingRules = MockRepository.GenerateMock&amp;lt;PackagingRuleProvider&amp;gt;();

        // how to inject fake PackagingRuleProvider into DevicePacker instance???
        // let&#39;s expose dependency as read-write property and inject through it
        devicePacker.Stub(pc =&gt; pc.Rules).Return(packagingRules);
    }
}
&lt;/pre&gt;
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;
Если компонент управляет временем жизни своих зависимостей, то он должен в какой-то момент времени уничтожать экземпляры созданных им зависимостей.
&lt;/strong&gt;

&lt;p&gt;
Логика компонента снова же усложняется и помимо &quot;полезной&quot; логики, нужно &quot;keep in mind&quot; корректно завершить жизненный цикл созданный им объектов.
&lt;pre class = &quot;brush: csharp&quot;&gt;
    public Package PackOrder(Order order)
    { 
       // create WeightCalculator instance with lifetime scope per order packing      
       // and destroy it after order is packed
       using(var DeviceWeightCalculator = new DeviceWeightCalculator())
       {
            var package = _algorithm.PackOrder(order);
            var weight = weightCalculator.CalculateWeight(package);
            if(_shippingMethodCapabilities.DoesExceeds(weight))
            {
                // do something, for example, divide order in different packages
            }
            return package;
       }
    }
&lt;/pre&gt;
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;
Время жизни зависимостей не может быть больше, чем время жизни самого компонента. 
&lt;/strong&gt;

&lt;p&gt;
Теряется гибкость управления временем жизни зависимостей. Так как компонент рулит своими зависимостями, то время их жизни не может быть больше, чем время жизни самого компонента, так как тогда некому будет уничтожить зависимость, кроме как тому компоненту, который её создал.
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;
При наличии shared dependency, то есть зависимости, которая используется разными компонентами в разное время, непонятно кто из компонент должен управлять этой зависимостью.
&lt;/strong&gt;

&lt;p&gt;
Что можно сделать в этом случае? Как сделать так, чтобы компоненты имели доступ к shared dependency? 
&lt;/p&gt;

&lt;p&gt;
Да-да, реализовать зависимость в виде Singleton. Но тут стоит вспомнить притчу: &lt;strong&gt;&quot;Если у вас есть одна проблема и вы пытаетесь решить её с помощью Singleton, то у вас есть две проблемы&quot;&lt;/strong&gt;. Я бы сказал, даже не две, а больше. Вот некоторые из них:
&lt;ul&gt;
&lt;li&gt;
Невозможность подмены зависимостей в тестах.
&lt;/li&gt;
&lt;li&gt;
Что делать если время жизни &lt;code&gt;shared dependency&lt;/code&gt; ограничего неким scope, отличным от &lt;code&gt;per-application lifetime scope&lt;/code&gt;, к примеру, &lt;code&gt;per-client processing scope&lt;/code&gt; или &lt;code&gt;per-order processing scope&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
Что делать, если доступ к зависимости происходит в мультипоточном сценарии? Надо еще наворачивать синхронизационные конструкции?
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;
Другой вариант состоит в том, чтобы перенести управление зависимостью на уровень компонента, который является parent&#39;ом для тех компонент, которым нужна зависимость, и прокидывать зависимость, созданную в parent&#39;е, тому компоненту, где она используется. В сложном приложении с несколькими &lt;a href=&quot;http://en.wikipedia.org/wiki/Abstraction_layer&quot;&gt;layer of indirection&lt;/a&gt; это означает, что помимо засорения  parent компонента знанием о зависимости, которая ему совершенно не нужна, мы еще засоряем и все промежуточные слои приложения от parent&#39;а и до целевого компонента ненужными знаниями. Также это делает наше приложение монолитным, и внесение изменений, которые затрагивают зависимость, либо parent component, либо политику lifetime зависимости скорее всего затронет все слои, через которые зависимость проброшена, и цена таких изменений будет велика.
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
Недостатки управления зависимостями внутри компонент приложения
&lt;/h3&gt;
Итак, на основе проведенного анализа, можно составить список проблем. Если копнуть глубже, думаю, список проблем будет больше. 

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Не соблюдается Single Responsibility Principle;&lt;/strong&gt; компоненту необходимо помимо собственной &quot;полезной&quot; логики заниматься управлением зависимостями, и это получается у него плохо. Нарушается распределения знаний в системе. Различные части приложения засоряются знаниями, которые им на самом деле не нужны.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Увеличивается &lt;a href=&quot;http://msdn.microsoft.com/en-us/magazine/cc947917.aspx&quot;&gt;coupling&lt;/a&gt; (cвязанность) компонент внутри приложения.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Страдает re-usability компонента&lt;/strong&gt;; компонент зависит от сценария использования, а сценарий использования зависит от компонента. Здесь можно провести параллель с нарушением &lt;a href=&quot;http://en.wikipedia.org/wiki/Dependency_inversion_principle&quot;&gt;Dependency Inversion Principle&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Дублирование логики&lt;/strong&gt;; многие компоненты создают зависимости на основании одних и тех же факторов и логики. Если факторы меняются, то надо обновлять все компоненты.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Уменьшается maintainability приложения;&lt;/strong&gt; Увеличивается цена и сложность внесения изменений.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Невозможность изолированного тестирования компонент;&lt;/strong&gt; а это может повлечь невозможность применения &lt;code&gt;test-first development&lt;/code&gt;, либо &lt;a href=&quot;http://en.wikipedia.org/wiki/Test-driven_development&quot;&gt;test-driven development&lt;/a&gt;, либо &lt;code&gt;behavior-driven development&lt;/code&gt; подхода, а это значит, что список проблем, с которыми нам придется столкнуться, увеличивается.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Усложнение приложения;&lt;/strong&gt; его структуры, потоков данных.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Уменьшается уровень readability кода.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
Здесь возникает вопрос: &lt;strong&gt;&quot;А не пытаемся ли мы заставить наши компоненты заниматься тем, чем они не должны заниматься?&quot;.&lt;/strong&gt; 
&lt;/p&gt;

&lt;p&gt;
Возможно, отдельно взятые компоненты приложения - не лучшее место для размещения логики управления зависимостями. А почему бы нам не возложить эту обязанность на абсолютно другой компонент, не имеющий отношения к приложению, некий внешний компонент &quot;X&quot;? Тогда компоненты приложения вместо того, чтобы самим создавать зависимости, запрашивали бы компонент &quot;X&quot; предоставить им нужную зависимость.
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;service-locator&quot;&gt;Service Locator&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
Идея &lt;code&gt;Service Locator&lt;/code&gt; состоит в том, что компоненты приложения сами не разрешают зависимости, а просят некий третий объект, &lt;code&gt;Service Locator&lt;/code&gt;, предоставить им зависимость, который располагает знанием какую конкретную реализацию компонента предоставить в ответ на запрос и где ее взять. 
&lt;pre class = &quot;brush: csharp&quot;&gt;
    public DevicePacker(ServiceLocator serviceLocator)
    {
        // dependency resolving depends on deployment scenario
        _algorithm = serviceLocator.GetService&amp;lt;IPackingAlgorithm&amp;gt;();
        _shippingMethodCapabilities = serviceLocator.GetService&amp;lt;ShippingMethodCapabilities&amp;gt;();
        _boxTracker= serviceLocator.GetService&amp;lt;BoxTracker&amp;gt;();
    }
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
На самом деле, все что мы сделали, это лишь ввели еще один &lt;code&gt;layer of indirection&lt;/code&gt;, и перенесли туда логику управления зависимостями, тем самым избавив компоненты приложения о необходимости знать о конкретных реализациях зависимостей, однако, теперь компоненты вынуждены зависеть от &lt;code&gt;Service Locator&lt;/code&gt; и знать где его можно взять, чтобы получить нужную зависимость. В итоге, проблема лишь передвинута на уровень дальше.
&lt;/p&gt;

&lt;p&gt;
Обычно проблема доступа к &lt;code&gt;Service Locator&lt;/code&gt; из компонент приложения решается до боли известным способом: &quot;Singleton&quot;. Да, чтобы компоненты приложения без лишней боли имели доступ к экземпляру &lt;code&gt;Service Locator&lt;/code&gt;, этот самый &lt;code&gt;Service Locator&lt;/code&gt; моделируется в виде &lt;code&gt;Singleton&lt;/code&gt;. 
&lt;pre class=&quot;brush: csharp&quot;&gt;
public DevicePacker()
{
    // service locator is a singleton
    _algorithm = ServiceLocator.GetService&amp;lt;IPackingAlgorithm&amp;gt;();
    _shippingMethodCapabilities = ServiceLocator.GetService&amp;lt;ShippingMethodCapabilities&amp;gt;();
    _boxTracker = ServiceLocator.GetService&amp;lt;BoxTracker&amp;gt;();
}
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
Этот подход ничем не отличается от описанного выше, с тем лишь различием, что вместо моделирования многих компонент приложения как Singleton для доступа к ним, мы делаем это только с Service Locator&#39;ом. И нам кажется, что на это можно закрыть глаза. Однако это вовсе не решает проблем, которые привносит использование Singleton, мы лишь уменьшаем масштаб бедствия и локализуем его.
&lt;/p&gt;

&lt;p&gt;
И хотя &lt;code&gt;Service Locator&lt;/code&gt; и решает частично некоторые проблемы из описанных выше, и его внедрение в legacy application позволяет уменьшить уровень связанности, но причина остается в прежнем виде: 
&lt;br /&gt;
&lt;strong&gt;Компоненты приложения инициируют процесс разрешения зависимостей (dependency injection), и не важно сами они это делают, или этим занимается &lt;code&gt;Service Locator&lt;/code&gt;.&lt;/strong&gt;
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;di-ioc&quot;&gt;Dependency Injection / Inversion of Control Container&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;
Идея состоит в том, чтобы не компоненты приложения просили зависимости у компонента &quot;X&quot; (контейнера компонент), а чтобы контейнер сам делал инъекцию зависимостей в те компоненты, которым они нужны. Тем самым мы инвертируем механизм инъекции зависимостей, и реализуем &lt;strong&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Hollywood_Principle&quot;&gt;Hollywood principle&lt;/a&gt;, &quot;Don&#39;t call us, we&#39;ll call you&quot;&lt;/strong&gt;. 
&lt;/p&gt;

&lt;p&gt;
Отсюда и название &lt;strong&gt;Inversion of Control Container&lt;/strong&gt;. Это название может быть сбивающим с толку, здесь важно понимать, что принцип &lt;strong&gt;Inversion of Control&lt;/strong&gt; не является отличительной чертой контейнеров компонент, а является неотъемлимой чертой любого framework&#39;a, и состоит в том, что &lt;code&gt;application code&lt;/code&gt; не вызывает &lt;code&gt;framework code&lt;/code&gt;, вместо этого &lt;code&gt;framework code&lt;/code&gt; вызывает &lt;code&gt;application code&lt;/code&gt;. Скорее нужно спросить: &lt;strong&gt;&quot;К какому аспекту работы приложения применяется принцип inversion of control&quot;.&lt;/strong&gt; Ответ: &lt;strong&gt;&quot;Dependency injection&quot;&lt;/strong&gt;. В итоге уточняем первоначальное название &quot;IoC container&quot; следующим &quot;DI/IoC Container&quot;.
&lt;/p&gt;

&lt;p&gt;
Основное преимущество использования &lt;code&gt;DI/IoC&lt;/code&gt; состоит в том, что компоненты приложения освобождены от необходимости знать о контейнере, как это было в случае &lt;code&gt;Service Locator&lt;/code&gt;, и не надо больше городить огород с &lt;code&gt;Service Locator as a Singleton&lt;/code&gt;, и это позволяет нам решить проблемы описанные выше.
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;di-options&quot;&gt;Dependency Injection Options&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;
Если IoC Container занимается инъекцией зависимостей, то дизайн компонента должен позволять передать ему зависимость. Различают несколько вариантов передачи зависимостей в компонент.
&lt;ol&gt;

&lt;li&gt;
&lt;strong&gt;Сonstructor injection&lt;/strong&gt;
&lt;br /&gt;
&lt;pre class = &quot;brush: csharp&quot;&gt;
    public DevicePacker(
        IPackingAlgorithm algorithm,
        ShippingMethodCapabilities shipingCapabilities,
        BoxTracker boxTracker)
    {
        _algorithm = _algorithm;
        _shippingMethodCapabilities = shipingCapabilities;
        _boxTracker = _boxTracker;
    }
&lt;/pre&gt;
&lt;p&gt;
Все необходимые зависимости явно передаются компоненту через конструктор. Это наиболее предпочтительный подход. Так как зависимости передаются через конструктор, то невозможно создать компонент, если контейнер не располагает необходимыми зависимостями. Это лучше, чем создать компонент без нужной зависимости, и обнаружить эту проблему где-то намного позже. К тому же, код компонента становится более читабельным и не требующим разъяснений (self-explanatory), глядя лишь на конструктор можно понять какие зависимые компоненты нужны данному компоненту для работы.
&lt;/p&gt;
&lt;/li&gt;


&lt;li&gt;
&lt;strong&gt;Property Injection&lt;/strong&gt;
&lt;/li&gt;
&lt;pre class = &quot;brush: csharp&quot;&gt;
    public DevicePacker(
        IPackingAlgorithm algorithm,
        ShippingMethodCapabilities shipingCapabilities)
    {
        _algorithm = _algorithm;
        _shippingMethodCapabilities = shipingCapabilities;
    }

    public BoxTracker BoxTracker { get; set; }
&lt;/pre&gt;
&lt;p&gt;
Зависимости передаются через свойства. Менее предпочительный вариант, обычно через свойства передаются опциональные зависимости, без которых компонент может продолжать корректно работать. К тому же мы нарушаем инкапсуляцию.
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;Method injection&lt;/strong&gt;
&lt;pre class = &quot;brush: csharp&quot;&gt;
    public DevicePacker(ShippingMethodCapabilities shipingCapabilities)
    {
        _shippingMethodCapabilities = shipingCapabilities;
    }

    public BoxTracker BoxTracker { get; set; }

    public void SetupAlgorithm(IPackingAlgorithm algorithm)
    {
        throw new NotImplementedException();
    }
&lt;/pre&gt;

&lt;p&gt;
Довольно экзотический вариант. Не встречал в практическом использовании, возможно его имеет смысл использовать, если есть legacy сomponent, который принимает зависимости через метод, и мы хотим прикрутить использование DI/IoC и не можем поменять method injection на constructor injection либо property injection.
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;h3&gt;
&lt;a name=&quot;component-lifecycle-and-lifestyle&quot;&gt;Component lifecycle and lifestyle&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;
В управление зависимостями входит не только dependency resolve и dependency injection, но также и управление жизненным циклом компонент. Раз логика управления зависимостями вынесена в контейнер, то управление временем жизни компонент также должно входить в его обязанности.
&lt;/p&gt;

&lt;p&gt;
Стандартный lifecycle компонента с точки зрения контейнера тривиален:
&lt;ol&gt;
&lt;li&gt;
Создание компонента.
&lt;/li&gt;
&lt;li&gt;
Его использование.
&lt;/li&gt;
&lt;li&gt;
Уничтожение компонента.
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;
Необходимость осуществления контейнером этих шагов для конкретного компонента в конкретный момент времени регулируется политикой lifestyle, который указывается при регитсрации компонента в контейнере. Разные реализации контейнеров определяют разные наборы возможных lifestyle, однако можно выделить наиболее распространенные типы lifestyle&#39;ов.
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transient&lt;/strong&gt;, контейнер создает новый компонент каждый раз, когда осуществляется &lt;code&gt;dependency injection&lt;/code&gt;. Контейнер не хранит ссылку на компонент, обязанность уничтожения компонента переносится на компонент приложения.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Singleton&lt;/strong&gt;, контейнер создает один экземпляр компонента, время жизни компонента совпадает со временем жизни контейнера. Удовлетворяет все зависимости этим одним и тем же экземпляром.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per Thread&lt;/strong&gt;, создается один экземпляр компонента для каждого потока, время жизни компонента совпадает со временем жизни потока, то есть контейнер уничтожает компонент при завершении потока, в котором этот компонент был создан.
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;Per lifetime scope&lt;/strong&gt;, приложение само определяет границы жизненного цикла, к примеру &lt;code&gt;per-application, per web request, per client processing, per order processing&lt;/code&gt;. Контейнер хранит компонент и уничтожает его при выходе приложения из определенного &lt;code&gt;lifetime scope&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;
Lifestyle компонента определяется при регистрации компонента в контейнера. Чтобы не углубляться в детали реализации, в этой статье возможные варианты регистрации компонент в контейнере рассмотрены не будут. Если есть интерес, то можно ознакомиться на примере &lt;a href=&quot;http://code.google.com/p/autofac/wiki/ComponentCreation&quot;&gt;регистрации компонент&lt;/a&gt; в Autofac&#39;e.
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;service-locator-vs-di-ioc&quot;&gt;Service Locator vs DI/IoC Container&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;
Нередко Service Locator используется совместно с DI/IoC container&#39;ом. Можно выделить два варианта такого совместного использования.
&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Service Locator внутренне реализован на основании DI/IoC container&#39;a и запрашивает зависимости у контейнера.&lt;/strong&gt;

&lt;p&gt;
Это не такой уж и экзотический case. Допустим, имеется существующее приложение, мы захотели прикрутить DI/IoC контейнер. Просто так это сделать у нас не получится, для этого скорее всего потребуется сделать редизайн компонент, чтобы они могли принять зависимость либо в форме &lt;code&gt;Constructor Injection&lt;/code&gt;, либо &lt;code&gt;Property Injection&lt;/code&gt;, и т.п. Также, скорее всего, сделать этот редизайн так, чтобы мы всецело переключиться на использование &lt;code&gt;DI/IoC container&lt;/code&gt; у нас не получится. Вот тут на помощь приходит &lt;code&gt;Service Locator&lt;/code&gt;. Однако у нас уже есть контейнер компонент, не хотелось бы в Service Locator&#39;е дублировать логику контейнера, тогда делаем так, чтобы &lt;code&gt;Service Locator&lt;/code&gt; запрашивал зависимости у контейнера, и возвращал их компонентам приложения.
&lt;pre class = &quot;brush: csharp&quot;&gt;
public class ServiceLocator
{
    private IWindsorContainer _container;

    public ServiceLocator()
    {
        _container = new WindsorContainer();
        SetupContainer(_container);
    }

    public T GetService&amp;lt;T&amp;gt;()
    {
        return _container.Resolve&amp;lt;T&amp;gt;();
    }
}
&lt;/pre&gt;
&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;strong&gt;DI/IoС контейнер injects Service Locator тому компоненту, где этот Service Locator нужен.&lt;/strong&gt;
&lt;p&gt;
Внимание, в предущем предожении английских слов больше чем русских :). Иногда сложно выразить технические вещи на русском :(. 
&lt;/p&gt;

&lt;p&gt;
Это скорее логическое продолжение первого use case&#39;а, когда у нас есть Service Locator и DI/IoC контейнер. Service Locator может быть реализован на базе контейнера, хотя в этом случае это необязательно. Здесь мы снова сталкиваемся с проблемой: &quot;Как компонент приложения получит ссылку на Service Locator?&quot;. Только в этот раз вместо того, чтобы моделировать Service Locator в виде Singleton, мы регистрируем Service Locator в контейнере, и контейнер делает инъекцию Service Locator&#39;a в нужное место.
&lt;pre class = &quot;brush: csharp&quot;&gt;
public interface IServiceLocator
{
    T GetService&amp;lt;T&amp;gt;();
}

// service locator is based on DI/IoC container
public class CastleWindsorServiceLocator : IServiceLocator
{
    private IWindsorContainer _container;

    public CastleWindsorServiceLocator()
    {
        _container = new WindsorContainer();
    }

    public T GetService&amp;lt;T&amp;gt;()
    {
        throw new NotImplementedException();
    }
}

public class DevicePacker
{
    private IPackingAlgorithm _algorithm;
    private ShippingMethodCapabilities _shippingMethodCapabilities;
    private BoxTracker _boxTracker;

    // inject service locator into the component
    public DevicePacker(IServiceLocator serviceLocator)
    {
        _algorithm = serviceLocator.GetService&amp;lt;IPackingAlgorithm&amp;gt;();
        _shippingMethodCapabilities = serviceLocator.GetService&amp;lt;ShippingMethodCapabilities&amp;gt;();
        _boxTracker = serviceLocator.GetService&amp;lt;BoxTracker&amp;gt;();
    }
}
&lt;/pre&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
&lt;a name=&quot;common-service-locator&quot;&gt;Сommon Service Locator&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;
Ранее мы говорили, что при использовании Service Locator, компоненты приложения вынуждены знать о Service Locator&#39;е в виде статической ссылки. Если Service Locator реализован на основании DI/IoC контейнера, то неплохо бы скрыть от компонента приложения внутреннюю реализацию Service Locator&#39;а. Также нам может потребоваться заменить текущую реализацию контейнера на другую (Castle.Windsor на Autofac), и в этом случае мы не должны менять клиентов Service Locator&#39;a.
&lt;/p&gt;
&lt;p&gt;
Cмотреть сюда: &lt;a href=&quot;http://commonservicelocator.codeplex.com/&quot;&gt;Common Service Locator&lt;/a&gt;.
&lt;p&gt;

&lt;h2&gt;
&lt;a name=&quot;ioc-container-implementation&quot;&gt;Реализации IoC Container&#39;ов&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;
Существует довольно много реализаций IoC контейнеров от разных vendor&#39;ов. Список тех, что я знаю либо слышал, ниже.
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;http://stw.castleproject.org/Windsor.MainPage.ashx&quot;&gt;Castle.Windsor&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://code.google.com/p/autofac/&quot;&gt;Autofac&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://unity.codeplex.com/&quot;&gt;Unity&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://en.wikipedia.org/wiki/Spring_Framework&quot;&gt;Spring.NET&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://structuremap.net/structuremap/&quot;&gt;StructureMap&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://ninject.org/&quot;&gt;NInject&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;
Лично я предпочитаю использовать &lt;a href=&quot;http://code.google.com/p/autofac/&quot;&gt;Autofac&lt;/a&gt;, от других контейнеров его выгодно отличает наличие развитой системы типов отношений между компонентом и его зависимостью. Помимо стандартного отношения использования имеются также:
&lt;ul&gt;
&lt;li&gt;
отложенная инциализация; зависимость нужна компоненту в некоторый момент в будущем;
&lt;/li&gt;
&lt;li&gt;
зависимость нужна компоненту до определенного момента в будушем, после которого компонент уничтожает зависимость;
&lt;/li&gt;
&lt;li&gt;
компоненту нужно создавать экземпляры зависимости;
&lt;/li&gt;
&lt;li&gt;
компоненту нужны все зависимости определенного типа;
&lt;/li&gt;
&lt;li&gt;
и другие...
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;
&lt;p&gt;
Это дает дополнительную гибкость, и позволяет компонентам нe ссылаться на контейнер в виде прямой ссылки либо в виде Service Locator&#39;а, а выражать требования к использованию зависимостей в виде различных типов отношений. Более детально &lt;a href=&quot;http://code.google.com/p/autofac/wiki/RelationshipTypes&quot;&gt;здесь&lt;/a&gt; либо еще подробнее &lt;a href=&quot;http://nblumhardt.com/2010/01/the-relationship-zoo/&quot;&gt;здесь&lt;/a&gt;. Но это скорее тема для отдельного поста.
&lt;/p&gt;

&lt;h2&gt;
&lt;a name=&quot;resources&quot;&gt;Полезные ресурсы&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;
В завершение статьи привожу список ссылок из своих bookmarks, которые могут быть полезны.
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;http://martinfowler.com/articles/injection.html#UsingAServiceLocator&quot;&gt;Inversion of control and dependecy injection container pattern&lt;/a&gt;  by Martin Fowler
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://martinfowler.com/bliki/InversionOfControl.html&quot;&gt;Inversion of control principle&lt;/a&gt; by Martin Fowler
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://dotnetslackers.com/articles/designpatterns/InversionOfControlAndDependencyInjectionWithCastleWindsorContainerPart1.aspx&quot;&gt;Inversion of Control and Dependency Injection with Castle Windsor Series&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://status-alexus.blogspot.com/2010/04/inversion-of-control-and-dependency.html&quot;&gt;Inversion of Control and Dependency Injection. Ссылки.&lt;/a&gt; by Alexey Diyan.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://codehelper.ru/questions/352/%D1%84%D0%B5%D0%B5%D1%80%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F-%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D1%82%D0%BE%D1%87%D0%B5%D0%BA-%D0%BD%D0%B0%D0%B4-diioc-%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D0%B0%D0%BC%D0%B8&quot;&gt;
Феерическая расстановка точек над DI/IOC&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/aa973811.aspx&quot;&gt;Inversion of Control and Dependency Injection: Working with Windsor Container.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx&quot;&gt;List of .NET Dependency Injection Containers (IOC).&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://elegantcode.com/2009/01/07/ioc-libraries-compared/&quot;&gt;IoC libraries compared.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://wiki.bittercoder.com/Default.aspx?Page=ContainerTutorials&amp;AspxAutoDetectCookieSupport=1&quot;&gt;Containers tutorial with Castle.Windsor&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/3600940394400118646/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/02/blog-post.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/3600940394400118646'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/3600940394400118646'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/02/blog-post.html' title='Управление зависимостями между компонентами в приложении'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYcUcw8MDJ6uvcIKT_yTwpDyKX5XSg7LSzXji1bKlDm9aYI5gmyMayq2mcnwulrBSgqH8Wh6uqZQLMnbtc7Ozfeng2yNwMJZX82JpKbhrhGFpLqMEsB5B-ZgtdAO_u8psbkGrdQPqWd1k/s72-c/dependency-graph.png" height="72" width="72"/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6273369423927430830.post-2307434615458485701</id><published>2011-02-08T00:47:00.001+02:00</published><updated>2011-02-08T00:50:15.344+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="welcome"/><title type='text'>Приветствие</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijBzG2Pfsv9fqcL-lDy3zjhsRMOoLawDgScNe3LOzljhNlTiC132Kt6HiOQx7jbPemKFtb7I1ibWHp_N56rx16UQmawu5xkrsOv-S_zPgM3sg_0ayuZXvIH6SMPa4IduLiJYLrvydsBk0/s1600/Blogger+version+2-256.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; style=&quot;border-style: none&quot; height=&quot;200&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijBzG2Pfsv9fqcL-lDy3zjhsRMOoLawDgScNe3LOzljhNlTiC132Kt6HiOQx7jbPemKFtb7I1ibWHp_N56rx16UQmawu5xkrsOv-S_zPgM3sg_0ayuZXvIH6SMPa4IduLiJYLrvydsBk0/s200/Blogger+version+2-256.png&quot; width=&quot;200&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p style=&quot;font-weight: bold&quot;&gt;Доброго времени суток, гости, а возможно и будущие посетители и подписчики моего блога.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;Что хочу сказать. Наконец-то я обзавелся блогом. Лучше поздно, чем никогда. По правде говоря раньше у меня не было особой потребности развернуто высказаться, поделиться интересными заметками, какими-то новостями, хотя это не значит что у меня их вообще не было. Но все меняется, и вот, как результат, у меня появился блог.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;В основном здесь будут публиковаться посты на технические темы. Будучи по профессии software developer в стеке .NET, посты будут охватывать соответсвующие технологии + темы посвященные разработке ПО в целом. Это будут либо обзоры каких аспектов, либо задачи с которыми я столкнулся и их возможные решения, либо просто мои домашние наброски и эксперименты. Скорее всего в ближайшее время появятся посты посвященные &lt;a href=&quot;http://nhforge.org/&quot;&gt;NHibernate&lt;/a&gt; или &lt;a href=&quot;http://en.wikipedia.org/wiki/Windows_Communication_Foundation&quot;&gt;WCF&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;В меньшей степени также буду публиковать заметки, не связанные с профессиональной деятельностью, новости моей жизни, заслуживающие всеобщей публикации либо просто какие-то интересности. Так например, в ближайшее время появится статья, посвященная &lt;a href=&quot;http://www.amazon.com/dp/B002Y27P3M&quot;&gt;Amazon Kindle&lt;/a&gt; e-book reader, его покупке, доставке, и использованию.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;В завершение хочу понадеяться, что Вас заинтересуют мои будущие статьи.  &lt;br /&gt;
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://samoshkin.blogspot.com/feeds/2307434615458485701/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://samoshkin.blogspot.com/2011/02/welcome-message.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/2307434615458485701'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6273369423927430830/posts/default/2307434615458485701'/><link rel='alternate' type='text/html' href='http://samoshkin.blogspot.com/2011/02/welcome-message.html' title='Приветствие'/><author><name>Alexey Samoshkin</name><uri>http://www.blogger.com/profile/17352512789879632849</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-JFst_dw6buzbVRR-WU3DWma-MLM6MoEo6ZpnA9oOcm_s-N5HdSs88AvRk8RCsrsnmdCeAPE-h_jzxVPNCyaWRUStLEIpqnMO0cQK31DPooJDe7dic2WSAjbnPzzrmQ/s1600/IMG_0744.JPG'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijBzG2Pfsv9fqcL-lDy3zjhsRMOoLawDgScNe3LOzljhNlTiC132Kt6HiOQx7jbPemKFtb7I1ibWHp_N56rx16UQmawu5xkrsOv-S_zPgM3sg_0ayuZXvIH6SMPa4IduLiJYLrvydsBk0/s72-c/Blogger+version+2-256.png" height="72" width="72"/><thr:total>3</thr:total></entry></feed>