Sergey Shishkin

on agile software development

Data Access Patterns @DNUG Cologne

Yesterday, during the 30th meeting of .NET UG Cologne, I gave a mini demo-talk on the Repository and Specification patterns for Data Access. There were no slides, just code.

What Is It All About?

I like to write as little code as possible and be able to write as simple code as possible. Especially when it comes to Data Access. This is what I want my Data Access code looks like:

var price = new ProductPriceRangeSpecification(300m, 500m);

var name = new ProductNameSpecification("Road Frame");

 

var products =

    (from p in repository.FilterBy(price.And(name.Negate()))

    orderby p.Name

    select p)

        .ToList();

How It Works?

The main character here is the Repository. I tried to keep its interface as small as possible (and it is still the biggest interface in the system):

public interface IRepository<T>

{

    void Save(T entity);

 

    void Delete(T entity);

 

    T Get(object key);

 

    IQueryable<T> AllEntities();

}

That’s really it. The rest of querying functionality will be added through extension methods. Here is where the FilterBy method comes from:

public static IQueryable<T> FilterBy<T>(

    this IRepository<T> repository,

    ISpecification<T> specification)

{

    return specification.Filter(repository.AllEntities());

}

I use extension methods extensively and don’t think I abuse them. In this case I clearly separated the contract needed to be implemented and the publicly available functionality built on top of it. Extension methods basically help to build a DSL around a very simplistic object model. And whenever I wonder where a method comes from, I hit F12.

Specifications appear on scene in supporting roles every here and there:

public interface ISpecification<T>

{

    Expression<Func<T, bool>> GetPredicate();

}


public
class ProductNameSpecification : SpecificationBase<Product>

{

    private readonly string query;

 

    public ProductNameSpecification(string query)

    {

        this.query = query;

    }

 

    public override Expression<Func<Product, bool>> GetPredicate()

    {

        return p => p.Name.Contains(query);

    }

}

The beautiful thing in this design is the fact that Specifications are not coupled to the database. They are expressed in terms of the Domain Model and belong to the Domain Model as well. And of course they can be tested against an in-memory implementation of the Repository.

Onion Or Cake?

All the code sits in a single C# project for simplicity, though is layered well.

In classic Domain-Driven Design the Repository abstraction belongs to the Domain layer. I however separate one additional Framework layer, which is a reusable part of the Domain layer across multiple applications and multiple Domains. Both Domain and Framework are kept clear of any infrastructure concerns.

The Infrastructure layer is where all that dirty NHibernate details are hidden. But the IRepository interface and Linq do a very good job to protect the application from NHibernate leaking into the Domain layer.

Here is what NDepend has to say on that matter:

Notice that Unit and Integration tests are separate. Unit tests don’t require any infrastructure to be in place, they can be run against pure Domain code.

What About Transactions?

I intentionally left the Unit Of Work out of the talk. Two reasons for that are the limited time of the talk and some technical problems that I have with the current implementation. So you also won’t find UoW and transactions in this code sample. I’m going to make transaction work in the next version. Stay tuned 😉

Requirements

The code sample is built against the AdventureWorks database. I use NHibernate as an O/R Mapper and Linq to NHibernate for queries. To make NHibernate mappings a bit simpler Fluent NHibernate is used. xUnit.Net is my testing framework of choice.

What About Feedback?

Download the code and tell me what you think.

Advertisements

Written by Sergey Shishkin

03.12.2008 at 14:47

Posted in Development

%d bloggers like this: