Wednesday, December 30, 2009

Query preprocessors, Inversion of control & Localization support

LINQ translator extension

As I promised earlier, we were going to make LINQ translator extendable and finally we’ve made this. The extension mechanism is called “Custom LINQ query preprocessors” and is already included into DataObjects.Net 4.1 code base.

Custom LINQ preprocessors must implement public interface IQueryPreProcessor which is defined in Xtensive.Storage assembly. Here it is:

public interface IQueryPreProcessor
{
  Expression Apply(Expression query);
}

As you might see, the contract is quite simple and straightforward: your preprocessor receives the whole query, modifies it in  the way you need, and returns the modified one. All preprocessors are called before the query is processed by internal LINQ translator, so it is the right time and place to apply necessary modifications.

Connecting preprocessors to translator (IoC)

After you have written you preprocessors, it is time to plug-in them to DataObjects.Net. This is done with the help of Inversion of Control concept. In order to follow it, you need to take the following steps:

1. Add reference to Microsoft.Practices.ServiceLocation.dll assembly. It is shipped with DataObjects.Net 4 and can be found in %DataObjects.Net Directory%\Lib\CommonServiceLocator directory.

2. Configure IoC container through application configuration file.

Add this line to configSections part:

    <section name="Services" type="Xtensive.Core.IoC.Configuration.ConfigurationSection, Xtensive.Core"/>

Add the corresponding configuration section:

<Services>
  <containers>
    <container name="domain">
      <types>
        <type type="Xtensive.Storage.IQueryPreProcessor, Xtensive.Storage" mapTo="Xtensive.Storage.Samples.Localization.QueryPreProcessor, Xtensive.Storage.Samples.Localization" singleton="true" />
      </types>
    </container>
  </containers>
</Services>

Note the usage of named service container (“domain”), the  IQueryPreProcessor type as an interface of a service and how it is mapped to the concrete implementation.

3. The last step is to configure Domain object & the above-mentioned service container.

// Building domain
domain = Domain.Build(DomainConfiguration.Load("Default"));

// Configuring domain-level services
var configurationSection = (ConfigurationSection)ConfigurationManager.GetSection("Services");
var container = new ServiceContainer();
container.Configure(configurationSection.Containers["domain"]);
domain.Services.SetLocatorProvider(() => new ServiceLocatorAdapter(container));

LINQ preprocessor in action

Having these actions done, we get the capability of using non-persistent localizable properties (Domain model can be found here) in LINQ queries:

using (var ts = Transaction.Open()) {

  Console.WriteLine("Implicit join through preprocessor");
  var pages = from p in Storage.Query.All<Page>()
  where p.Title=="Welcome!"
  select p;
  Console.WriteLine(pages.ToList().Count);

  ts.Complete();
}

Pay attention that neither PageLocalization type nor its members participate in the query, original p.Title expression in Where clause is used instead. As we know, Page.Title is not a persistent property and regular LINQ translator doesn’t know how to translate this expression. But having the initial query preprocessed with Xtensive.Storage.Samples.Localization.QueryPreProcessor makes such kind of expressions possible to use. The only thing the preprocessor makes is the replacement of p.Title expression to something like this:

p.Localizations.Where(localization => localization.CultureName==LocalizationContext.Current.CultureName)
          .Select(localization => (string)localization[“Title”])
          .FirstOrDefault();

That’s it.

The source code is available in our public repository in Xtensive.Storage.Samples.Localization folder.

Happy preprocessing! =)

