What is a Fluent API?

A Fluent API is an object-oriented design that allows code to be written in a way that mimics natural, human-readable language.
Some Fluent API code, floating through the ether.

An example - Making a Cake

Let us suppose we wish to write an API for baking a cake. We can call it CakeMaker. The sorts of steps we would like CakeMaker to allow might be:

  • Gathering ingredients.
  • Preparing them.
  • Mixing everything together for the right amount of time.
  • Baking the cake, at the appropriate temperature.
  • Eating a slice.
  • Perhaps finishing off with a well-earned snooze.

CakeMaker Rules

There are also some rules which govern how we make the cake:

  • We need to do the steps in the right order. For example, we cannot prepare the ingredients before we have gathered them.
  • Although, there might be some leeway to do some steps in a slightly different order. Or, do them more than once. For example, perhaps the ingredients will need to be mixed again, for a bit longer.
  • We would like to give some more detail on how some of the steps will be carried out, such as how long the cake should be baked for and at what temperature.
  • We want each cake 'instance' to be self-contained so, for example, the ingredients from one cake do not become mixed up with the ingredients for another.

The 'CakeMaker' Fluent API

A Fluent API allows us to address all these asks:

CakeMaker
    .gatherIngredients(
        // Vegan alternatives possible
        ["flour", "eggs", "sugar", "butter"])
    .prepareIngredients()
    .mixIngredients(minutes(10))
    .bake({
        temperatureInCelsius: 100,
        time: minutes(40)
    })
    .sleep(minutes(20))
    .eat(Pace.Fast);
Examples are written in TypeScript due to its terse nature, especially when passing arrays and objects as method parameters.

What is happening here is immediately clear. We can see that each of the steps is being carried out in the order we would expect. Furthermore, the additional information passed to each step is also easy to read.

Ensuring the correct order

What happens if we try to execute the steps in the wrong order? A Fluent API, if written to do so, can tell you when things are going wrong:

CakeMaker
    .eat(Pace.Fast)
    .gatherIngredients(
        // Vegan alternatives possible
        ["flour", "eggs", "sugar", "butter"])
    .prepareIngredients()
    .mixIngredients(minutes(10))
    .bake({
        temperatureInCelsius: 100,
        time: minutes(40)
    })
    .sleep(minutes(60));
Here, we are trying to have our cake and eat it which, sadly, is not possible.

We can see above that the eat method call has a red underline, indicating an error. This is because this Fluent API has been written such that the first step has to be gatherIngredients.

Of course, all the type-safety we would expect for any method is still present. This is what happens if we try to call the minutes utility function, which expects a number, with a string:

CakeMaker
    .gatherIngredients(
        // Vegan alternatives possible
        ["flour", "eggs", "sugar", "butter"])
    .prepareIngredients()
    .mixIngredients(minutes("10"))
    .bake({
        temperatureInCelsius: 100,
        time: minutes(40)
    })
    .sleep(minutes(60))
    .eat(Pace.Fast);

You would get this function parameter type safety without Fluent APIs. But this shows that, in tandem with standard, explicitly typed language features, a Fluent API can make it super clear exactly how your code is meant to be used.

Bending the rules

We considered, in the list of rules above, that we might want to do some steps in a different order, or even repeat some steps more than once. A Fluent API can allow this, too, whilst still preventing everything from turning into a free-for-all.

To illustrate, a single Fluent API can be written to work for all these:

Eating cake mix
CakeMaker
  .gather(...)
  .prepare(...)
  .mix(...)
  // Hmmm... Cake Mix!
  .eat(Pace.Fast));
Baking the cake more than once
CakeMaker
  .gather(...)
  .prepare(...)
  .mix(...)
  .bake({
    temperature: 100,
    time: minutes(40)
  })
  // Thought it was
  // Fahrenheit 😬
  .bake({
    temperature: 100,
    time: minutes(20)
  })
  // Why won't it cook?
  .bake({
    temperature: 200,
    time: minutes(20)
  })
  // It's... crunchy
  .eat(
    Pace.Uncommitted);
Making two types of cake
CakeMaker
  .gather(
    ingredients)
  // Sponge cake
  .prepare(
    spongeIngredients)
  .mix(...)
  // Chocolate cake
  .prepare(
    chocIngredients)
  .mix(...)
  // Bake them both
  .bake(...)
  .eat(...);
Taking it easy, snoozing at multiple points
CakeMaker
  .gather(...)
  // So tired...
  .sleep(minutes(20))
  .prepare(...)
  // Should've gone to
  // bed earlier
  .sleep(minutes(20))
  .mix(...)
  // ...zzzz
  .sleep(minutes(20))
  .bake(...)
  // Am I ill?
  .sleep(minutes(20))
  // ...no, just hungry.
  .eat(...);

It is clear to see that a Fluent API can be written to allow a lot of flexibility. There are, however, some limitations ...

Limitations

Due to the underlying Object-Oriented Programming constructs that Fluent APIs rely upon, the following limitations apply:

The Initiating Method cannot be repeated

The Initiating Method refers to the first method in the Fluent API method chain.

In our CakeMaker example, this would be the .gather(ingredients) method.

It can only be called at the start, and it can only be called once.

The Executing Method cannot be repeated

The Executing Method is the final method in the Fluent API method chain.

The .eat() method in our CakeMaker is an example of this.

It, too, can be run just the once, and only as the final step.

A workaround

What if you do want to repeat either the first or last steps more than once? One way to make this happen is to add extra placeholder methods at the start and end of the method chain.

For example:

  • The Initiating Method could be called init()
  • The Executing Method could be called exec()

