A use case for the Immutable Fluent Builder design pattern variant.

The Fluent Builder design pattern is popular in object-oriented programming. Most programmers use the mutable variant, while I favour the immutable alternative. The advantages of Immutable Fluent Builders, however, may not be immediately clear.

"I never thought of someone reusing a configured builder (soulds like too big class/SRP violation)."

It inspires me when I encounter a differing perspective. Could I be wrong? Or did I fail to produce a compelling example?

It's possible that I'm wrong, but in my my recent article on Builder isomorphisms I focused on the pattern variations themselves, to the point where a convincing example wasn't my top priority.

I recently encountered a good use case for an Immutable Fluent Builder.

Build links #

I was developing a REST API and wanted to generate some links like these:

  "links": [
      "rel": "urn:reservations",
      "href": "http://localhost:53568/reservations"
      "rel": "urn:year",
      "href": "http://localhost:53568/calendar/2020"
      "rel": "urn:month",
      "href": "http://localhost:53568/calendar/2020/7"
      "rel": "urn:day",
      "href": "http://localhost:53568/calendar/2020/7/7"

As I recently described, the ASP.NET Core Action API is tricky, and since there was some repetition, I was looking for a way to reduce the code duplication. At first I just thought I'd make a few private helper methods, but then it occurred to me that an Immutable Fluent Builder as an Adapter to the Action API might offer a fertile alternative.

UrlBuilder class #

The various Action overloads all accept null arguments, so there's effectively no clear invariants to enforce on that dimension. While I wanted an Immutable Fluent Builder, I made all the fields nullable.

public sealed class UrlBuilder
    private readonly string? action;
    private readonly string? controller;
    private readonly object? values;
    public UrlBuilder()
    private UrlBuilder(string? action, string? controller, object? values)
        this.action = action;
        this.controller = controller;
        this.values = values;
    // ...

I also gave the UrlBuilder class a public constructor and a private copy constructor. That's the standard way I implement that pattern.

Most of the modification methods are straightforward:

public UrlBuilder WithAction(string newAction)
    return new UrlBuilder(newAction, controller, values);
public UrlBuilder WithValues(object newValues)
    return new UrlBuilder(action, controller, newValues);

I wanted to encapsulate the suffix-handling behaviour I recently described in the appropriate method:

public UrlBuilder WithController(string newController)
    if (newController is null)
        throw new ArgumentNullException(nameof(newController));
    const string controllerSuffix = "controller";
    var index = newController.LastIndexOf(
    if (0 <= index)
        newController = newController.Remove(index);
    return new UrlBuilder(action, newController, values);

The WithController method handles both the case where newController is suffixed by "Controller" and the case where it isn't. I also wrote unit tests to verify that the implementation works as intended.

Finally, a Builder should have a method to build the desired object:

public Uri BuildAbsolute(IUrlHelper url)
    if (url is null)
        throw new ArgumentNullException(nameof(url));
    var actionUrl = url.Action(
    return new Uri(actionUrl);

One could imagine also defining a BuildRelative method, but I didn't need it.

Generating links #

Each of the objects shown above are represented by a simple Data Transfer Object:

public class LinkDto
    public string? Rel { getset; }
    public string? Href { getset; }

My next step was to define an extension method on Uri, so that I could turn a URL into a link:

internal static LinkDto Link(this Uri uri, string rel)
    return new LinkDto { Rel = rel, Href = uri.ToString() };

With that function I could now write code like this:

private LinkDto CreateYearLink()
    return new UrlBuilder()
        .WithValues(new { year = DateTime.Now.Year })

It's acceptable, but verbose. This only creates the urn:year link; to create the urn:month and urn:day links, I needed similar code. Only the WithValues method calls differed. The calls to WithAction and WithController were identical.

Shared Immutable Builder #

Since UrlBuilder is immutable, I can trivially define a shared instance:

private readonly static UrlBuilder calendar =
    new UrlBuilder()

This enabled me to write more succinct methods for each of the relationship types:

internal static LinkDto LinkToYear(this IUrlHelper url, int year)
    return calendar.WithValues(new { year }).BuildAbsolute(url).Link("urn:year");
internal static LinkDto LinkToMonth(this IUrlHelper url, int year, int month)
    return calendar.WithValues(new { year, month }).BuildAbsolute(url).Link("urn:month");
internal static LinkDto LinkToDay(this IUrlHelper url, int year, int month, int day)
    return calendar.WithValues(new { year, month, day }).BuildAbsolute(url).Link("urn:day");

This is possible exactly because UrlBuilder is immutable. Had the Builder been mutable, such sharing would have created an aliasing bug, as I previously described. Immutability enables reuse.

Conclusion #

I got my first taste of functional programming around 2010. Since then, when I'm not programming in F# or Haskell, I've steadily worked on identifying good ways to enjoy the benefits of functional programming in C#.

Immutability is a fairly low-hanging fruit. It requires more boilerplate code, but apart from that, it's easy to make classes immutable in C#, and Visual Studio has plenty of refactorings that make it easier.

Immutability is one of those features you're unlikely to realise that you're missing. When it's not there, you work around it, but when it's there, it simplifies many tasks.

The example you've seen in this article relates to the Fluent Builder pattern. At first glance, it seems as though a mutable Fluent Builder has the same capabilities as a corresponding Immutable Fluent Builder. You can, however, build upon shared Immutable Fluent Builders, which you can't with mutable Fluent Builders.

Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.


Monday, 10 August 2020 06:59:00 UTC


"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Monday, 10 August 2020 06:59:00 UTC