Fluent APIs Make Developers Love Using Your Libraries

James Hickey - Nov 28 '18 - - Dev Community

Ever find yourself frustrated with 3rd party libraries that are supposed to be useful? Easy to use?

Ever gone back to some nice/reusable class that you built a few weeks ago only to realize you have no idea how to get started with it. Nothing obviously sticks out as a starting point?

So, you decide to check out the documentation only to spend an hour trying to figure out how to get started with the one thing you need the library for!

I've been there so many times!

Throughout my career, I've always wondered if there was a way to build reusable pieces that are just 100% plain and obvious to use. Something that just guides you through the proper usage.

Let's Learn From The Best

Let's try to learn from the best. The tools that are the easiest to use. Here are a few tools that I personally find are enjoyable to use:

  • Laravel (PHP Framework)
  • JQuery
  • .NET Core
  • LINQ

These represent, to me, some of the easiest-to-use yet powerful tools I've encountered in my career.

I don't use JQuery anymore, nor will I (by choice). But everyone would agree that JQuery made working with the DOM enjoyable compared to the native tools at the time.

So - what do all of these tools have in common? What makes them so easy to use?

They are Fluent APIs.

Eh? What's A Fluent API?

The term "fluent" has the connotation that "fluent API" somehow relates to (spoken) languages. You can be fluent in English, for example.

Fluent interfaces are a style of programming that focuses on how your code reads easily and naturally - like you are reading a book.

Readability

For example, in .NET Core you might find code like this:

app.UseDeveloperExceptionPage()
    .UseDatabaseErrorPage();
Enter fullscreen mode Exit fullscreen mode

This is very easy to understand and to read. You intend to use the developer exception page (which has nice debugging info for developers). You also wish to use some special error page when you get database errors.

Unlocking New Methods

Let's look at another example:

services
    .AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Enter fullscreen mode Exit fullscreen mode

In this example, calling the AddMvc method unlocks access to other methods that only make sense within that specific context.

This to me is the most powerful aspect of fluent interfaces - they only let you use what makes sense to use within the current context.

Other Benefits

When a fluent API is done properly you shouldn't be able to:

  • Call methods in the wrong order (ever used code like this?)
  • Be confused where to start (since the IDE intellisense will tell you what methods are available to "get started")
  • Call methods in a specific chain partially. (E.g. "You forgot to call the "Done" method of some class so it never performed it's logic)

If you want to continue reading about these ideas you can read this post where I discuss more benefits you get with fluent APIs.

Applied To A Real Library

For the reasons above (and the reasons in the post I linked to) I opted to use a fluent API when writing Coravel. I think this is one of the main reasons that people are using it - it's so easy to work with.

For example, can you figure out what this code is intending to do?

code

This is how easy code written in a fluent style can be to understand. There's no mistaking what this code does.

Wizard-like Code Completion

Take this example of me using Coravel in Visual Studio Code:

code

When someone wants to schedule something they get access to new methods. These methods are only ever available in the context of scheduling something.

There's no bleeding of methods from other contexts here.

After selecting the frequency those methods are gone and you get access to a new group of selections:

code

Let's Build A Fluent API Right Now!

So you're dying to know - how do I actually build one?

Building basic fluent APIs can actually be really simple. The key "trick" is that the methods in your class can return an interface that restricts what methods the caller can use.

Here's what a working example might look like:

public class MyFluentClass : ICalculation, IResult
{
  private int _value;
  private int _result;

  private MyFluentClass() { }

  public static ICalculation WithValue(int value)
  {
    return new MyFluentClass
    {
      _value = value
    };
  }

  public IResult Add(int toAdd)
  {
    this._result = this._value + toAdd;
    return this;
  }

  public IResult Subtract(int toSubtract)
  {
    this._result = this._value - toSubtract;
    return this;
  }

  public int Result() => this._result;
}

public interface ICalculation
{
  IResult Add(int toAdd);
  IResult Subtract(int toSubtract);
}

public interface IResult
{
  int Result();
}
Enter fullscreen mode Exit fullscreen mode

Things to notice:

  • Use a private constructor so the caller is forced to use the static factory method
  • The static factory method has access to the instance's private members
  • WithValue returns the interface ICalculation. This is what begins to restrict what methods the caller can use.
  • Add and Subtract return the interface IResult. This is what unlocks the next series of methods in the chain (which happens to be Result).

Here's what it looks like in action:

1

2

3

Conclusion

Thanks for taking the time to go through this - I hope you learned something new and enjoyed the content!

By the way, this is part 2 of my series "What I've Learned So Far Building Coravel". Here are the other parts of the series:

Part 1 - What I've Learned So Far Building Coravel (Open Source .NET Core Tooling)

This post originally appeared on builtwithdot.net's blog.

Navigating Your Software Development Career

An e-mail newsletter where I'll answer subscriber questions and offer advice around topics like:

✔ What are the general stages of a software developer?
✔ How do I know which stage I'm at? How do I get to the next stage?
✔ What is a tech leader and how do I become one?

Sound interesting? Join the community!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .