Monday, July 27, 2009

Generic method type parameter as Type parameter

Generics is another mechanism offered by the common language runtime (CLR) and programming languages that provides one more form of code re-use: algorithm re-use.
Jeffrey Richter, CLR via C# 2nd

However algorithm abstraction from data types it operates on is not the only usage scenario. Type parameter of a generic method can be treated as a parameter of Type. Consider the following example

// T IUnityContainer.Resolve<T>()
IService service = unityContainer.Resolve<IService>();

Unity container implementation uses supplied type parameter to resolve an instance of requested type from the container. It is beneficial as it allows us to read code naturally and it avoids unnecessary type casts.

It is quite often used when building fluent interfaces (in particular Method Chaining). Consider autofac container configuration example:

var builder = new ContainerBuilder();
builder.Register<Straight6TwinTurbo>().As<IEngine>().FactoryScoped();

The configuration can be read as a natural language sentence.

However it is only half of the truth. There are several scenarios where this approach doesn’t work so well.

Not all languages support generics, others may have syntax that makes them hard to read. For example the same thing written in VB.NET doesn’t have the same readability as in C#:

Builder.Register(Of Straight6TwinTurbo)().As(Of IEngine)().FactoryScoped();

On the other hand it is beneficial when type parameter is known at compile time and it may complicate more dynamic scenarios when it is deferred until runtime.

For example, Unity container has an Extension called StaticFactoryExtension. The purpose of this extension is to add the ability to register types within the container while deferring the instantiation of the type to a factory method. The extension is configured through IStaticFactoryConfiguration:

public interface IStaticFactoryConfiguration : IUnityContainerExtensionConfigurator
{
    IStaticFactoryConfiguration RegisterFactory<TTypeToBuild>(FactoryDelegate factoryMethod);
    IStaticFactoryConfiguration RegisterFactory<TTypeToBuild>(string name, FactoryDelegate factoryMethod);
}

It is used like this:

container.AddNewExtension<StaticFactoryExtension>();
IStaticFactoryConfiguration config = container.Configure<IStaticFactoryConfiguration>();
config.RegisterFactory<IProductService>(ProductServiceFactory.Create);

Note however that the factory registration API includes only generic methods. It makes harder to implement scenarios were calling code wants to enumerate types at runtime and register particular factory for them.

In order to solve this problem the API should provide non generic version in addition to generic one. For example, Unity container provides both (generic and non-generic) versions for Resolve method:

public interface IUnityContainer
{
    T Resolve<T>();
    object Resolve(Type t);

    //... other members elided for clarity
}

Summary:

  • CONSIDER audience of your API
  • CONSIDER providing non generic version of your API taking into account your audience and usage scenarios

Thursday, July 16, 2009

Scenario in terms of abstraction levels

Framework Design Guidelines promotes several principles but one of them is worth special attention:

Frameworks must be designed starting from a set of usage scenarios and code samples implementing these scenarios.

It allows framework designers to see their API from consumer’s perspective and the value the sight produces should not be underestimated. The framework designers are experts in subject area and thus they are cursed by this knowledge. That is why it is highly important to express mainline usage scenarios in terms that consumers are familiar with or can get easily to know (of course advanced scenarios may require much deeper knowledge in the subject area).

Unfortunately designers quite often rely on standard design methodologies (including object-oriented design methodologies) that are oriented more on maintainability than on usability of the API.

Recently I came across an API (or basically an approach promoted over the application) that is responsible for data persistence. Basically in common scenarios data persistence is used in both directions (read/write). From consumer’s perspective it looks like a single scenario. Suggested approach assumes that data is loaded through loaders and data changes are propagated back to data source using Unit of Work pattern:

// Represents some company information
internal class Company
{
    public int Id { get; set; }
    public int TrustLevel { get; set; }
}

// Loaders are responsible for data reading.
internal interface ICompanyLoader
{
    // Loads limitted set of data about companies that has highest 
    // profits.
    IEnumerable<Company> LoadTopPerformingCompanies(int count);
}

// Here goes Unit of Work pattern.
internal interface IUnitOfWork
{
    // Methods that are responsible for state changes tracking and writing out changes 
    // coordination.

    void RegisterNew(object obj);
    void RegisterDirty(object obj);
    void RegisterClean(object obj);
    void RegisterDeleted(object obj);
    void Commit();
    void Rollback();
}

Used like this:

// Loading data which is expressed in terms of business specific
var companies = companyLoader.LoadTopPerformingCompanies(10);

// Updating data which is expressed in terms of a selected 
// persistence layer implementation
foreach (var company in companies)
{
    company.TrustLevel++;
    unitOfWork.RegisterDirty(company);
}
unitOfWork.Commit();

So where is the problem? It seems working…

As mentioned from consumer’s perspective this is a single scenario. However the API expresses single scenario in terms of different abstraction levels:

  • Data loading is expressed in business specific terms (load top performing companies – companies that has profits for the last year more than the others)
  • Data updating is expressed in terms of state management (register object as dirty - updated)

This is the catch. In order for consumer to implement the scenario one needs to switch between abstractions which dramatically reduces productivity and on the other hand readability of the code.

In order to avoid mentioned problems a single level of abstraction must be used:

// It may be Table Data Gateway pattern 
interface ICompanyGateway
{
    // Loads limitted set of data about companies that has highest 
    // profits.
    IEnumerable<Company> LoadTopPerformingCompanies(int count);

    // Updates companies trust
    void IncreaseCompaniesTrust(IEnumerable<Company> companies);
}

...

// Loading and updating data which is expressed in terms of business specific
var companies = companyGateway.LoadTopPerformingCompanies(10);
companyGateway.IncreaseCompaniesTrust(companies);

Summary:

  • DO NOT mix different abstraction levels within single scenario
  • DO provide expected by the consumer terms required to implement the scenario