... and then what were formerly the start and end methods can now be repeated (as long as the Fluent API is written to allow this):

CakeMaker
    .init()
    // 'gather' can now be repeated:
    .gather(...)
    .gather(...)
    .prepare(...)
    .mix(...)
    .bake(...)
    // 'eat' can now be repeated:
    .eat(...)
    .eat(...)
    .exec(...);

Adding extra methods like this would usually require a lot of code changes, but the ability to easily re-order methods in Fluent API Generator makes the work comparatively trivial.

Method signatures must be unique

The methods used to build Fluent APIs should have unique signatures. They are, under the surface, object-oriented class methods and unique signatures for these is a standard requirement. This would usually be achieved by simply having different method names for each step, something you will likely want to do anyway for the majority of cases.

For Java and C#, repeating the same method name is allowed as long as the type(s) passed to each method are different.

The following would be allowed, for example. Although chop() is repeated, the first time it has no parameters (presumably because there is a default 'chop size'), whereas the second time it takes a Size parameter:

PrepareIngredients
    .chop()
    .chop(Size.Fine)
    .blanche();

A further restriction for TypeScript

TypeScript does not allow repeated method names, even if the method signatures are unique. The above example would not work for TypeScript. Instead, you would have to use different method names, perhaps chop() and chopSomeMore(Size.Fine).

Fluent API Generator will allow you to use the same method signature or name a second time. It is up to you to decide when this might cause issues with your chosen language.

Repeated step sequences are necessarily infinite

One or more steps can be looped. A set of steps can be looped within another set of steps. And, two sets of looped steps can overlap.

Here are some examples that illustrate this:

// Repeating a single step:
StepTaker
    .start()
    .stepA().stepA().stepA()
    .finish();

// Repeating two steps:
StepTaker
    .start()
    .stepA().stepB()
    .stepA().stepB()
    .finish();

// Mixing single and two step repeats:
StepTaker
    .start()
    .stepA().stepA().stepA()
    .stepA().stepB()
    .stepA().stepB()
    .finish();

// Overlapping repeats:
StepTaker
    .start()
    .stepA().stepB()
    .stepA().stepB().stepC()
    .stepB().stepC()
    .finish();

These examples are only the start; more complex arrangements are possible, allowing several loops to overlap, for example, or loops that are nested several levels deep.

However: it is not possible to limit the number of times a loop is repeated.

Yes, you could write code to throw an exception if a step is repeated, say, more than once. But, this would not give you the in-context red underline that tells you that only two steps are allowed, while you are coding.

To illustrate, this is not possible:

// Indicating 'only one repeat allowed' is not possible:
    StepTaker
    .start()
    .stepA()
    .stepA()
    .stepA() // This error underline will never appear
    .finish();

For the most part this will not be an issue. But, if you want to signify to your users that a step can only be repeated a certain number of times ... a Fluent API won't help you.

If you do need some control over how many times an operation is carried out, an option is to use non-repeating steps and a different step name for the second step:

StepTaker
    .start()
    .stepA()
    .stepA_Again()
    .finish();

Visualising looped steps

Fluent API Generator will give you a visual representation of which steps can be looped. If the Highlight Circular Chains option is checked then you will see permitted loops indicated:

A method chain with two circular methods

Here we see two loops which overlap. There is more detail on this in the Help popup available in the Examples section of Fluent API Generator.

Aren't all these limitations a bit... limiting?

Yes! For these reasons, and others described on the Why page, a Fluent API will not always be the right choice.

Nevertheless, Fluent APIs can bring great clarity of intent for your users in the right circumstances. They are found in many popular frameworks, as is also described on the Why page, and it is highly likely you have already used them.

Try it out and see

Building a complex Fluent API from scratch, only to discover it is not suitable for your use case, can be a frustrating waste of time. Fluent API Generator makes it trivial to try out some examples, to see if a Fluent API will work for you.

The 'Pseudo-Fluent' API

A true Fluent API gives you control over the order in which methods are called, and how many times.

There is a lesser equivalent to this which gives you method chaining like a full Fluent API, but does not provide any additional guidance on ordering or looping.

Functions can be called in any order, repeatedly, or not at all:

CakeMaker
    .eat(Pace.Fast)
    .eat(Pace.Fast)
    .eat(Pace.Fast)
    .bake({
        temperatureInCelsius: 100,
        time: minutes(40)
    })
    .mixIngredients(minutes(10))
    .gatherIngredients(
        // Vegan alternatives possible
        ["flour", "eggs", "sugar", "butter"])
    .prepareIngredients()
    .sleep(minutes(60));
Cake insanity.

There would be no red underlines indicating problems, and no way to know anything is amiss until you try to run the code... or, even worse, your users try to run the code.

For some scenarios, where order really doesn't matter, this is fine. The Pseudo-Fluent API still gives clarity as to what is being executed, and in what order.

For languages that lack static typing, such as JavaScript, the Pseudo-Fluent API is far more common. Large parts of jQuery, for example, use Pseudo-Fluent APIs liberally. This works well as it suits the super-fluid nature of DOM manipulation.

It is actually possible to write a full Fluent API in JavaScript. But, this requires using a more functional approach and is beyond the scope of this article.

Lastly, if building it from scratch, a Pseudo-Fluent API is comparatively simple to write. Indeed, the journey to a full Fluent API begins with a Pseudo-Fluent API, as is described on the How page.

In many cases, a full Fluent API would be a better choice than a Pseudo-Fluent API, but the comparatively complex code required is a barrier.

Fluent API Generator changes that.