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! =)

Monday, December 28, 2009

Logging, part 3. Configuring logging through log4net

In the previous post I demonstrated how to configure and use internal DataObjects.Net logging capabilities. These are rather useful and flexible but in case you want much more flexibility or something, using one of the external logging frameworks could be the right choice for you.

As I already mentioned, external logging components are connected to DataObjects.Net through the mechanism of adapters: DataObjects.Net => adapter for logger => logger.

In case of log4net you’ll need the following assemblies:

  • log4net.dll (can be found here)
  • Xtensive.Adapters.log4net.dll (is contained in DataObjects.Net installer)

The next step is to configure both DataObjects.Net & log4net.

Add these sections to configSection block of your application configuration file:

    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
    <section name="Xtensive.Core.IoC" type="Xtensive.Core.IoC.Configuration.ConfigurationSection, Xtensive.Core"/>

Xtensive.Core.IoC namespace goes for basic Inversion of Control implementation however it is powerful enough to accomplish most of appropriate tasks. In this case it is used to map Xtensive.Core.Diagnostics.ILogProvider interface to some external implementation (Xtensive.Adapters.log4net.LogProviderImplementation type).

<Xtensive.Core.IoC>
  <containers>
    <container>
      <types>
        <type type="Xtensive.Core.Diagnostics.ILogProvider, Xtensive.Core" mapTo="Xtensive.Adapters.log4net.LogProviderImplementation, Xtensive.Adapters.log4net" singleton="true"/>
      </types>
    </container>
  </containers>
</Xtensive.Core.IoC>

The last step is log4net configuration:

<log4net>
  <appender name="FileAppender" type="log4net.Appender.FileAppender">
    <file value="log-file.txt" />
    <appendToFile value="true" />
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date %-5level %logger - %message%newline" />
    </layout>
  </appender>
  <root>
    <level value="WARN" />
    <appender-ref ref="FileAppender" />
  </root>
<!-- To log warnings & errors from Xtensive.Storage.* loggers --> <logger name="Storage" additivity="false"> <level value="WARN" /> <appender-ref ref="FileAppender" /> </logger> <!-- To log all SQL statements --> <logger name="Storage.Providers.Sql" additivity="false"> <level value="ALL" /> <appender-ref ref="FileAppender" /> </logger> </log4net>

Having these configuration steps done, you’ll get DataObjects.Net & log4net bundle configured & working.

Friday, December 25, 2009

Logging, part 2. Architecture & configuration

The main goal was: how to make logging and its configuration easy for simple scenarios and in the meantime highly adaptable for complex ones.

In order to achieve the required level of flexibility most logging frameworks have the following components:

  • Loggers (named instances of some public class or interface (usually ILog) that provides developers with functionality to write diagnostic messages to).
  • Appenders (output destinations of above-mentioned loggers).
  • Log manager or log provider (usually a central access point of a framework. It resolves loggers by their names).

DataObjects.Net logging system follows exactly this pattern but bearing in mind that it must provide the possibility to plug-in any logging framework it introduces its own set of abstract components (actually loggers & log manager, but not appenders) which in fact just wrap up the plugged-in ones and simply redirect diagnostic messages to them. Moreover, in case when none of standalone logging frameworks is plugged-in, DataObjects.Net contains its own simple implementation of those components.

The main access point is the public static LogProvider class with one method LogProvider.GetLog(string logName), which is used to resolve a required ILog instance by its name. Once instance of ILog is obtained, it can be used to log Debug, Info, Warning, Error & FatalError messages through the corresponding methods.

It could be considered as a good practice when members from one namespace log their messages into the same logging space. This namespace-based approach is quite useful as usually a namespace contains a set of classes that are closely coupled and execute some shared piece of programming logic, therefore the idea to merge their diagnostic output in one log seems to be rather sensible. Due to this approach DataObjects.Net contains a set of predefined loggers for most frequently used namespaces, such as: Xtensive.Core, Xtensive.Storage, Xtensive.Storage.Building and so on. Each of these loggers has name which corresponds to its namespace except "Xtensive." prefix. Say, logger for Xtensive.Core namespace is named as "Core".

For usability reasons the above-mentioned namespaces contain public static class named Log which exposes the same set of logging methods as ILog interface. As you might understand, each of these static Log classes is no more than a connector between its consumers (classes which use it as a logger) and corresponding ILog instance that is transparently constructed on demand.

Configuring internal DataObjects.Net's log output

Internal logging subsystem is not as powerful as some well-known logging monsters but rather flexible and doesn't require any additional components. Configuration of DataObjects.Net's logging is made in application configuration file (app.config or web.config).

