The mail goal of an API is to facilitate the use of commands and attributes to achieve certain functionality. Over time, an API may have evolved and become somewhat obscure on its usage which reduces its usability and increases the implementation time.
To clean/simplify an API, you can apply a fluent interface to it. This allows you to modify the API with a more descriptive interface which improves its usability. At the same time, this preserves the current API interface and eliminates the risk of introducing bugs to code that is already implemented. To get a better understanding, let‘s take a look at an order system. To create an order, an API often uses the following code:
Customer cust = new Customer() { CustomerId = 12900 };
OrderItem item1 = new OrderItem { Name = "Pen", PricePerUnit = .99, Quantity = 10, Sku = "1675" };
OrderItem item2 = new OrderItem { Name = "Notepad", PricePerUnit = 2.99, Quantity = 10, Sku = "5456" };
OrderItem item3 = new OrderItem { Name = "Marker", PricePerUnit = 1.99, Quantity = 10, Sku = "9836" };
OrderHeader oh = new OrderHeader();
oh.Customer = cust;
oh.AddItem(item1);
oh.AddItem(item2);
oh.AddItem(item3);
oh.Complete();
oh.Send();
That code sample is a simple API order system that is creating an order request with three items. You may argue that the code is really simple to follow, and there is no need to refactor, and I agree with you. However, we should think of a more complex API which may require you to add so many lines of code and call methods that are not that descriptive. With that in mind, let’s look now at how we can make the order creation a bit fluent:
OrderHeader.CreateOrder()
.WithCustomer(cust)
.WithItems(item1,item2,item3)
.CompleteAndSend();
You can see that the code reads more like a sentence or language than actual method calls. It is descriptive and easier to use which is our main goal. So how does the code for this look? Well, let’s see now:
interface FluentOrderHeader
{
FluentOrderHeader WithItems(params OrderItem[] item);
FluentOrderHeader WithCustomer(Customer cust);
void CompleteAndSend();
}
partial class OrderHeader : FluentOrderHeader
{
public static FluentOrderHeader CreateOrder()
{
return new OrderHeader();
}
public FluentOrderHeader WithItems(params OrderItem[] items)
{
foreach (OrderItem item in items)
{
this.AddItem(item);
}
return this;
}
public FluentOrderHeader WithCustomer(Customer cust)
{
this.Customer = cust;
return this;
}
public void CompleteAndSend()
{
this.Complete();
this.Send();
}
}
We first define the interface with the methods that can help our API become fluent. You should note that each call returns the instance reference. This allows for the chaining of the methods. The next step is to create a partial class of the OrderHeader
that implements our interface. This allows us to isolate the changes to the partial class without making any modifications to the current code. This new class handles the implementation changes using the existent API.
In my opinion, a fluent interface should be used to simplify obscure APIs which are often used in several projects. If the API is only use by one system, the effort to adapt a fluent interface may not be necessary. We need to keep in mind that a fluent implementation requires a bit more thinking, so it can actually provide a descriptive and fluent set of methods which allows any developer to easily follow it. Do not confuse this with just method chaining. The main difference between method chaining and fluent interface is that with the latter, we are trying to define a domain specific language that tries to target a specific task. Method chaining just facilitates the use of APIs by returning a reference, so it can be used in subsequent calls.