10 comments:

  1. Hello, concept looks simple and nice, but I would recommend to use abstract class instead of interface for things like this and rid of dependency injection. Let end user decide how to configure DataObjects, not DataObjects itself.

    ReplyDelete
  2. Hello Alex,
    thanks for your comment.

    Sorry but I haven't understood the reasons why do you prefer abstract class over interface in this very case and what do you suggest to use instead of well-known and widely used "inversion of control" pattern.

    Could you explain your ideas in a more precise way?

    Thanks in advance.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Ok, about abstract classes and interfaces: There are two reasons: Firstly, If you use abstract classes instead of interfaces then you can safely add non abstract members to the type without breaking client's code.
    Second is that it is just more natural, if you wite
    MyQueryPreprocessor : IQueryPreProcessor
    it is like to say "I can preprocess queries".
    Whereas when you write
    MyQueryPreprocessor : QueryPreProcessor
    you say "I am QueryPreProcessor" which is really the case.

    About dependency injection. There are alternatives. You can just add property to domain configuration:
    <Storage>
     <QueryPreprocessors>
      <add type="MyPreprocessor"...>

    or register it manually
    domain.QueryPreProcessors.Add(
    new MyQueryPreProcessor())

    ReplyDelete
  5. Alex, I suspect that you are joking on interfaces and abstract classes. I'm sure that you already know the following concepts but let me list them anyway.
    Notion of interface is invented as a definition of contract while abstract class is a part of implementation of a contract. Competent usage of interfaces leads to lesser coupling, whereas focusing on classes gives the opposite effect. So we do not want to force people to inherit some base abstract class defined in Xtensive.Storage namespace. Implementation of the IQueryPreProcessor contract is much better.

    What about your antipathy to dependency injection concept: the main point is which part of application will be responsible for extention types mapping, extension instantiation and their lifecycle provision. It can be:
    a) DataObjects.Net (your first example)
    b) User code (your second example)
    c) Specialized component (approach that is described in the post)

    Personally, I don't like the idea that ORM must provide IoC functionality because being IoC framework is not the responsibility of ORM. For instance, take a look at NHibernate, it has nice open architecture, it really can be extended through different IoC frameworks, e.g., Windsor or Spring.Net. Its great extensibility is taken as an example for those architects who wants to design true enterprise-level applications even on MSDN site (http://msdn.microsoft.com/en-us/library/aa973811.aspx).
    As for b) & c) options, they are rather equivalent, because both configure the ORM from the outside.

    ReplyDelete
  6. Your reaction is surprising for me, No, I am not joking at all about abstract classes and interfaces. it is interesting question and I am glad to discuss it.
    Probably, I do not understand something, but I do not see why using interfaces instead of abstract classes leads to lesser coupling. In contrast, I think abstract classes force you to follow single responsibility principle. Moreover, It is very easy to show why abstract class provide more flexibility:
    Imagine all RequestPreProcessors turn out to have something in common, for example, they use Domain. In this case you can add Domain property to abstract class which would be usefull for new users and old user's code will still work.
    Would not you provite an opposite example, showing advantages of interfaces ?

    About my anthypathy to dependency injection. Probably, I do not understand something, but I do not consider a), b) and c) you listed as alternatives, all three options should be available at the same time. For example, I can follow a) option and then DataObjects instantiate type using parameterless constructor and place it to collection. Or I can instantiate it manually using code I've already listed Or I can instantiate it using dependency injection container and in the latest case I do need DataObjects' assistance.

    IQueryRequestProcessor myProcessor = IoCContainer.GetInstance< IQueryRequestProcessor>();
    domain.QueryPreProcessors.Add(
    myProcessor);

    I hope it does not look like a joke again :)

    ReplyDelete
  7. Concerning interfaces vs. abstract classes: this very topic is quite interesting according to large number of articles in the internet (http://www.google.com/search?q=interface+vs.+abstract+class). Moreover, I'm convinced that both options have their strong and weak sides and there is no need to be fanatical about only one approach. DataObjects.Net uses abstract class inheritance rather widely, for instance, in case where implementation for some reasons is really worth sharing among descendants, e.g. take a look at Entity, Structure or EntitySet (although it is not really abstract) types.
    As you might understood, I'm not going to be obsessive about interface usage everywhere, what I'm telling you is that interfaces provide clear separation between behavior and implementation. In case of interfaces we do not force users to inherit an abstract class, we do not limit users to follow our own hierarchy, we just define a contract which must be implemented somehow, that's it. User's implementation might implement several interfaces in one class which simply can't be achieved with abstract classes approach. Moreover, changing some implementation details in base abstract class might lead to changing dependent implementations in all descendants. Interface approach does not have such lacks.
    Again, I'm not going to argue that interfaces are the only God blessed approach; it also has disadvantages that you've already listed so I propose to close the fruitless argument.

    What about the second part of the discussion: I'm regarding the point of view when ORM is capable of instantiating, tracking & disposing third-party components (services) as a confusion of ideas. In order to fulfill the pattern ORM must know the mappings between contracts and implementations, how and when to create new implementations, how to configure them, track specific named instances, distinguish between singleton services and transient ones, know which instances must be shared between several sessions and which are not, how and when dispose a network of dependent instances and so on. All these tasks are not the responsibility of ORM just because it is not IoC framework. All it should do is to expose the extensibility points through the appropriate API.
    In my opinion, the best solution for this task is to use professionally designed and built Inversion of Control framework. In addition to these listed functions it has one more advantage over manual instantiation and registration of implementations: instantiations are made on demand (they are being constructed in lazy way), so there is no any necessity to construct all services at startup time.
    Again, I am not intended to bring you around to my point of view; I find your conviction quite mature and sensible. There are just several ways of doing the same things according to one's views and personal experience so let us follow our own way, agreed?

    ReplyDelete
  8. Of course, you follow your way, I've just tried to explain my point of view because you asked. Once I did it it is up to you to decide whether it worth considering or not.

    ReplyDelete
  9. Thanks!
    It was quite pleasant for me to participate in such kind of friendly debates.

    ReplyDelete