Starting with Fluent API

Last week, while trying out a new logging mechanism, Serilog, I stumbled upon a new style of API design. It is known as Fluent API and looked like an interesting topic to understand.

What is Fluent API?

This term was coined by object oriented programming guru Martin Fowler in 2005. Not so new 🙂 His idea was to develop client friendly code that is easy to understand (read) and easy to maintain.

Business processes are defined by developing mix of objects and then chaining them together via some internal domain specific language. LINQ is a perfect place to start with. The way multiple methods are chained together to create a SQL like business operation is an example of Fluent API. We keep on adding Where(), Select(), Take() etc. methods to our code statement and each operation returns IEnumerable object. This is commonly known as method chaining. See following example to understand it better:

var underAgeCustomerCount = Customers                
.Where(c => c.Age <= 18)
.OrderBy(c => c.FirstName)
.Count();

How it is done?

I’ll take one of my real life project as a sample and show how we can add a very basic Fluent interface to that.

Business Goal of the project

This project contains a policy engine where users can define policies based on which operations are performed on data. Users can create multiple policies and save them. When data passes through this policy engine, appropriate policy is kicked off and a specific operation is performed.

Each policy consists of an operation and set of filters. Consider filter as a SQL WHERE clause. It can have multiple filter statements and these statements are added together by either AND or OR. So a filter can look like this:

WHERE Name = 'Gaurav Sharma' AND Age > 18 AND City = 'HYD'

Above is a filter, having 3 statements and uses AND as separator.

Operation is a method that is executed if data matches with defined filter.

Current Implementation

There are POCO classes defined for Policy, Operation, Filter and FilterStatement. Then there is a PolicyManager class that provided functionality to Validate and Save policy. Code looks like this:

public class Policy
{
public Guid ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Filter Filter { get; set; }
public Operation Operation { get; set; }
}


public class Filter
{
public string Type { get; set; }
public List<FilterStatement> FilterStatements { get; set; }
}
public class FilterStatement
{
public string Statement { get; set; }
}
public class Operation
{
public string Name { get; set; }
}
 
public class PolicyManager
{
public void Validate(Policy policy)
{
// check for invalid properties in policy object
// throw exception in case of validation failures
}

public Policy Save(Policy policy)
{
// create policy and save to store

// create a new ID and update the object
policy.ID = Guid.NewGuid();

return policy;
}
}
 
This is very basic representation of what we are actually doing in our project but for this blog I think it is sufficient. Now if you have to write client code for this then it would look similar to:
 
var policy = new Policy();

policy.Name = "Sample policy";
policy.Description = "This is a sample policy";

var operation = new Operation();
operation.Name = "Create Order";

var statement1 = new FilterStatement();
statement1.Statement = "City = New Delhi";

var statement2 = new FilterStatement();
statement1.Statement = "CustomerName = Gaurav Sharma";

var statement3 = new FilterStatement();
statement1.Statement = "TotalAmount > 100000";

var filterStatements = new List<FilterStatement>();
filterStatements.Add(statement1);
filterStatements.Add(statement2);
filterStatements.Add(statement3);

var filter = new Filter();
filter.FilterStatements = filterStatements;

policy.Operation = operation;
policy.Filter = filter;

var manager = new PolicyManager();
manager.Validate(policy);
policy = manager.Save(policy);

Console.WriteLine("Successfully saved policy with ID {0}", policy.ID);

Remember good old ADO.NET days when we use to write this type of code for data operations. Remember DataConnection, DataReader, DataSet etc. It was painful.

We can clean this a little more by using different constructors and object initializes. Let’s skip that for now. Now we will add very simple Fluent interface implementation to our project and see how it improves client code.

public interface IFluentPolicyEngine
{
IFluentPolicyEngine CreatePolicy(string name, string description);

IFluentPolicyEngine PerformsOperationOnData(string operation);

IFluentPolicyEngine HavingDataFilter(string type);

IFluentPolicyEngine Where(string filter);

IFluentPolicyEngine Validate();

void Save();
}
 
See the return type defined for our new fluent interface methods. This helps us in chaining these methods together. Let’s implement this interface.
 
public class FluentPolicyEngine : IFluentPolicyEngine
{
public Policy Policy { get; set; }

public FluentPolicyEngine()
{
Policy = new Policy();
}
public IFluentPolicyEngine CreatePolicy(string name,
string description)
{
Policy.Name = name;
Policy.Description = description;
return this;
}

public IFluentPolicyEngine PerformsOperationOnData(string operation)
{
var op = new Operation
{
Name = operation
};
Policy.Operation = op;
return this;
}

public IFluentPolicyEngine HavingDataFilter(string type)
{
var filter = new Filter
{
FilterStatements = new List<FilterStatement>(),
Type = type
};
Policy.Filter = filter;
return this;
}

public IFluentPolicyEngine Where(string filter)
{
var filterStatement = new FilterStatement
{
Statement = filter
};
Policy.Filter.FilterStatements.Add(filterStatement);

return this;
}

public IFluentPolicyEngine Validate()
{
if (Policy.Operation == null)
throw new InvalidDataException("Operation is not provided.");

if (!(Policy.Filter != null
&& Policy.Filter.FilterStatements != null
&& Policy.Filter.FilterStatements.Count > 0))
throw new InvalidDataException("Filter is missing.");

return this;
}

public void Save()
{
Policy.ID = Guid.NewGuid();
}
}
 
This is again a very simple implementation of our interface but sufficient for this write up. Methods are simple and self explanatory. Now our new client code looks like:
 
var policyEngine = new FluentPolicyEngine();
policyEngine
.CreatePolicy("Fluent Policy", "Fluent policy sample")
.PerformsOperationOnData("Create Order")
.HavingDataFilter("All")
.Where("City = New Delhi")
.Where("CustomerName = Gaurav Sharma")
.Where("TotalAmount > 100000")
.Validate()
.Save();

Console.WriteLine("Successfully saved policy with ID {0}",
policyEngine.Policy.ID);
 
See how readable this code is now.
Some important points
  • Unless client developer understand Fluent API business domain, it is very tough to write this type of code. Think about a developer knowing nothing about SQL and then trying his hands on LINQ.
  • Debugging is little difficult in this type of client code. You can not put breakpoint on a specific method call. 
  • There are multiple ways of doing what I just showed you. For example, in C# we can use extension methods to achieve similar results.
  • Lot of configuration APIs provide fluent interfaces. Entity framework is one of them.
  • Although it simplifies client code, your API code can become complex. Keep a watch on that.

It is not easy to create efficient Fluent API. You have to think a lot. So think.then.code.

Download Code: http://1drv.ms/1khBvvD
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s