First of all, include Xtensive.Core.Diagnostics section into configSections section:

  <configSections>
    <section name="Xtensive.Core.Diagnostics" type="Xtensive.Core.Diagnostics.Configuration.ConfigurationSection, Xtensive.Core" />

The second step is to configure logs (appenders in terms of log4net):

<Xtensive.Core.Diagnostics>
  <logs>
    <!-- Use these settings for Xtensive.Storage.* logs -->
    <log name="Storage" events="Warning,Error,FatalError" provider="File" fileName="Storage.log" />
  </logs>
</Xtensive.Core.Diagnostics>

Note that each log has a name which is equal to the namespace where it is located except "Xtensive." prefix. This is true for logs from DataObjects.Net only and might not be true for logs from your own application.

Types of events: Debug, Info, Warning, Error, FatalError.

Types of providers: File (you need to provide file name as well), Debug, Console, Null (no logs at all, analogue of /dev/null), Error.

The example of log:

2009-12-17 00:00:02,052 DEBUG Storage.Providers.Sql - Session 'Default, #9'. Creating connection 'sqlserver://*****'.
2009-12-17 00:00:02,052 DEBUG Storage.Providers.Sql - Session 'Default, #9'. Opening connection 'sqlserver://*****'.
2009-12-17 00:00:02,052 DEBUG Storage.Providers.Sql - Session 'Default, #9'. Beginning transaction @ ReadCommitted.
2009-12-17 00:00:02,068 DEBUG Storage.Providers.Sql - Session 'Default, #9'. SQL batch: 
SELECT [a].[Id], [a].[TypeId], [a].[Name], [a].[Code], [a].[Description], [a].[LongDescription],
 [a].[IsForChildren], [a].[BasePrice], [a].[Price], [a].[SizeString], [a].[HasNoInnerCover]
 FROM [dbo].[Product] [a] ORDER BY [a].[Id] ASC
2009-12-17 00:00:02,068 DEBUG Storage.Providers.Sql - Session 'Default, #9'. Commit transaction.
2009-12-17 00:00:02,068 DEBUG Storage.Providers.Sql - Session 'Default, #9'. Closing connection 'sqlserver://*****'.

Looks pretty good, right?

In the next post I’ll describe how to use external logging framework with DataObjects.Net.

Wednesday, December 23, 2009

Logging, part 1. Introduction

In the next posts I’m going to describe how logging in DataObjects.Net is designed, how it works and how to configure and use it in most effective way. In the meantime, I’m writing exactly the same chapter in the manual, so this work will be paralleled, although I suppose that the blog version will be a bit more informal than manual’s one.

Let’s start then.

In general, logging is the feature most of software engineers use to track how the system works and analyze when it starts to behave in improper manner. It goes without saying that logging capabilities are essential for any product designed primarily for developers and software companies. But having decided that your framework must use some kind of logging, you immediately face up to another challenge: which logging system to use as there are plenty of them (log4net, NLog, etc.). Moreover, you might want to invent your own super-duper logging system.

This choice is rather simple for small products or libraries: they just use one of the most famous and simple in usage, i.e. log4net, or writes everything to some place that can be set up somewhere in configuration file, i.e. "C:\Debug\". But is this straightforward approach good enough for their customers that use these small standalone components to build something more complex and non-trivial?

In such cases the right word is "Transparent integration", it really matters how your small library can be integrated into large system, is its logging subsystem flexible enough to be easily integrated with logging framework that is used there? These are the questions DataObjects.Net development team was thinking about when logging subsystem was about to be implemented.

To be continued…

BTW, we are going to publish the updated localization sample with the generalized LINQ pre-processor that is used to automatically and transparently join localizable & localization entities and substitute calls to localizable properties soon. Stay tuned!

Wednesday, December 16, 2009

Localization support, part 4. Queries

Another interesting part in the localization support story (part 1, part 2 & part 3) is how to make queries for localizable objects.

Problem

The only problem here is the virtuality of localizable properties.

public class Page : Entity
{
  ...
  public string Title
  {
    get { return Localizations.Current.Title; }
    set { Localizations.Current.Title = value; }
  }

  public string Content
  {
    get { return Localizations.Current.Content; }
    set { Localizations.Current.Content = value; }
  }
  ...
}

As these are not persistent properties, domain model doesn’t contain even a bit of information about them nor Page table doesn’t contain corresponding Title & Content columns, therefore LINQ translator simply doesn’t know what to do when it encounters them during LINQ query parsing stage. The following query leads to InvalidOperationException:

var pages = Query<Page>.All.Where(p => p.Title=="Welcome");

