A set of C# functional containers I use to write DDD-style programs:
Option<T>
ValueObject<T>
Entity<T>
This lightweight library allows you to:
- Deal with asynchrony, concurrency and parallelism (in progress!) challenges with few and clear code thanks to the functional programming principles.
- Write thin application and infrastructure layers to maximize the proportion of code written in the domain layer.
- Write few conditional logic. It will be handled thanks to functional thinking and operations like
Bind
,Map
. - Harvest errors with a little effort.
- Chain operations to improve the reliability and lisibility of DDD-style C# programs.
I recommend to read this article series first.
An ASP.Net Core Web API test project is available.
Add several items in a shopping cart, once it reaches a certain total amount, an error is returned.
This Roslyn code generator will help to write DDD's ubiquituous language code by generating builders, setters and constructors for you (beta).
Option<T>
is a container to model the presence or absence of data instead of using null
.
// Defining some value
Option<string> someString = Some("foo");
// Defining the absence of value, instead of using null
Option<string> none = None<string>();
Use Match
to get the inner value of an Option<T>
container. You must provide it two functions:
- One for the case where the container holds a value (Some). This value will be injected.
- One for the case where the contaniner doesn't hold a value (None).
The returned value of provided functions must be of the same type.
private string GetOptionValue<T>(Option<T> value)
{
return value.Match(
None: () => "Empty", // Returns a string
Some: (value) => $"{value}"); // Returns a string
}
Map
function is the same operator than Select
in LINQ. It applies a function Func<T,R>
to the inner value of Option<T>
to transform its content.
[TestMethod]
public void When_mapping_a_function_on_some_value_then_it_is_applied()
{
var addOneFun = (int i) => i + 1;
var sut = Some(0);
var result = sut.Map(addOneFun)
.Match(
Some: v => v,
None: () => 0);
// Is equal to 1 because addOneFun was applied on a some value.
Assert.AreEqual(1, result);
}
[TestMethod]
public void When_mapping_a_function_on_a_none_value_then_it_is_not_applied()
{
var addOneFun = (int i) => i + 1;
var sut = None<int>();
var result = sut.Map(addOneFun)
.Match(
Some: v => v,
None: () => 0);
// Is equal to zero because addOneFun was applied on a none value.
Assert.AreEqual(0, result);
}
Same usage as Map
. The difference with Map
is that Bind
takes an Option<R>
returning function, i.e Func<T,Option<R>>
. Its is the same operator than SelectMany
in LINQ.
[TestMethod]
public void When_binding_three_AddOne_functions_on_Some_zero_value_then_the_result_is_three()
{
var addOneFun = (int i) => Some(i + 1); //returns an Option<int> instead of an int with Map.
var sut = Some(0);
var result = sut.Bind(addOneFun)
.Bind(addOneFun)
.Bind(addOneFun)
.Match(
Some: v => v,
None: () => 0);
Assert.AreEqual(3, result);
}
This function calls a fallback function if the value of an Option<T>
is None
.
[TestMethod]
public void When_binding_a_function_returning_a_none_value_then_orelse_is_fired()
{
var func = (int i) => None<int>();
var sut = Some(0);
var result = sut
.Bind(func)
// Previous instruction returned None:
// in that case, the given function to OrElse will be called.
.OrElse(() => Some(1000))
.Match(
Some: v => v,
None: () => 0);
Assert.IsTrue(result == 1000);
}
This library contains an Error
data structure to avoid the use of exceptions for domain problems.
public record Error
{
public string Message { get; }
public string Code { get; }
public string TypeName { get; }
public Error(string message) : this(message, string.Empty) { }
public Error(string message, string code)
{
Message = message;
TypeName = GetType().Name;
Code = code;
}
}
ValueObject<T>
is a container for DDD value objects.
ValueObject<T>
is a two states container:
- A valid state: the inner value of
ValueObject<T>
value is available. - An invalid state: the inner value of
ValueObject<T>
is unaccessible and only anIEnumerable<Error>
causing the invalid state is available.
- Value Objects must be declared as
records
to make them immutable. - Make the Value Object constructor private and create a public static constructor that returns a
ValueObject<T>
.
This is important to enjoy the benefits of this library and functional programming style.
Use the Valid function:
ValueObject<T>.Valid(/*your value of type T*/)
Use the invalid function:
ValueObject<T>.Invalid(new Error(/*... your message and code .... */));
Implicit conversions have been added to create a ValueObject<T>
:
- In an invalid state from an
Error
- In a valid state from an object.
// Declaire a Value Object as a record to enforce its immutability
public record Quantity {
public const int MAX_QUANTITY = 10;
public readonly int Value;
// Make standard constructor private
private Quantity(int quantity)
{
Value = quantity;
Unit = unit;
}
// Use instead a static public constructor that will return a ValueObject<Quantity>
public static ValueObject<Quantity> Create(int quantity)
{
if (quantity > MAX_QUANTITY)
{
// The Error class is embeded in Grenat.Functionnal.DDD.
// An implicit conversion is also defined in the library to convert an Error to an invalid ValueObject<T>.
return new Error($"Quantity cannot be more than {MAX_QUANTITY}");
}
else
{
// An implicit conversion is defined in the library ton convert a T to a valid ValueObject<T>.
return new Quantity(quantity);
}
}
}
Like Option<T>
, you need to use the Match
function. You must provide it two functions:
- One for the valid state. The inner value of the
ValueObject<T>
will be injected. - One for the invalid state. An
IEnumerable<Error>
containing the errors will be injected.
Important: the returned value of provided functions must be of the same type.
var quantity = Quantity.Create(10);
var value = quantity.Match(
Valid: v => v.Value, // Returns an int
Invalid: e => 0); // Returns an int
Console.WriteLine(value); // 10
In case of an Error, ValueObject<T>
switches to an invalid state and the Error is stored in an IEnumerable<Error>
.
var result = Quantity.Create(10, "Liters")
.Bind((q) => q.Add(10))
.Bind((q) => q.Add(100000)) //invalid
.Bind((q) => q.Add(10))
.Match(
Valid: v => new { Value = v.Value, Errors = Enumerable.Empty<Error>()},
Invalid: e => new { Value = 0, Errors = e});
Like Option<T>
, Map
and Bind
functions have been creating for function chaining:
var result = Quantity.Create(10, "Liters")
.Bind((q) => q.Add(10))
.Bind((q) => q.Add(30))
.Match(
Valid: v => new { value = v.Value, errors = Enumerable.Empty<Error>()},
Invalid: e => new { value = 0, errors = e});
Console.WriteLine(result.value); // 50
Entity<T>
is a container for DDD entities. A DDD entity should consist of value objects, list of value objects, other entities or list of other entities.
It has a lot more of extension methods than value objects. You will use them for:
- Defining in a functional way the properties of an Entity (value objects, list of entities, ...).
- Chaining functions.
- Dealing with aynchronism and parallelism challenges.
Like ValueObject<T>
, Entity<T>
is a two states container:
- A valid state: the inner value of
Entity<T>
value is available.. - An invalid state: the inner value of
Entity<T>
is unaccessible and only anIEnumerable<Error>
causing the invalid state is available.
Like ValueObject<T>
:
- Entities must be declared as
records
to make them immutable. - Make the Entity constructor private and create a public static constructor that returns an
Entity<T>
.
This is important to enjoy the benefits of the library and functional programming style.
Use the Valid function:
Entity<T>.Valid(/*your value of type T*/)
Entity<T>.Invalid(new Error(/* ... your message and error code ... */));
Implicit conversions have been added to create a Entity<T>
:
- In an invalid state from an
Error
- In a valid state from an object.
// Declare an Entity as a record to enforce its immutability
public record CartItem
{
public Identifier Id { get; init; }
public Identifier ProductId { get; init; }
// Make standard constructor private
private CartItem()
{
Id = new Identifier();
ProductId = new Identifier();
}
// Create a static public constructor that will return an Entity<Item>
// See "Builder pattern" further in this file for a more complete example.
public static Entity<CartItem> Create(string id, string productId)
{
if (string.IsNullOrEmpty(id) && string.IsNullOrEmpty(productId))
return new Error("Id and ProductId cannot be null or empty.");
return Entity<CartItem>.Valid(new CartItem());
}
}
Value objects will be created and containerized in a ValueObject<T>
container thanks to their static constructor (see above). But they may be containerized in a parent Entity<T>
. We face the problem of a container containing other containers, i.e. an Entity<T>
containing some ValueObject<T>
or some Entity<T>
. This is not very convenient.
We don't want to write this:
public record Cart
{
public ValueObject<Identifier> Id { get; init; }
public ImmutableList<Entity<Item>> Items { get; init; }
public ValueObject<Amount> TotalAmount { get; init; }
/*...*/
}
We want to write this:
public record Cart
{
public Identifier Id { get; init; }
public ImmutableList<Item> Items { get; init; }
public Amount TotalAmount { get; init; }
/*...*/
}
What's more, whenever we need to change an entity's property, we have to recreate a new instance of it to preserve immutability. To solve these problems, this library contains some setters.
For value objects:
SetValueObject
SetValueObjectList
SetValueObjectOption
For entities:
SetEntity
SetEntityList
SetEntityDictionary
SetEntityOption
Behind the scenes, they:
- Unwrap the inner value of the
Entity<T>
modify. - Unwrap the inner value of the container (an
Entity<V>
, aValueObject<V>
, anOption<V>
, ...) that is passed as a parameter. - Modify a target property of the
Entity<T>
thanks the setter function. - And they wrap the result in an new instance of
Entity<T>
.
If one of the Entity
or ValueObject
parameter is invalid, then the resulting Entity<T>
will become invalid too.
Finally, Error
s are harvested into this resulting entity.
Here is the function's signature:
public static Entity<T> SetValueObject<T, V>(this Entity<T> entity, ValueObject<V> valueObject, Func<T, V, T> setter) { /*...*/ }
It takes as arguments:
- The entity update.
- The value object to set into the entity.
- A setter function.
You can use it this way:
public static Entity<Cart> SetTotalAmount(this Entity<Cart> cart, ValueObject<Amount> totalAmount)
{
return cart.SetValueObject(totalAmount, (cart, totalAmount) => cart with { TotalAmount = totalAmount });
}
SetEntity
is the same as SetValueObject
but instead of accepting a ValueObject<T>
as a parameter, it accepts an Entity<T>
. Here is its signature:
public static Entity<T> SetEntity<T, E>(this Entity<T> parentEntity, Entity<E> entity, Func<T, E, T> setter) { /* ... */ }
As their name suggests, these setters do the same than the previous ones, but for immutable collections and immutable dictionaries of ValueObjects or Entities.
Use these functions to set a property of an Entity<T>
that contains an Option<V>
, i.e:
// First entity
public record InnerEntity
{
public readonly int Value = 0;
private InnerEntity(int value)
{
Value = value;
}
public static Entity<InnerEntity> Create(int value)
{
return Entity<InnerEntity>.Valid(new InnerEntity(value));
}
}
// An other entity that contains the previous entity in an option propoperty
public record ContainerEntity
{
public Option<InnerEntity> InnerEntityOption { get; set; }
/* ... */
}
If predicates are not verified, then Option<V>
will be None
, else it will contain Some(V)
:
var containerEntity = ContainerEntity.Create();
containerEntity = containerEntity.SetEntityOption(
() => InnerEntity.Create(0),
() => false,
static (containerEntity, v) => containerEntity with { TestEntityOption = v });
// 2nd setter
// containerEntity.InnerEntityOption will be equal to None.
containerEntity = containerEntity.SetEntityOption(
InnerEntity.Create(0),
v => v.Value >= 1,
static (containerEntity, v) => containerEntity with { TestEntityOption = v });
Same than SetEntityOption
, these setters are designed for Value Objects that are embedded in a ValueObject<T>
container.
public record CartItem
{
/* See other declarations above */
public Option<Discount> DiscountValue { get; init; }
/* ... */
}
public static Entity<CartItem> WithDiscountValue(this Entity<CartItem> cartItem, int discountValue)
{
return cartItem.SetValueObjectOption(
() => Discount.Create(discountValue),
() => discountValue > 0,
(cartItem, discount) => cartItem with { DiscountValue = discount });
}
All setters perform error harvesting. That is to say, if you try to set an invalid value object or an invalid entity, their errors are harvested and added to the ones already existing on the parent entity. It is very interesting for APIs: if the user types bad data, then all the errors will be returned.
Here is an Identifier
value object:
public record Identifier
{
public const string DEFAULT_VALUE = "";
public readonly string Value = "";
private Identifier(string identifier)
{
Value = identifier;
}
public static ValueObject<Identifier> Create(string identifier)
{
if (string.IsNullOrEmpty(identifier))
return new Error("An identifier cannot be null or empty.");
return new Identifier(identifier);
}
}
Here is an Amount
value object:
public record Amount
{
public const int MAX_AMOUNT = 2500;
public const int DEFAULT_VALUE = 0;
public readonly int Value;
public Amount() { Value = DEFAULT_VALUE; }
private Amount(int value)
{
Value = value;
}
public static ValueObject<Amount> Create(int amount)
{
if (amount < 0)
return new Error("An amount cannot be negative.");
if (amount > MAX_AMOUNT)
return new Error(String.Format($"An amount cannot be more than {MAX_AMOUNT}"));
return new Amount(amount);
}
public static implicit operator int(Amount amount) => amount.Value;
}
Let's create a cart by setting invalid data and let's see the errors harvesting in action. Cart.Create
is a static constructor returning an empty Entity<Cart>
:
var cart = Cart.Create()
.SetValueObject(Identifier.Create(null), (cart, identifier) => cart with { Id = identifier })
.SetValueObject(Amount.Create(-1), (cart, totalAmount) => cart with { TotalAmount = totalAmount })
.SetValueObject(Amount.Create(3000), (cart, totalAmountWithTax) => cart with { TotalAmountWithTax = totalAmountWithTax });
Console.WriteLine(cart.Errors);
// Output :
// An identifier cannot be null or empty.
// An amount cannot be negative.
// An amount cannot be more than 2500.
You can write an application layer in a functional style like this:
public static OperationResultDto<ProductDto> AddProduct(
AddProductDto addProductDto,
Func<string, int> CountProductReferences
Func<Product, Product> SaveProduct)
{
var p = addProductDto.ToProductEntity()
.Bind(VerifyProductReference, CountProductReferences(addProductDto.Reference))
.Map(SaveProduct);
return p.Match(
Valid: (v) => new OperationResultDto<ProductDto>(v.ToProductDto()),
Invalid: (e) => new OperationResultDto<ProductDto>(e)
);
}
Sometimes you may want to call an action on the inner value of Entity<T>
in an application layer pipeline for example. In that case, Map()
will apply the action on the value and will return the original Entity<T>
after that.
Let's say you need to send a message after an operation:
public static OperationResultDto<ProductDto> AddProduct(
AddProductDto addProductDto,
Func<Product, Product> SaveProduct
Action<Product> ProductAddedMessage)
{
var p = addProductDto.ToProductEntity()
.Map(SaveProduct)
.Map(ProductAddedMessage);
return p.Match(
Valid: (v) => new OperationResultDto<ProductDto>(v.ToProductDto()),
Invalid: (e) => new OperationResultDto<ProductDto>(e)
);
}
AsyncFunc<T>
delegates to simplify the writing of Func<Task<T>>
. As a result :
Func<Task<T>>
is equivalent toAsyncFunc<T>
.Func<T, Task<R>>
is equivalent toAsyncFunc<T, R>
.
Like AsyncFunc<T>
an AsyncAction<T>
delegate have been added.
This library contains asynchronous versions of Entity<T>.Bind()
and Entity<T>.Map()
: Entity<T>.BindAsync()
and Entity<T>.MapAsync()
. Here is an asynchronous version of the previous code:
public static async Task<OperationResultDto<ProductDto>> AddProduct(
AddProductDto addProductDto,
AsyncFunc<string, int> CountProductReferences,
AsyncFunc<Product, Product> SaveProduct)
{
var p = await addProductDto.ToProductEntity()
.BindAsync(VerifyProductReference, () => CountProductReferences(addProductDto.Reference))
.MapAsync(SaveProduct);
return p.Match(
Valid: (v) => new OperationResultDto<ProductDto>(v.ToProductDto()),
Invalid: (e) => new OperationResultDto<ProductDto>(e)
);
}
This function allow you to run an IEnumerable
of tasks on the inner value of an Entity<T>
in parallel. When all tasks are achieved, an aggregation function will be fired and the results will be injected in an ImmmutableList
argument.
The function's signature is as follows:
public static async Task<Entity<R>> MapParallel<T, R>(this Entity<T> entity,
IEnumerable<AsyncFunc<T, R>> funcs,
Func<ImmutableList<R>, R> aggregationFunc) {}
You can use it this way:
var sut = Entity<int>.Valid(5);
var funcs = new List<AsyncFunc<int, int>>()
{
async (p) => await Task.FromResult(p + 1),
async (p) => await Task.FromResult(p + 2),
async (p) => await Task.FromResult(p + 3)
};
var result = (await sut.MapParallel(funcs, (values) => values.Sum()))
.Match(
Valid: v => v,
Invalid: e => 0);
Assert.IsTrue(result == 21);
The usage of BindParallel
is the same than MapParallel
. The signature of the function is slightly different:
// BindParallel takes a collection of asynchronous functions that return Entity<R> (R only for MapParallel)
public static async Task<Entity<R>> BindParallel<T, R>(this Entity<T> entity,
IEnumerable<AsyncFunc<T, Entity<R>>> funcs,
Func<ImmutableList<R>, R> aggregationFunc) {}
If one of the result of the computations is invalid then the errors will be harvested in the original entity.
You can use it this way:
var sut = Entity<int>.Valid(5);
var funcs = new List<AsyncFunc<int, Entity<int>>>()
{
async (p) => await Task.FromResult(Entity<int>.Valid(p + 1)),
async (p) => await Task.FromResult(Entity<int>.Invalid(new Error("Error 1"))),
async (p) => await Task.FromResult(Entity<int>.Invalid(new Error("Error 2")))
};
var result = await sut.BindParallel(funcs, (values) => values.Sum());
Assert.IsTrue(result.Errors.Count() == 2); // Errors are harvested
The code to call an application layer's AddProduct()
function (see above) can look like this:
[HttpPost]
public async Task<ActionResult> AddProduct(AddProductDto addProductDto)
{
var response = await CatalogOperations.AddProduct(
addProductDto,
_productRepository.CountExistingProductReferences,
_productRepository.AddProduct);
if (response.Success)
return Ok(response.Data);
else
return UnprocessableEntity(response.Errors);
}
I suggest you read this post on my blog.
A Roslyn code generator exists to automatically generate code for the following patterns.
An entity with 6 fields requires a constructor with 6 parameters which is a lot. Moreover, some parameters could not be known depending on the call context. This would require creating several constructors or creating optional parameters.
To avoid multiplying the constructors, we can take inspiration from the builder pattern:
var cartItem = new CartItemBuilder()
.WithId("45xxsDg1=")
.WithProductId("ne252TJqAWk3")
.WithAmount(25)
.Build();
How to do that? First, let's create the CartItem Entity :
public record CartItem
{
public Identifier Id { get; init; }
public Identifier ProductId { get; init; }
public Amount Amount { get; init; }
private CartItem()
{
Id = new Identifier();
ProductId = new Identifier();
Amount = new Amount();
}
public static Entity<CartItem> Create(string id, string productId, int amount)
{
if (string.IsNullOrEmpty(id) && string.IsNullOrEmpty(productId))
return new Error("Id and ProductId cannot be null or empty.");
// Creating an empty Entity<CartItem>, then using setters (see below)
// to set the value object properties
return Entity<CartItem>.Valid(new CartItem())
.SetId(id)
.SetProductId(productId)
.SetAmount(amount);
}
}
Fore more clarity, let's create some setters:
public static class CartItemSetters
{
public static Entity<CartItem> SetId(this Entity<CartItem> cartItem, string id)
{
return cartItem.SetValueObject(Identifier.Create(id), (cartItem, identifier) => cartItem with { Id = identifier });
}
public static Entity<CartItem> SetProductId(this Entity<CartItem> cartItem, string productId)
{
return cartItem.SetValueObject(Identifier.Create(productId), (cartItem, productId) => cartItem with { ProductId = productId });
}
public static Entity<CartItem> SetAmount(this Entity<CartItem> cartItem, int amount)
{
return cartItem.SetValueObject(Amount.Create(amount), (cartItem, amount) => cartItem with { Amount = amount });
}
}
Finally, let's create the builder:
public record CartItemBuilder
{
private string _id { get; set; }
private string _productId { get; set; }
private int _amount { get; set; }
public CartItemBuilder WithId(string id) { _id = id; return this; }
public CartItemBuilder WithProductId(string productId) { _productId = productId; return this; }
public CartItemBuilder WithAmount(int amount) { _amount = amount; return this; }
public Entity<CartItem> Build() => Item.Create(_id, _productId, _amount);
}
For better readability, you can create some setters using extension methods. Be careful, setters can be an anti-pattern because they can lead to a data structure inconsistency. Use them wisely.
Here is an example:
public static class CartSetters
{
public static Entity<Cart> SetId(this Entity<Cart> cart, string id)
{
return cart.SetValueObject(Identifier.Create(id), static (cart, identifier) => cart with { Id = identifier });
}
public static Entity<Cart> SetTotalAmountWithoutTax(this Entity<Cart> cart, int totalAmount)
{
return cart.SetValueObject(Amount.Create(totalAmount), static (cart, totalAmount) => cart with { TotalAmountWithoutTax = totalAmount });
}
/* ... */
}