Developing a Fluent API is so cool ! šŸ˜Ž

Ohad Avenshtein
3 min readMay 29, 2019

--

šŸ¤“ What the heck are you talking about ?

Today iā€™ll share with you a cool code strategy. Fluent API is based on the builder design pattern (https://www.dofactory.com/net/builder-design-pattern) and is widely used across many programming languages. If youā€™re a JavaScript junky, youā€™re probably familiar with chaining map-reduce-filter functions together.
C# developers using this pattern in their LINQ expressions. Iā€™m sure there are plenty more examples.

Here is a simple LINQ example before weā€™ll get start:

int[] numbers = { 5, 4, 1, 3, 9, 6, 7, 2, 0 };
int maxNumber = numbers.Where(num => num < 9).Max());
Console.WriteLine(maxNumber); // Output: 7

Find more awesome examples LINQ at http://linq101.nilzorblog.com/linq101-lambda.php

šŸ¤‘ Money time !

At work, Fluent API style was a perfect fit for our integration tests infrastructure. In each integration test we need to perform 3 actions by order:
1. Prepare - Prepares an entity that will be sent to a CRUD method.
2. Create\Read\Update\Delete - Executes one of the CRUD methods and using the entity received from Prepare.
3. Post - Validates CRUD method was successfully invoked, checks that data is correct and performs assertions.

All services are implementing IService<T, TU> where T & TU are two types of entities.

public class SomeService : IService<SomeEntity,SomeOtherEntity>

Instead of using Lambda expression, for using Prepare and Post one needs to implement a proper interface. For instance: If you using Prepare for Create -implementation of IPrepareCreateAction<T> is required.

public class PrepareCreateSomeEntity : IPrepareCreateAction<SomeEntity>
{
public string Name { get; set; } = "SomeName";
public SomeEntityPrepare()
{
return new SomeEntity { Name = Name };
}
}

Keep in mind that after using Prepare, API user shouldnā€™t be able to invoke anything else BUT Create method. Whatā€™s cool about FluentAPI that it allows to guide the API user with executable actions. You can think about it as a pipeline.

The technical idea is simple. Each method returns an interface which exposes only invokable methods:

public interface ICanUsePrepare<T> 
{
ICanUseCreate<T> Prepare(IPrepareCreateAction<T> prepareCreateAction);
}
public interface ICanUseCreate<T>
{
ICanUsePost<T> Create();
}
public interface ICanUsePost<T>
{
void Post(IPostCreateAction<T> postAction);
}

Data is gets updated in each step & shared between them using class members.
In this case, whatā€™s needed to be shared is the original entity and its related Id from db. Those were united to a ActionModel<T> type.

public class ActionModel<T>
{
public string Id { get; set; }
public T Entity { get; set; }
}

CrudBaseFluent implements all ā€œCanUseā€ interfaces.

public class CrudBaseFluent<T, TU> : ICanUsePrepare<T>, 
ICanUseCreate<T>,
ICanUsePost<T>
where T : class
where TU : class

Prepare updates this.Model.Entity and returns current class instance which cast to ICanUseCreate<T> so the API user would be restricted to invoke only methods configured in that interface. This interface contains only Create method but ofcourse itā€™s possible to add some more methods or to inherit from other interface.

public ICanUseCreate<T> Prepare(IPrepareCreateAction<T> prepareCreateAction)
{
this.Model.Entity = prepareCreateAction.Prepare();
return this;
}
public ICanUsePost<T> Create()
{
var createId = _service.Create(this.Model.Entity);
this.Model.Id = createId.ToString();
return this;
}

A static method for an easy initialization is always nice.

public static CrudBaseFluent<T, TU> Initialize(IService<T, TU> service)
{
var crudFluentInstance = new CrudBaseFluent<T, TU>(service);
return crudFluentInstance;
}

Here is the full CrudBaseFluent<T, TU> implementation:

public class CrudBaseFluent<T, TU> : ICanUsePrepare<T>, 
ICanUseCreate<T>,
ICanUsePost<T>
where T : class
where TU : class
{
public ActionModel<T> Model { get; set; }
= new ActionModel<T>();
private readonly IService<T, TU> _service; public CrudBaseFluent(IService<T, TU> service)
{
this._service = service;
}
public static CrudBaseFluent<T, TU> Initialize(IService<T, TU> service)
{
var crudFluentInstance = new CrudBaseFluent<T, TU>(service);
return crudFluentInstance;
}
public ICanUseCreate<T> Prepare(IPrepareCreateAction<T> prepareCreateAction)
{
this.Model.Entity = prepareCreateAction.Prepare();
return this;
}
public ICanUsePost<T> Create()
{
var createId = _service.Create(this.Model.Entity);
this.Model.Id = createId.ToString();
return this;
}
public void Post(IPostCreateAction<T> postAction)
{
postAction.Post(this.Model);
}
}

Finally, use it ! šŸ¤ž

CrudBaseFluent<SomeEntity, SomeOtherEntity>
.Initialize(_services)
.Prepare(new PrepareCreateSomeEntity())
.Create()
.Post(new PostSomeEntityAction<SomeEntity, SomeOtherEntity>());

--

--

Ohad Avenshtein

Passionate Full-Stack developer who loves basketball šŸ€