Exception:
“Unable to translate '$<Queryable<Page>>(Query<Page>.All).Where(p => (p.Title == "Welcome"))' expression”.

And the Exception.InnerException shows us the detailed description of the failure:
“Field 'p.Title' must be persistent (marked by [Field] attribute)”.

Therefore, the query in order to be executable must be rewritten in the following way:

var pages = from p in Query<Page>.All
join pl in Query<PageLocalization>.All
  on p equals pl.Target
where pl.CultureName==LocalizationContext.Current.CultureName && pl.Title=="Welcome"
select p;

Certainly, it is not convenient to write such overloaded queries every time you want to filter or sort by localizable properties. The only way we can optimize it is to introduce some level of abstraction.

Solution

First step is to define LocalizationPair, a pair of target entity and corresponding localization:

public struct LocalizationPair<TTarget, TLocalization> where TTarget: Entity where TLocalization: Model.Localization<TTarget>
{
  public TTarget Target { get; private set; }
  public TLocalization Localization { get; private set; }
}

The next one is to build a class that hides the complexity of join and filter operation. I named this class as “Repository”, but frankly speaking it isn’t real repository as it doesn’t implement all functionality from well-known DDD Repository pattern. Anyway, here it is:

public static class Repository<TTarget, TLocalization> where TTarget: Entity where TLocalization: Model.Localization<TTarget>
{
  public static IQueryable<LocalizationPair<TTarget,TLocalization>> All
  {
    get
    {
      return from target in Query<TTarget>.All
        join localization in Query<TLocalization>.All
          on target equals localization.Target
        where localization.CultureName==LocalizationContext.Current.CultureName
        select new LocalizationPair<TTarget, TLocalization>(target, localization);
    }
  }
}

And this is how we can use these 2 classes in queries:

var pages = from pair in Repository<Page, PageLocalization>.All
where pair.Localization.Title=="Welcome!"
select pair.Target;

Simple and functional enough to use.

Conclusion

The above-mentioned sample is built on basis of DataObjects.Net 4.1, no changes were made to ORM itself to achieve the declared features and I think this is quite promising in terms of maturity and flexibility.

There are 2 ways how we are going to develop the very idea of localization:

  • LINQ extension API will be implemented. This will help us to connect custom LINQ query rewriters which will transparently alter queries in order to insert joins, filters and stuff. Particularly this feature will eliminate the necessity of LocalizationPair & Repository classes, defined in the sample. As soon as this functionality appears, I’ll update the sample and make a post on this topic.
  • The actual localization support on ORM level will be implemented at the same time with full-text search realization or a bit later because these features are interconnected.

Friday, December 11, 2009

Localization support, part 3. CRUD operations

In the previous posts we discussed the options for localization implementation on database level & on domain model level. Let’s continue the topic and see how CRUD operations can be applied to localized entities.

Scenarios

Generally, there are two scenarios:

1. In first you deal with localized properties as if they are regular ones and DataObjects.Net does all the other stuff in background. You act as there is no such notion as localization. You simply get and set values to properties as usual. In this case you deal with currently active culture and corresponding localization object. Switching of Thread.CurrentCulture property or localization scope usage are the best ways to do this.

var english = new CultureInfo("en-US");
var russian = new CultureInfo("ru-RU");
var welcomePage = new Page();

// Editing localizable properties through localization scope
using (new LocalizationScope(english)) {
  welcomePage.Title = "Welcome!";
  welcomePage.Content = "My dear guests, welcome to my birthday party!";
}

// Editing localizable properties through CurrentThread.CurrentCulture
Thread.CurrentThread.CurrentCulture = russian;
welcomePage.Title = "Здравствуйте!";
welcomePage.Content = "Добро пожаловать, дорогие гости! На базаре сейчас всё так дорого.";

Note, code that works with localizable properties, is the same that one that work with any other regular persistent properties.

2. In the second scenario your code knows that some localization takes place and wants to get or update localized properties for several cultures at a time. Say, you edit Page object from website administration system and want to see all available localizations for it. Therefore, you should have the possibility to get them all and to make some changes directly to the chosen ones. The approach with localization scope is not an option for such kind of task.

var goodbyePage = new Page();

// Editing localizations directly
goodbyePage.Localizations[english].Title = "Goodbye!";
goodbyePage.Localizations[english].Content = "Goodbye, my dear friends.";
goodbyePage.Localizations[russian].Title = "До свидания!";
goodbyePage.Localizations[russian].Content = "Надеюсь больше никогда вас не увидеть.";

Both scenarios are supported in the above-mentioned Localization sample and Page instances as well as PageLocalization instances are persisted transparently.

Results

Here are localizations for 2 Page instances:

Table

