Showing posts with label patterns. Show all posts
Showing posts with label patterns. Show all posts

Mar 28, 2012

ASP.NET MVC extension points in action

Recently I made a tech talk about my alt.net web stack of love. It has lots of things there. Validation, NHibernate, Routing etc. So here are slides:

Source code for it is on bitbucket. Its purpose is just see it all in action. If you want, you should move code your new/old projects. Don’t try to make some type of project template out of it.

To try things out

Once again link to sources.

I was asked to give some kind of practice task to try all stack together. So here it is:

Implement blog details page to show posts inside it. So when I open localhost/blogs/3 I should see something like:

untitled_page

When I navigate to post details page I should be able to see post content and leave comments. Just like on this blog Smile.

Oct 21, 2011

Setup SQL Server Compact 4 to unit test NHibernate related code

Most of the time to fake data base SQL lite data base is used. But it has certain differences from SQL server. With release of SQL Compact 4 it becomes really good choice to mock data base calls. So in this post I will describe a way of setting up NHibernate to work with SQL CE local data base. So here are our goals:

  1. Each test fixture has its own fresh DB instance
  2. NHibernate SessionFactory is same for all the tests (performance is still important in tests)
  3. Caching of NHibernate doesn’t stands on isolation way (each test will have clean factory, without any cached entities)
  4. Works just after getting from source control with no additional configuration

First of all we need to install sql ce tools for visual studio. After done that we can add an empty data base file to our tests project:

Sql server compact 4.0 local data base

lets call it db. This file is going to be the one that is going to be copied for each test and where NHibernate will create its tables.

Now lets create a base class for tests that are going to use NHibernate:

public class DbTests
{
    protected static ISessionFactory factory;
    static Configuration nhConfig;

    static DbTests()
    {
        File.Copy("db.sdf", "Temp.sdf", true);
        nhConfig = NhConfigure();
        factory = nhConfig.BuildSessionFactory();
    }

    [TestFixtureSetUp]
    public void Setup()
    {
        File.Copy("db.sdf", "Temp.sdf", true);
        new SchemaExport(nhConfig).Execute(true, true, false);
    }

    [TestFixtureTearDown]
    public void TearDown()
    {
        File.Delete("Temp.sdf");
    }

    static Configuration NhConfigure()
    {
        DomainMapper mapper = new DomainMapper();
        HbmMapping mappings = mapper.CompileMappingFor(new[] { typeof(TestEntity) });

        var configuration = new Configuration();
        configuration.SessionFactory()
            .Integrate.Using<MsSqlCe40Dialect>()
            .Connected.By<SqlServerCeDriver>()
            .Using("Data Source=Temp.sdf");

        configuration.AddDeserializedMapping(mappings, "domain");
        
        return configuration;
    }
}

What is done here is pretty straight forward. Each test fixture will get its own empty data base with new schema installed.

Also you will need to install SqlServerCompact package from nuget in order to get SqlServerCeDriver support.

There is an interesting bug when using identity columns with SQL CE. You can get NHibernate.AssertionFailure : null identifier exception. Here is how you can solve it.

The last thing we want to take care about is NHibernate cache. Each test probably will have its own ISession instance, so only second level caching should be handled. Here is how we can clean up it:

static void ClearCache()
{
    factory.EvictQueries();
    foreach (var collectionMetadata in factory.GetAllCollectionMetadata()) 
        factory.EvictCollection(collectionMetadata.Key);
    foreach (var classMetadata in factory.GetAllClassMetadata()) 
        factory.EvictEntity(classMetadata.Key);
}

Just add this method call to Setup and that it. As always working sample attached:

Source code sample doesn’t contain nuget packages and uses this way of working. So don’t be scared of everything red in ReSharper after open solution. Just build it.

Jul 22, 2011

Slides and code

I promised to publish slides and code from my talk about unit testing. Here they are:

And a source code:

Apr 12, 2011

Unit testing classes with a lot of dependencies

I’m sure every TD developer will scream that it’s not right to have classes with a lot of dependencies. I agree with you, but it happens. It happens for example in MVP pattern for ASP.NET, or for controllers in MVC.

