Fluent API Desing

Summary: Good APIs guide their users through a well-lit path, resulting in clearer code, fewer mistakes and better maintainability

Good APIs guide their users through a well-lit path, resulting in clearer code, fewer mistakes and better maintainability

What are Fluent APIs

In software engineering1, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL). The term was coined in 2005 by Eric Evans and Martin Fowler.

wikipedia

A Cascading API is designed to allow operations to be expressed via an unbroken sequence of chained method calls, which can be split over multiple statements. A Fluent API is a casscading API designed to always be expressed in a single statement

David Beaumont

Why ?

why do care about expressing something in a single statement ? what are the benefits using Fluent APIs ?

A well designed fluent API makes domain-specific operations more understandable & maintainable.

David Beaumont

Designing Fluent APIs

Type Uniformity

wether or not to return the same type at each point of the chain

Homogenous APIs

uses a single type, which is useful for order-independent parameters

var lineSplitter =  Splitter.on(',')
    .trimResults()
    .limit(4)


var lineSplitter =  Splitter.on(',')
    .limit(4)
    .trimResults()

Heterogenous APIs

uses different return type. they guide the user through series of distinct steps. thy can express more complex operations and reuse common sub-APIs

assertThat(multiMap)            // -> MultimapSubject
    .valuesForKey(testKey)      // -> IterableSubject
    .contains(testValues)       // -> Orderd
    .inOrder();

Fallibility

Fallible APIs need to let caller know what happens when an operation fails. There may be multiple methods for different cases.

Example for a Fallible API is the Guava ImmutableMap builder, which has a build method, that can throw a runtime exception if you insert the same key multiple times.

ImmutableMap.builder()
    .putAll(firstMap)
    .putAll(secondMap)
    .build()

there is no way to tell from this code, that the build method can throw an exception, which can be confusing. Fluent API (or APIs in general) should hide implementation details, but it should be explicit or unsuprising.

Later in the version 31 two new methods were added the API of ImmutableMap:

which makes it very clear, that building an ImmutableMap can throw an exception

Mutability

wether or not the API mutate the same object. Mutable APIs are usually homogeneous, and often reflect the classic builder pattern.

new StringBuilder()
    .append(key)
    .append("=")
    .append(value)
    .toString();

Immutable APIs are harder to misuse, but allocate intermediate instances, which can be costly.

for var line : lines {
    doSomethingWith(Splitter
        .on(',')
        .limit(4)
        .trimResults()
        .splitToList(line));
}

Tip: when using Immutable APIs, caching intermediate results, that is used repeatedly is effective, and allows semantic naming

var CSV_SPLITTER =  Splitter
        .on(',')
        .limit(4)
        .trimResults();

for var line : lines {
    doSomethingWith(CSV_SPLITTER.splitToList(line));
}

Fluent Utilities

TODO

Optional Parameters

valid no-op valie to bypass teh action of the method

comparison

FluencyType UniformityFallibilityReusabilityMutability
ImmutableMapCascadingHomogeneousFallibleReusableMutable
StreamsCascadingHeterogeneousInfallibleOne-ShotIt Depends…
SplitterItem1.3HomogeneousInfallibleReusableImmutable
FloggerIt Depends…HomogeneousInfallibleOne-ShotMutable
TruthFluentHeterogeneousInfallibleOne-ShotImmutable

Tips & Tricks

Resources

Footnotes

  1. something