Note that first 2 columns are key columns. First one is string representation of CultureInfo, and the second on is a reference to localizable object. All other columns are localized versions of properties from Page class.

In the next post we’ll figure out what should be done to use LINQ with localized entities.

Thursday, December 10, 2009

Localization support, part 2. Domain modeling

In the previous post we discussed the theoretical possibilities of localization feature implementation in terms of physical organization on database level. Let’s continue the discussion on Domain model level.

Obviously, all these implementation approaches require some common infrastructure, such as automatic binding to Thread.CurrentCulture, temporary switching of current culture and so on. In that case, let’s start with this part.

Infrastructure

  1. To begin with, let’s think of what information do we need.
    First and the most necessary one is an instance of CultureInfo class which is used to represent current culture in particular application code block.
  2. Another point, not so obvious as first one, but also important is some kind of localization policy which describes what should be done if requested localization is not found. Say, you have localizations for “en-US” & “ru-RU” cultures but don’t have one for “fr-FR” culture. What should be done if someone switches Thread.CurrentCulture to “fr-FR” culture and tries to access localized properties? Should new localization for the specified culture be created or default one should be used? If so, which culture is default then?

To answer these questions the notion of immutable LocalizationContext class is introduced. It is defined in pseudo-code as follows:

public class LocalizationContext
{
  public CultureInfo Culture { get; }

  public string CultureName { get; }

  public LocalizationPolicy Policy { get; }

  public static LocalizationContext Current { get; }

}

LocalizationContext.Current property provides a programmer with valid current localization context everywhere it is required.

For now, LocalizationContext.Current is bound to Thread.CurrentThread.CurrentCulture property and changes its value each time current culture of current thread is being changed. Hence, if you want to temporarily change localization context (activate another culture) you are to change Thread.CurrentThread.CurrentCulture property and after doing some work revert it back, which is not robust nor convenient at all. To overcome this problem, LocalizationScope class is added. It acts as a disposable region where specified localization context is activated and after disposal it restores the previous localization scope value. Here is how it works:

// LocalizationContext.Current.Culture is en-US

using(new LocalizationScope(new CultureInfo("ru-RU"))) {
  // LocalizationContext.Current.Culture is ru-RU
  // do some work with ru-RU culture
}

// LocalizationContext.Current.Culture is en-US again

So now LocalizationContext.Current property logic must take into account the presence and configuration of currently active localization scope and fall back to Thread.CurrentCulture in case current localization scope is absent.

Modeling Domain

Say we have a Page class in Domain model with 2 persistent properties: Title & Content, both of them we want to make localizable. Then this is how we do it:

  1. We define PageLocalization - localization class for Page, which contains localized persistent properties. Its primary key consists of 2 fields: a reference to Page and a string representation of CultureInfo, which in turn can be represented as CultureInfo.Name.
  2. We define localizable properties in Page class as NOT persistent. They are no more than wrappers for appropriate localized persistent properties located in localized instance.

Here is localization for page class:

[HierarchyRoot]
public class PageLocalization : Localization<Page>
{
  [Field(Length = 100)]
  public string Title { get; set; }

  [Field]
  public string Content { get; set; }

  public PageLocalization(CultureInfo culture, Page target)
    : base(culture, target)
  {}
}

It inherits Localization<Page> class where key fields are declared.

And here is the Page class:

[HierarchyRoot]
public class Page : Entity
{
  [Field, Key]
  public int Id { get; private set; }

  public string Title
  {
    get { return Localizations.Current.Title; }
    set { Localizations.Current.Title = value; }
  }

  public string Content
  {
    get { return Localizations.Current.Content; }
    set { Localizations.Current.Content = value; }
  }

  [Field, Association(PairTo = "Target", OnOwnerRemove = OnRemoveAction.Cascade)]
  public LocalizationSet<PageLocalization> Localizations { get; private set; }
}

Localizable properties such as Title & Content redirect all calls to currently active PageLocalization which is accessed through Page.Localizations.Current property. What is LocalizationSet<PageLocalization> then?

Believe it or not, LocalizationSet<PageLocalization> is no more than common EntitySet<T> with some additional functionality:

public class LocalizationSet<TItem> : EntitySet<TItem> where TItem : Localization
{
  public TItem this[CultureInfo culture] { get; }

  public TItem Fetch(string cultureName)

  public TItem Current { get; }

  private TItem GetCurrent()

  private TItem Create(CultureInfo culture)
}

Here the decision what to do if localization for the current culture is requested, is made according to localization policy in current localization context.

In the next post we’ll try to figure out how to deal with CRUD operations, LINQ queries and localized entities.

Stay tuned.