Why it happens? The main reason I think are unnecessary abstractions. Many applications have interface like IRepository<T> : where T : IEntity. Such interface seams to be very useful. It gives you strongly typed access to the entities, and gives you ability to put common methods there (like Get<T>(int id)). Everything looks good so far. We configure our application to use IoC container and don’t bother with dependencies:

public ProductsController : Controller
{
    private readonly IRepository<Product> productsRepository;

    public ProductsController(IRepository<Product> productsRepository)
    {
        this.productsRepository = productsRepository;
    }

    public ActionResult Index()
    {
        var products = productsRepository.GetAll();
        return View(products);
    }
}

Everything looks good so far and we are able to write very simple unit test for it. Its easy to mock GetAll() method and verify that View.Model contains exact the same collection that returned from repository.

But then you need to create a Create method that requires getting list of all categories that could be assigned to product. And all of a sudden you have two dependencies:

public ProductsController : Controller
{
    private readonly IRepository<Product> productsRepository;
    private readonly IRepository<Category> categoriesRepository;

    public ProductsController(IRepository<Product> productsRepository, IRepository<Category> categoriesRepository)
    {
        this.productsRepository = productsRepository;
        this.categoriesRepository = categoriesRepository;
    }

    public ActionResult Index()
    {
        var products = productsRepository.GetAll();
        return View(products);
    }

    public ActionResult Create()
    {
        var categories = categoriesRepository.GetAll();
        ViewBag.Categories = categories;
        return View();
    }
}

Its still looking good. But only if you have 2-3 tests that are using ProductsController constructor with 1 parameter. When you have 20 of those its becoming a nightmare to add new dependency in constructor.

I do understand that its not best example of why a lot of dependencies happens, but its real one. Its very easy to have up to 4-6 decencies in constructor. And to do with it in tests? Each test needs to set 2 or 3 of them and he doesn’t cares about other. My first approach was creating a helper methods as such:

public ProductsController GetController(IRepository<Product> productsRepository)
{
    return new ProductsController(productsRepository, Mock.Of<IRepository<Category>>);
}

Its perfectly works for classes with less then 3 dependencies. But even there the problem still exists. Consider mocking of current HttpContext.Current.User.Identity. It may happen that each controller action needs access to it, but most of tests doesn’t care what is going to be returned as current user. You will need to copy paste mocking code in each of such methods, or put some complex chain of methods calling each other to get default user for all test controllers.

What I prefer to do now is having a simple factory for controllers (presenters) in test class:

internal class ProductsControllerFactory()
{
    public ProductsControllerFactory()
    {
        this.CategoriesRepsitory = Mock.Of<IRepository<Category>>();
        this.ProductsRepository = Mock.Of<IRepository<Product>>();
        
        // you can put all default stubs here
    }

    public ProductsController GetController()
    {
        return new ProductsController(this.CategoriesRepository, 
                                      this.ProductsRepository);
    }

    public IRepository<Category> CategoriesRepsitory { get; set; }

    public IRepository<Product> ProductsRepository { get; set; }
}

So you end up with having constructor call in one place. Adding a dependency won’t affect existing tests, it will affect only controller factory. Each test can easily set dependency it needs and ignore dependencies it doesn’t cares about.

Nov 11, 2010

Don't use constants

I don’t really remember when, but inside my current team I have told that I really hate to see classes that are like this:

public class Contants
{    
    public class Settings
    {
        public const ConnectionStringName = "SomeProject.ConnectionString";    
    }
    public class FieldNames
    {
        public const SomeField = "MyField";
    }
}

Almost all of the projects that I saw had this kind of class. Usage of it seams to be obvious – to store reusable strings to remove repetition.

But for me such a classes are lacking of the one thing – intention. Each constant exists inside our application for some reason and Constants class completely removes context that is connected to its value.

Today we had a some kind of “real world” scenario where I can show what to do with constants. I our case we had an email template where system will substitute some user input. So template contains %UserName%, %Reason% and so force string patterns that are going to be replaced with some values. The most obvious storage for these patterns is Constants class, but we don’t want it :)! So what I think we should do is to create a set of extension methods like this:

static class StringExtensions
{
    public static string SubstituteUserName(this string template, string userName)
    {
        return template.Replace("%UserName%", userName);
    }
}

