Practical .NET

When To Build Fluent Interfaces for Re-Use and Clarity

Fluent methods are a hot design idea and they can improve the readability of your code. However, they only make sense in specific scenarios. Here are some criteria to help you decide if you should be creating a fluent interface, and some design guidelines for when you do.

One of the comments that cropped up in response to my column on commenting raised a key question: When descriptive method names get very long, do they actually help make your code easier to read? If, for example, you have code filled with method names like CheckInventoryLevelForProductAndCreateASalesOrderOrABackOrder, have you made life easier for the next programmer?

The Jet Propulsion Laboratory's programming guidelines (PDF here) recommend keeping any method to a maximum of 60 lines so that all of the method's code fits on a page at once. A similar guideline might reasonably be applied to line length -- and I say this as someone who once discovered that COBOL has a 64-character limit on subroutine names. And that's ignoring whether normal human beings can parse a 64-character word (unless, of course, you're Welsh).

But the real issue is that a method with a name that long is probably doing too much. If, for example, a developer just wants to create a sales order and never create a back order, the method is useless to them. Instead, following the single responsibility principle, I'd create multiple methods, each of which does one thing: CheckInventoryLevelForProduct, AddProductToSalesOrder, and CreateABackOrder. In addition to eliminating the long-name problem, the three new methods can be mixed and matched to support a wider variety of scenarios. These three methods would also be much simpler than the original method and, as a result, would be easier to understand, explain, test and discuss.

However, it would be naïve to claim that writing simpler methods will make a complex application more understandable. While these simpler methods would be less complex than the original, they'd also add complexity to the application by generating more methods. Personally, I prefer to create complex applications by assembling lots of simple parts, but you may disagree.

As an example of a problem created by having more methods, consider that it's entirely possible that the original, more complex method was the "typical" process -- what most developers want to do most of the time. Now, those developers have to coordinate the three methods. There is an answer to the "typical" issue: The Façade pattern, which has you provide a simple way to work with a complex subsystem to support the typical scenarios. A Façade for this system would handle combining the three methods for the typical process while allowing developers doing the "atypical" thing to combine those three methods in other ways. Rather than give the Façade method the original long name, I'd call it something like DefaultOrderProcess and add a comment at the start of the method that describes the business assumptions behind whatever the "default order process" is (along with a date so that anyone who reads the comment can judge how out-of-date that description of the process is).

Fluent Interfaces
But the existence of those three methods creates problems that the Façade doesn't address, the most important of which is, "How should these methods should work together?" I can imagine code like this:

Public Function DefaultOrderProcessing(
          prod As Product, Quantity as Integer)
  If quantity => Product.InventoryLevel Then
    Dim so As SalesOrder
    so = SalesOrder.CreateSalesOrder
    so.AddProduct(prod)
  Else
    Dim bo As BackOrder
    bo = BackOrder.CreateBackOrder
    bo.AddProduct(prod)
  End If

But there's another design which looks like this:

If quantity => Product.InventoryLevel Then
  SalesOrder.CreateSalesOrder.AddProduct(prod, quantity)
Else
  BackOrder.CreateBackOrder.AddProduct(prod)
End If

This second design is an example of a fluent API (named from the idea that the methods in the API flow into one another). Technically, a fluent interface is just simple method chaining. What makes it "fluent" is the intent behind writing the code this way: A fluent interface is one designed to improve readability. There are even some guidelines for implementing fluent APIs:

  • Each method returns the same object (SalesOrder in this case).
  • The first object establishes the context for the process.
  • The last method returns nothing and finalizes the process.

However, all of these are guidelines rather than requirements. The key issue is to keep your focus on making code understandable.

Because the fluent methods operate on the context object, fluent interfaces are usually used to configure objects rather than, as my example did, to perform processing. A more typical example of fluent programming might be this code that sets up a SalesOrder:

so = SalesOrder.CreateSalesOrder
so.AddProduct(prod, quantity).Expedite.SetDiscount

If you're willing to violate the guideline on having the final method return nothing, you could rewrite the code like this:

so = SalesOrder.CreateSalesOrder.AddProduct(prod, quantity).Expedite.SetDiscount

As the number of chained methods increases, there comes a question of whether the resulting statement is any easier to read than the lengthy method name. However, unlike a method name, that chain of methods can be broken up over multiple lines to improve readability, as this example does:

so = SalesOrder.
CreateSalesOrder.
AddProduct(prod, quantity).
ExpediteOrder.
DiscountOrder

I'll add a guideline of my own here: Method names should also be expressive when read by themselves. A fluent interface could be written like this:

so = SalesOrder.
CreateSalesOrder.
AddProduct(prod, quantity).
With.
Expedite.
Discount

With this design, some programmer will, eventually, be faced with methods obscurely called With, Expedite and Discount. In addition to ensuring understandability in the code that uses the methods, I'd want the individual methods to be understandable also. I wouldn't have a method called With and would prefer methods with names like ExpediteOrder and DiscountOrder.

Runtime Versus Design Time
However, this raises the question of whether this design makes sense when it comes to creating an application. Is this strategy superior to other tools already in your toolbox? For the configuration example, the same goals could be accomplished with property settings, using code like this:

