Specifications

Direct access to the IDocumentStorage<TDocument> or IDocumentStorage provokes uncontrolled increasing number of query types. If you still working in this style after for a while you will see that the same queries are implemented in different ways and one query works fine but its analogue is very slow. Then you will lose control and not able to enumerate which queries go to the storage. All of these provokes bugs and makes support difficult. In fact an application as a rule has only few typical queries and it will be fine having them in one place.

Repository and Specifications patterns come to the rescue. InfinniPlatform implements these patterns and provides two base classes: Specification<TDocument> (for typed context) and Specification (for dynamic context).

Specifications for Typed Documents

Suppose we need to organize a storage of some articles and we know each article has date, author, title, text and can be in two states: draft and published.

class Article : Document
{
    public DateTime Date { get; set; }
    public string Author { get; set; }
    public string Title { get; set; }
    public string Text { get; set; }
    public ArticleState State { get; set; }

    // ...
}

enum ArticleState
{
    Draft = 0,
    Published = 1
}

And in the most of the cases we need to fetch published articles for the specified period:

 class GetPublishedArticles : Specification<Article>
 {
     private readonly DateTime _start;
     private readonly DateTime _end;

     public GetPublishedArticles(DateTime start, DateTime end)
     {
         _start = start;
         _end = end;
     }

     public override Expression<Func<Article, bool>> Filter =>
         a => a.Date >= _start && a.Date <= _end
              && a.State == ArticleState.Published;
 }

After that it will be right to create appropriate repository of the articles:

 interface IArticleRepository
 {
     IEnumerable<Article> GetArticles(ISpecification<Article> specification);

     // ...
 }

 class ArticleRepository : IArticleRepository
 {
     private readonly IDocumentStorage<Article> _storage;

     public ArticleRepository(IDocumentStorageFactory factory)
     {
         _storage = factory.GetStorage<Article>();
     }

     public IEnumerable<Article> GetArticles(ISpecification<Article> specification)
     {
         return _storage.Find(specification.Filter).ToList();
     }

     // ...
 }

And use it to fetch articles:

IArticleRepository repository;
DateTime start, end;

// ...

ISpecification<Article> specification = new GetPublishedArticles(start, end);
IEnumerable<Article> publishedArticles = repository.GetArticles(specification);

Specifications for Dynamic Documents

Let’s consider the same example as above but in dynamic context. Declare specification:

 class GetPublishedArticles : Specification
 {
     private readonly DateTime _start;
     private readonly DateTime _end;

     public GetPublishedArticles(DateTime start, DateTime end)
     {
         _start = start;
         _end = end;
     }

     public override Func<IDocumentFilterBuilder, object> Filter =>
         a => a.And(a.Gte("Date", _start), a.Lte("Date", _end),
                    a.Eq("State", ArticleState.Published));
 }

Declare the repository of the articles:

 interface IArticleRepository
 {
     IEnumerable<DynamicDocument> GetArticles(ISpecification specification);

     // ...
 }

 class ArticleRepository : IArticleRepository
 {
     private readonly IDocumentStorage _storage;

     public ArticleRepository(IDocumentStorageFactory factory)
     {
         _storage = factory.GetStorage("Article");
     }

     public IEnumerable<DynamicDocument> GetArticles(ISpecification specification)
     {
         return _storage.Find(specification.Filter).ToList();
     }

     // ...
 }

And use it to fetch articles:

IArticleRepository repository;
DateTime start, end;

// ...

ISpecification specification = new GetPublishedArticles(start, end);
IEnumerable<DynamicDocument> publishedArticles = repository.GetArticles(specification);

Composing Specifications

The specification classes Specification<TDocument> and Specification override !, & and | operators and implement, respectively, negation, conjunction and disjunction. Thus you can compose existing specifications instead of creating new.

// Not
var notSpecification = !specification;

// And
var andSpecification = specification1 & specification2 & specification3;

// Or
var orSpecification = specification1 | specification2 | specification3;