This approach will allow us easily to write unit tests, because method is separated from others and really simple, and it will allow to write code in the following way:

"Hello %UserName%, thanks for your message about %Reason%"
                      .SubstituteUserName(userName)
                      .SubstituteReason(reason);

I think its really readable and looks nice.

The next thing why such an approach works is the way how developer works with the legacy code. In legacy system it is really often for developer to perform text based search to find places where similar tasks were already solved. For example when you need to get some value from the web.config file you can either use ConfigurationManager or WebConfigurationManager or maybe in your project some custom wrapper is implemented. For me the easiest way to find such information is to grab existing key of some value and try find all the references on it. In the good project you will meet only one place of usage :). In other cases it may be two places – Constants class and some domain specific wrapper (in this case why do need this constant separately of its usage?). But if you find that this constant is reused in 100 places and there is no common pattern you will just add the 101th place and forget about it. The reason why that happened is the existence of this Constans class.

So my approach for constants is that they can be only private in some classes that are using them. This allows new developers easy way to understand why those values are here and how to use them.

Feb 5, 2010

NHibernate и Castle Windsor. Session per web request

Как известно основными объектами, с которыми приходится работать в NHibernate это Session и SessionFactory. При этом SessionFactory должен быть один на все приложение, поскольку его создание обходится дорого, а Session – дешевый для создания объект.

Можно рассмотреть следующий вариант, объект Session создается по необходимости – т.е. на каждый запрос к базе мы получаем новый экземпляр сессии. Но в этом сценарии мы теряем кеш первого уровня, получаем сложности с транзакциями и т.д. Наиболее распространен подход Session Per Web Request. В этом случае сессия создается в начале обработки запроса, и удаляется по завершению.

В этом посте я покажу как очень просто реализовать это с использованием Castle Windsor. Для этого добавляем один класс, который будет отвечать за конфигурацию SessionFactory:

public class NHibernateConfiguration
{
    public ISessionFactory CreateSessionFactory()
    {
        FluentConfiguration configuration = Fluently.Configure()
                                                    .Database(MsSqlConfiguration.MsSql2008.ConnectionString(cs=>cs.FromConnectionStringWithKey("dbConnection")))
                                                    .Mappings(m=>m.FluentMappings.AddFromAssemblyOf<CategoryMap>());

        return configuration.BuildSessionFactory();
    }
}

Далее, добавив ссылки на Castle.Core, Castle.MicroKernel и Castle.Windsor создаем класс, который настроит правила работы с SessionFactory и Session:

public static class ServiceLocaterInitializer
{
    /// <summary>
    /// Initializes Castle Windsor.
    /// </summary>
    public static void Init()
    {
        IWindsorContainer container = new WindsorContainer();
        container.AddFacility<FactorySupportFacility>();
        container.Register(Component.For<ISessionFactory>()
                               .LifeStyle.Singleton
                               .UsingFactoryMethod(() => new NHibernateConfiguration().CreateSessionFactory()));

        container.Register(Component.For<ISession>()
                               .LifeStyle.PerWebRequest
                               .UsingFactoryMethod(kernel => kernel.Resolve<ISessionFactory>().OpenSession()));
    }
}

Чтобы это заработало необходимо добавить http модуль в веб конфиге:

<add name="PerRequestLifestyle" type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.MicroKernel" />

Далее, чтобы получить объект Session достаточно сделать следующий вызов:

var session = container.Resolve<ISession>();

Жизненным циклом объектов Castle будет управлять сам.

Dec 22, 2009

Когда использовать наследование в ORM средствах

NHibernate поддерживает три способа имитации наследования в реляционных базах данных. Table per class hierarchy, table per subclass, table per concrete class. О каждой из этих стратегий можно найти множество примеров как их реализовывать. Я же хотел бы поделиться своим опытом о том, когда же стоит применять наследование.

Вплоть до моего последнего проекта, в моих базах содержались таблицы называющиеся на подобие ContentType и содержащие две колонки – Id, TypeName. Необходимость таких таблиц с точки зрения базы действительно необходимо. Вот к примеру на сайтах социальных сетей можно загружать фото и видео. У обоих есть общие атрибуты, такие как название, подпись, комментарии, дата добавления и т.д. Естественно, что будет логично расположить информацию о фото и видео в таблице Content и добавить поле Type, чтобы приложение могло отличить фото от видео (хотя бы для того, чтоб правильно отображать каждое из них).