so = SalesOrder.CreateSalesOrder
so.AddProduct(prod)
so.Expedite = True
so.Discounted = True

Even if I assume that calling methods is preferable to setting properties, there's another issue: These fluent calls must be set up at design time and frozen in code. At runtime, you may need a more flexible system. For instance, if you're not careful, your fluent interface could force you to write code like this:

Public Function DefaultOrderProcessing(
          prod As Product, Quantity as Integer, 
            expedited As Boolean, discounted as Boolean)
so = SalesOrder.CreateSalesOrder
If expedited And discounted Then
    so.AddProduct(prod, quantity).ExpediteOrder.DiscountOrder
ElseIf expedited And Not discounted Then
    so.AddProduct(prod, quantity).ExpediteOrder
ElseIf Not expedited And discounted Then
    so.AddProduct(prod, quantity).DiscountOrder
Else
    so.AddProduct(prod, quantity)
End If

On the other hand, using properties to configure the SalesOrder would support code like this:

Public Function DefaultOrderProcessing(
          prod As Product, Quantity as Integer, 
           expedited As Boolean, discounted as Boolean)
so = SalesOrder.CreateSalesOrder
so.AddProduct(prod)
so.Expedite = expedited
so.Discount = discounted

This leads to my primary question: When ensuring readability and clarity, when does a fluent API make sense, without sacrificing other goals of good coding practice?

Criteria for Fluent APIs
For me, when deciding on a fluent API, I consider three issues. The first is whether my chained method will, when passed a parameter, do nothing. Going back to my previous example, the expedited and discounted flags could cause the ExpediteOrder and DiscountOrder methods to do nothing if the flags are set to False. With that condition in place, I could string together all the methods every time:

so.AddProduct(prod, quantity).ExpediteOrder(expedited).DiscountOrder(discounted)

Driving your methods with parameters is attractive. The more parameters that your fluent methods accept, the more flexible those methods can be. The more flexible those methods are, the more likely it is that you can build complex method chains at design time without painting yourself into a corner. However, the trade-off is that more flexible methods are going to be more complex -- and my goal is to have lots of simple methods.

Furthermore, as the number of parameters required by the method increases, the readability of the code goes down -- and readability is the point of the fluent interface. Taking that into account, the second case when fluent APIs make sense is when the SalesOrder object holds the flags that control whether the order should be expedited or discounted and where the chained methods have access to that object. That reduces the number of parameters I have to pass to the fluent method, and helps keep the code readable. This condition is met if the chained methods are implemented as extension methods, because extension methods are automatically passed the object from which they're being called.

But in that second case, why not embed the code that handles discounting and expediting in the SalesOrder object, since it has the necessary data? There are two characteristics that would cause me to create a fluent API, rather than embed the code in the SalesOrder object. The code for expediting/discounting would have to:

  • Be used on many objects that don't share an inheritance structure, so I can't put the code in some base class
  • Be so complex that it would be better managed in a simpler class of its own, rather than making the SalesOrder object more complex

In other words, while I value the readability of a fluent interface, I'm not willing to add complexity in the form of more objects and methods just to get there. I want my fluent API to also simplify my code and not make it unnecessarily complex. Again, you're free to disagree; you may feel that the readability of the fluent interface justifies creating separate extension methods, even when the code could be integrated into the object itself.

However, considering a method that applies to several different objects does lead to the third condition when implementing a fluent interface -- when the fluent method consists of code that's used by a number of different classes. For example, a sales order and a back order will always have products added to them, so I could see moving the code that adds product into a fluent method:

BackOrder.CreateOrder.AddProduct(prod, quantity)
SalesOrder.CreateOrder.AddProduct(prod, quantity)

Even then, if the BackOrder and SalesOrder objects inherited from some common base object whose code I have access to, I'd be tempted to put the code in that base object. As it is, for my AddProduct to work as an extension method, I'd either need to create two extension methods (one for the SalesOrder class and one for the BackOrder class), or have the SalesOrder and BackOrder classes share an interface that I could write a single AddProduct method for. If I have to write a separate method for each class, I'd be tempted just to give each class their own AddProduct method rather than write a separate fluent API method.

If you put this all together, I would use a fluent interface to:

  • Hold useful code (usually configuration code) that crosses two or more classes
  • Where those classes don't share a common base class
  • And that code is either very similar for all of the classes that use it
  • Or the code is so complex that I don't want to put in the class itself

Those restrictions make fluent APIs a niche solution…but a large enough niche to be worth filling. So, when building a fluent API, I would:

  • Implement my methods as extension methods that leveraged properties on the context class so that they require few parameters.
  • Write the methods so that, under some conditions, they will do nothing.

Fluent interfaces are a useful tool to keep in your code design pocket. You just want to make sure you're using this tool when it's appropriate and not just because it's cool. Next month, I'll take a look at building some fluent API methods.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

  • TypeScript Tops New JetBrains 'Language Promise Index'

    In its latest annual developer ecosystem report, JetBrains introduced a new "Language Promise Index" topped by Microsoft's TypeScript programming language.

Subscribe on YouTube