DEV Community

Cover image for Writing pipes and filters for fluent OR-mapping
Kosala (Nuwan) Perera
Kosala (Nuwan) Perera

Posted on • Originally published at keepontruckin.hashnode.dev on

Writing pipes and filters for fluent OR-mapping

I like the idea of fluent interfaces. It gives the whole thing a very Ubiquitous feel since the code becomes more understandable, say, two months from now like Uncle Bob says in the Clean Code.

// An example LINQ expression to filter all active products
// that belongs to a specified supplier.
db.Products
  .Where(p => !p.Discontinued && p.SupplierId == supplierId)
  .Take(10)
  .ToList();
Enter fullscreen mode Exit fullscreen mode

Simplifying query expressions

The first thing we are going to need is a lot of query expressions. I can decompose or consolidate query expressions so a little extract method magic is all that is necessary to get started. Kids' stuff.

// Better readability.
db.Products.Where(p => Active(p) && WithSupplier(p, supplierId));
// Better resuability.
db.Suppliers.Where(s => s.Products.Any(p => Active(p));
Enter fullscreen mode Exit fullscreen mode

That seems a little better since it allows reusing the query expressions. LINQ may look like the best thing that happened for .NET, but it's getting all kinds of help from our friends at C#. It's the extension methods.

Chaining query expressions

The other thing we're going to need is chaining. And that means extension methods. It's slightly obnoxious that extension methods require non-nested non-generic static classes. So what does each predicate have that can be used for chaining that the fluent interface requires? IQueryable?

// Use filters to create the pipes.
db.Products.Active().WithSupplier(supplierId)
  .Take(10)
  .ToList();
// Extension methods for `IQueryable<Product>`.
static class IQueryableOfProduct
{
    static IQueryable<Product> Active(this IQueryable<Product> query)
        => query.Where(p => !p.Discontinued);
    static IQueryable<Product> WithSupplier(
        this IQueryable<Product> query, int supplierId)
        => query.Where(p => p.SupplierId == supplierId)
}
Enter fullscreen mode Exit fullscreen mode

I can pass in the IQueryable<> domain entity type as the first parameter on which the method must operate. All I have to do is add the this modifier to the parameter and the C# compiler will do the rest for me. I'm not going to lie, you'd be amazed how tricky it is and if you return the wrong type, chaining breaks and nothing gets compiled.

Describing pipes and filters

Naming things is hard. But there are more problems. Not only are the extension methods should be reusable, but they must be understandable. And you do want the code to scream what kind of business there is. Using a domain-specific language to describe methods seems like the right answer. Indeed.

/KP

Top comments (0)