Для ситуации, когда вариантов таких типов всего два – в общем то не будет сложностей с управлением ими. Но предположим, что вам необходимо где либо (пусть даже на странице администратора) показать список всех загруженных пользователями фотографий и видео, при этом должна быть колонка, которая в текстовом виде показывает чем является контент. Большинство сайтов в данный момент требует локализации, и простенькая таблица, в которой раньше было только два поля превращается вот в такого монстра:

Схема базы с локализацией

Если же перенести знание о типе контента в приложение, то вы можете создать следующую иерархию классов:

Диаграмма классов с наследованием

Абстрактный класс Content содержит абстрактное поле ContentType, и если же объединить этот подход с локализацией, которую я описывал ранее, то можно получить чистую доменную модель, и базу, лишенную необходимости в сложной структуре из-за локализации (как Вы сами понимаете, в базе при использовании стратегии per class hierarchy будет содержаться лишь одна таблица Content, в которое будет поле ContentTypeId, но семантика этого TypeId будет содержаться в приложении, и NHibernate сам позаботится о том, чтобы поставить для вас правильный тип).

Конечно же можно оставить в базе данных таблицу ContentType, и при выводе для пользователя проверять его значение и доставать по ключу из ресурсов нужную локализованную строку. Но при добавлении нового типа контента, я могу дать 90% гарантию, что кто-то забудет добавить строку в ресурсы, которая будет соответствовать новому типу. Или же переименование типа в базе данных приведет к полной неработоспособности приложения, но узнать об этом можно будет лишь на этапе выполнения, а не компиляции (именно это и случалось довольно часто в последнем моём проекте).

Такой подход дает еще одно преимущество – уменьшение объема (а по хорошему и полное отсутствие) условной логики в приложении. Все действия, которые зависят от того, какой тип Вам пришел можно располагать в самих конкретных объектах, а не в виде switch case конструкций. Например предположим, что с некоторых пор загрузка фото и видео в вашем приложении стала платной, причем загрузка видео стоит больше нежели загрузка фото, при отсутствии иерархии вам бы пришлось написать следующий код:

public double GetPrice()
{
    switch (ContentType)
    {
        case "Photo":
            return PHOTO_PRICE;
        case "Video":
            return VIDEO_PRICE;
        default:
            return 0;
    }
}

Не важно что будет в этом switch – будь то строки, или какой то enum. Важно, что такая конструкция будет не одна, а будет она везде где необходимо выполнить что-то в зависимости от типа контента. Как искать все условные операторы, если к фото и видео добавились еще и музыкальные файлы? Самое плохое то, что опять же ошибку вы сможете найти только во время выполнения, и самое страшное, что если забыть дописать в этом switch взимание платы за музыкальные файлы – пользователи будут загружать их по нулевой цене!

Так что всякий раз когда вы создаете таблицу с именем <Entity>Types или что-то подобное – подумайте не стоит ли заменить её полноценной схемой наследования.

Но есть одна проблема, которую мне так и не удалось решить. Возможно она специфична для NHibernate, но мне так не кажется. Если существует функционал изменения типа объекта. Например пользователь захочет наложить на фотографию музыку, и это будет уже видео. ORM не даст так просто изменить тип объекта, который привязан к определенному Id. Этот вопрос возникал и в Google groups, и я его задавал на StackOverflow.

Почему Вы не можете удалить фото, и создать вместо него объект видео? Это объясняется следующим, вероятней всего для просмотра контента вы будете использовать url наподобие www.mysite.com/Content?Id=2. И поле Id будет уникальным для контента (либо авто инкрементное поле в базе, либо hilo), значит Id вновь созданного объекта будет другим. Картинка пользователя пользовалась огромной популярностью, но пользователи, которые воспользуются старой ссылкой уже не найдут интересующее.

Так же проблемой могут быть каскадные операции. Удаление контента просто обязано повлечь за собой удаление всех комментариев к нему, но их нельзя удалять потому как они должны перейти к вновь созданному видео.

