Fluent Interface Design in .NET
Eric Vogel shows you how to simplify the consumption of your API by adding a fluent interface design.
One way to clean up an existing API is to provide a fluent interface to it. The goal of a fluent interface is to simplify the consumption of your API by making it more readable and discoverable.
If you have a class library for sending an email message, a non-fluent version of the library entails constructing an email sender that connects to an outgoing mail server with a set of user credentials. Then you create an email that contains a message-to and message-from address along with the message body. Finally, you pass the email message to the sender object for delivery. The following example shows a non-fluent email API:
var sender = new EmailSender("smtp.emailserver.com");
var emailMessage = new EmailMessage();
message.To = "[email protected]";
message.From = "[email protected]";
message.Message = "Hello from the world of tomorrow!";
The code for such a library consists of an EmailSender class, shown in Listing 1, and an EmailMessage class, shown in Listing 2.
If you were to describe the flow of the program, you might say: Send a new email message from my account to [email protected] saying, "Hello from the world of tomorrow!" with the subject, "hello."
Here's a fluent version of the same API:
"password").CreateEmail().From("[email protected]").To("[email protected]").Saying("Hello from
the world of tomorrow!").WithSubject("Hello").Done().Send();
Implementing the Fluent Interface
Now that I've defined how the Fluent API will function, I'll go over how to implement it. First, I'll define the IFluentEmailSender interface that allows the EmailSender to be accessed in a fluid manner. The FromServer and WithCredentials methods both return the interface itself, which allows the methods to be called in either order. The CreateEmail message method returns an implementer of the IFluentEmailMessage interface to allow the email message to be defined. The Send method is used to deliver the email. The full interface source code is shown in the following example:
public interface IFluentEmailSender
IFluentEmailSender FromServer(string host);
IFluentEmailSender WithCredentials(string username, string password);
Next you define the IFluentEmailMessage interface that will allow setting of properties on an email message. Each method on the interface, except Done, will return the interface itself to allow method chaining. In this way, a user of the interface may call any of the methods, except Done, in any order. The Done method is the terminating method, which returns the IFluentEmailSender interface instance, so the email message may be sent. The following code shows the IFluentEmailMessage for the full interface definition:
public interface IFluentEmailMessage
IFluentEmailMessage From(string fromAddress);
IFluentEmailMessage To(string toAddress);
IFluentEmailMessage Saying(string message);
IFluentEmailMessage WithSubject(string subject);
Now you add the Fluent interface to the EmailSender and EmailMessage classes. I've chosen to go with a partial class implementation. This approach allows the existing code to remain mostly untouched, and the Fluent implementation to remain isolated from the core classes.
First you update the EmailSender class. Create a new partial class file named EmailSender.Fluent.cs.
Next, implement the IFluentEmailSender interface. The FromServer method sets the outgoing email host address and returns the class itself. The WithCredentials method calls the existing SetCredentials method, passing the username and password, and returns itself. The CreateEmail method creates a new EmailMessage object and returns it. The Send method simply calls the existing Send method with the created EmailMessage object. See Listing 3 for the full EmailSender fluent API implementation.
Now you update the EmailMessage class to implement the IFluentEmailMessage interface. Add a new class file named EmailMessage.Fluent.cs. I've added an IFluentEmailSender private member variable that is used for the Done method implementation. The methods--From, To, Saying, and WithSubject--each set the appropriate property on the object instance and return the object itself. The Done method returns the IFluentEmailSender member that is set in the object constructor, which allows the email to be sent by the sender. See Listing 4 for the full EmailMessage Fluent API implementation.
As you can see, by adding a fluent interface to an existing API, you can increase its ease of use. In addition, the API consumer code becomes more readable and discoverable.
As with all patterns, there's the downside of added code complexity. If your existing API is already small, or very simple, then adding a fluent interface may not be worth it. On the other hand, if your existing API is cumbersome to use, or you're creating a new API, consider adding a fluent interface and reap its benefits.
Eric Vogel is a Senior Software Developer for Red Cedar Solutions Group in Okemos, Michigan. He is the president of the Greater Lansing User Group for .NET. Eric enjoys learning about software architecture and craftsmanship, and is always looking for ways to create more robust and testable applications. Contact him at [email protected].