Предложенным решением является использование композиции вместо наследования. Возможно, но я ничего не могу об этом сказать, не пробовал.

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

P.S. если кто-то найдет решение вопроса со сменой типа – огромная просьба поделиться :)

Dec 3, 2009

Явные роли – валидация

В тему предыдущего поста о явных ролях в приложениях можно рассмотреть вопрос валидации. Каждый объект может быть валдиным или нет в зависимости от сценария, в котором он участвует в данный момент. Например правило – пользователь может стать избранным, если он сделал 6 и более заказов. Там приложение контролировало это в самом методе MakePreferred и выбрасывало исключение, если пользователь не удовлетворяет условию.

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

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

Рассмотрим что это будет значить в рамках подхода явного разделения ответственности между классами. Первое, что стоит учитывать, это что одна и та же сущность может быть валидной или нет, в зависимости от контекста (роли) в данный момент. Пользователь вполне может подходить для создания нового заказа, но не может при этом стать избранным.

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

public void btnMakePreferred_Click(Object sender, EventArgs e)
{
    Customer customer = ORM.Get<Customer>(customerId);
    if(customer.Orders.Count > 5)
    {
        customer.MakePreferred();
    }
}

И это хорошо если он выглядит именно так, т.е. MakePreferred вызывается у самого объекта, тем самым давая возможность относительно легко изменить правило определения избранного пользователя. А иногда бывает и такое:

public void btnMakePreferred_Click(Object sender, EventArgs e)
{
    Customer customer = ORM.Get<Customer>(customerId);
    if(customer.Orders.Count > 5)
    {
        customer.IsPreferred = true;
    }
}

По собственному опыту я знаю что такой код встречается в проектах слишком часто.

Что же можно сделать, чтобы явно указать, что для валидации в данном контексте должно использоваться определенное бизнес правило? Ответ прост – сделать роли явными.

Для этого выделим следующий интерфейс (что из себя представляют Entity и IRole описано в предыдущем посте):

public interface IValidator<TEntity, TRole> where TEntity: Entity
                                            where TRole:   IRole
{
    bool IsValid(TRole entity);
}

Он отвечает на единственный порос, походит ли объект для данной роли. И рассмотрим конкретную реализацию:

public class MakeCustomerPreferredValidator: IValidator<Customer, IPreferredMaker>
{
    public bool IsValid(IPreferredMaker entity)
    {
        Customer customer = entity as Customer;
        if(customer == null)
        {
            throw new ArgumentException("Entity must be Customer", "entity");
        }

        return customer.Orders.Count > 5;
    }
}

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

Теперь о том как использовать этот класс, первый вариант (плохой):

IValidator<Customer, IPreferredMaker> validator = new MakeCustomerPreferredValidator();
if (validator.IsValid(preferredMaker))
{
    preferredMaker.MakePreferred();
}

Плохой потому что в этом случае, существует явная зависимость от типа MakeCustomerPreferredValidator. Мне больше нравится для этих целей некоторый Factory класс, который может быть реализован следующим образом:

public class ValidatorsFactory
{
    public IValidator<TEntity, TRole> GetValidator<TEntity, TRole>() where TEntity : Entity
                                                                     where TRole : IRole
    {
        var validators = from t in Assembly.GetAssembly(typeof(ValidatorsFactory)).GetTypes()
                         where t.GetInterfaces().Contains(typeof(IValidator<TEntity, TRole>))
                         select t;

        return (IValidator<TEntity, TRole>) validators.SingleOrDefault();
    }
}

Но я бы рекомендовал использовать любой Service Locator для этих целей.

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

Repository<Customer> customerRepository = new Repository<Customer>(Global.CurrentSession);
ValidatorsFactory factory = new ValidatorsFactory();

IValidator<Customer, IPreferredMaker> validator = factory.GetValidator<Customer, IPreferredMaker>();
IPreferredMaker preferredMaker = customerRepository.Get<IPreferredMaker>(101);

if(validator.IsValid(preferredMaker))
{
    preferredMaker.MakePreferred();
}

Такой подход обеспечивает явное разделение концепций в приложении, и дает возможность явно обязать разработчиков не разбрасывать код в совершенно неожиданных местах.

Исходный код.

Nov 30, 2009

Делаем роли явными

Версия 2. Исправления с учетом комментариев.

Данный пост появился в результате многократного прослушивания лекции Udi Dahanan, которая называлась Intentions and Interfaces - Making Patterns Complete (смотреть тут, если не посмотрите, то дальше в общем то читать будет не интересно).

Главная идея лекции заключается в том, чтобы сделать роли в приложении явными. Т.е. если создается система для электронной коммерции, где можно делать заказы, то явно выделить такие интерфейсы (роли) как IMakeOrders, IPayment и т.д. Это позволит четко разделить части приложения, отвечающие за разную функциональность.

Была затронута небезынтересная тема ORM средств, в частности NHibernate, и приведен следующий пример кода:

public class ServiceLayer
{
    public void MakePreferred(Id customerId)
    {
        IMakeCustomerPreferred c = ORM.Get<IMakeCustomerPreferred>(customerId);
        c.MakePreferred();
    }
}

Это мне понравилось больше всего, т.е. работая с таким кодом, просто невозможно явно не разделять различные части приложения.

Первая попытка реализовать данный подход это использовать схемы наследования, доступные в NHibernate. Не получилось это сделать по причине отсутствия таблицы для интерфейсов (IMakeOrders, IPayment и т.д.), так же нельзя вводить и колонку descriminator, поскольку один объект может реализовывать более одного интерфейса. В общем чистыми маппингами такого дизайна приложения не достичь.

Udi Dahanan запостил у себя на блоге реализацию своего подхода.  Честно сказать – не понравилось. Да, возможно такая реализация как то ближе к DDD или к SOA или еще к куче умных слов, но выделять интерфейс для для каждого бизнес объекта мне кажется излишним.

Так же в примере изменены исходные коды NHibernate, что станет проблемой при выходе новой версии библиотеки.

Я бы предпочел построить эту абстракцию выше, без изменения исходного кода класса Session и прочее. И вот к чему я пришел.

Рассмотрим следующую модель:

imageПользователь реализует 2 роли, IOrderMaker и IPreferredMaker. Теперь хотелось бы иметь возможность написать на aspx странице следующий код:

private void MakeOrder(int productId, int customerId, int amount)
{
    Repository<Product> productRepository = new Repository<Product>(Session);
    Repository<Customer> customerRepository = new Repository<Customer>(Session);

    Product product = productRepository.Get(productId);

    IOrderMaker orderMaker = customerRepository.Get<IOrderMaker>(customerId);
    orderMaker.MakeOrder(product, amount);
}

Чтобы это сделать был реализован метод Get в классе Repository:

public TRole Get<TRole>(int id) where TRole:IRole
{
    return (TRole) (Object) session.Get<TEntity>(id);
}

Но в данном случае никто не мешает использовать напрямую поля и методы класса Customer, которые он реализует через интерфейс IOrderMaker, чтобы скрыть их можно воспользоваться интерфейсами с явной реализацией. Тогда метод MakeOrder будет виден только при приведении типа Customer к IOrderMaker. Если Вам это не нужно, можно оставить их простыми методами.

Теперь обратимся к роли IPreferredMaker. Допустим пользователь может стать избранным, только если он сделал более 5 заказов. В таком случае следует заставить репозиторий получить покупателя со всеми его заказами. Это делается при помощи fetching strategy. Изменять её хотелось бы независимо от реализации MakePreferred. Поэтому был задан следующий интерфейс:

/// <summary>
/// Sets fetching strategy for specified role
/// </summary>
/// <typeparam name="TEntity"><see cref="Entity"/></typeparam>
/// <typeparam name="TRole"><see cref="IRole"/></typeparam>
public interface IFetchingStrategy<TEntity, TRole>
{
    void AddFetchTo(ICriteria criteria);
}
И реализация:
/// <summary>
/// Loads customer with his orders
/// </summary>
public class MakeCustomerPreferred: IFetchingStrategy<Customer, IPreferredMaker>
{
    public void AddFetchTo(ICriteria criteria)
    {
        criteria.SetFetchMode<Customer>(x => x.Orders, FetchMode.Join);
    }
}
Теперь надо заставить репозиторий использовать эту стратегию, сделать это можно следующим образом (изменяем метод Get):
public TRole Get<TRole>(int id) where TRole:IRole
{
    ICriteria criteria = session.CreateCriteria(typeof(TEntity));
    criteria.Add<TEntity>(x => x.Id == id);

    IEnumerable<Type> fetchingStrategies = GetFetchingStrategies<TRole>();

    foreach (Type fetchingStrategyType in fetchingStrategies)
    {
        IFetchingStrategy<TEntity, TRole> fetchingStrategy = (IFetchingStrategy<TEntity, TRole>)Activator.CreateInstance(fetchingStrategyType);
        fetchingStrategy.AddFetchTo(criteria);
    }

    TRole result = (TRole) criteria.UniqueResult();
    return result;
}

private static IEnumerable<Type> GetFetchingStrategies<TRole>()
{
    return from t in Assembly.GetAssembly(typeof (IFetchingStrategy<TEntity, IRole>)).GetTypes()
           where t.GetInterfaces().Contains(typeof (IFetchingStrategy<TEntity, TRole>))
           select t;
}

Метод GetFetchingStrategies получает все типы, реализующие IFetchingStrategy<TEntity, IRole> (все стратегии для передаваемой роли и сущности). Метод Get создает экземпляр каждой из стратегий по очереди и применяет к запросу. Естественно метод поиска стратегий можно упростить если использовать Service Locator.

Последнее что хотелось бы упомянуть, это расположение файлов в проекте, я обычно делаю примерно так:

Содержимое проекта

О том как тестировать приложения с таким дизайном я напишу позже.

Пример кода

Nov 6, 2009

Inversion of control. Управление зависимостями.

Данный пост является некоторым следствием работы над последним проектом. В этом проекте использовалась S#arp архитектура, которая обеспечивает отличную возможность для юнит тестирования и каркас для создания корпоративных веб приложений. Так же крайне полезно будет прочесть статью касающуюся зависимостей. Я считаю что разобраться в чем то лучше всего позволяет попытка объяснить это другому. Именно это и я и хочу сделать.

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

public class Logger 
{ 
 public void LogMessage(string message) 
 { 
  using(FileStream fileStream = new FileStream("log.txt", FileMode.Append, FileAccess.Write)) 
  { 
   StreamWriter writer = new StreamWriter(fileStream); writer.Write(message); 
    }
 } 
} 

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

Получив такое требование конечно хотелось бы добавить новый метод LogMessage(string message, bool storeInDataBase), который принимает дополнительный параметр типа bool и если он true пишет все что надо в базу.

Проблема в этом подходе я думаю уже видна – если в дальнейшем мы захотим писать не только базу данных и в файл, но в Windowns EventLog, то что, добавлять еще один флаг?

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

image

Наш класс Logger напрямую зависит от файловой системы. Чтобы избавится от этой зависимости можно использовать принцип Inversion of control. Для этого выделим интерфейс, который будет отвечать за сохранение информации в лог:

public interface ILogContainer 
{ 
 void SaveMessage(string message); 
} 

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

public class FileLogContainer: ILogContainer 
{ 
 public void SaveMessage(string message) 
 { 
  using (FileStream fileStream = new FileStream("log.txt", FileMode.Append, FileAccess.Write)) 
  { 
   StreamWriter writer = new StreamWriter(fileStream); writer.Write(message); 
  } 
 } 
} 

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

public class Logger 
{ 
 private ILogContainer logContainer; 
 public Logger(ILogContainer logContainer) 
 { 
  this.logContainer = logContainer; 
 } 
 public void LogMessage(string message) 
 { 
  logContainer. SaveMessage(message); 
 } 
}

После этого преобразования диаграмма зависимостей будет выглядеть:

image

Из диаграммы понятно почему этот подход называется Inversion of control, получается что зависимость от файловой системы была как бы развернута в обратную сторону.

Что нам дает этот подход? Помимо того, что теперь мы можем использовать любое хранилище логов, реализующее ILogContainer, полученный результат отлично поддается тестированию. Интерфейс ILogContainer дает точку в которй можно встроить mock object, с помощью которого можно правильно проверить работу класса Logger.