Skip to content

SolidStack.Core.Flow

Maxime Gélinas edited this page Jun 6, 2018 · 6 revisions

Overview

SolidStack.Core.Flow focuses on encapsulating the branching logic of your code so you can write a linear and much more readable code flow without having to deal with exceptions, null checks and unnecessary conditions.

Table of contents

Getting Started

What does it look like?

Optional values...

IOption<Product> product = TryGetProduct(2);

return product
	.WhenSome()
	.MapTo(p => Ok(p))
	.WhenNone()
	.MapTo(() => NotFound())
	.Map();

Operation results...

IResult<IProductUpdateError, Product> result = TryUpdateProduct(product);

return result
	.WhenError<ProductValidationError>()
	.MapTo(error => BadRequest(error))
	.WhenError<ProductConcurrencyError>()
	.MapTo(error => Conflict(error))
	.WhenError()
	.MapTo(error => StatusCode(500, error))
	.WhenSuccess()
	.MapTo(product => Ok(product))
	.Map();

Where can I get it?

First, install NuGet. Then, install SolidStack.Core.Flow from the package manager console:

PM> Install-Package SolidStack.Core.Flow

Turning nulls into optional objects with the Option type

Returning null values in your code for an optional value could be bad, because from the point of view of the code consumer, you never know if there is a value or not, so you must always branch out this null condition. This is where the Option type comes into the place. The Option type wraps null checks and makes it obvious to the code consumer that the value is optional.

Executing actions on optional values

Use the Do method under the WhenSome method to create an action that will be executed if the optional value is present and/or use the same method under the WhenNone method to create an action that will be executed if the optional value isn't present. Then, invoke the Execute method to execute the appropriate action(s).

IOption<Product> optionalProduct;
optionalProduct
	.WhenSome()
	.Do(product => Console.WriteLine("There is a product."))
	.WhenNone()
	.Do(() => Console.WriteLine("There is no product."))
	.Execute();

Mapping optional values

Use the MapTo method under the WhenSome method to define the mapping action that will be executed if the optional value is present and/or use the same method under the WhenNone method to to define the mapping action that will be executed if the optional value isn't present. Then, invoke the Map method to execute the appropriate mapping action.

IOption<Product> optionalProduct;
var productName = 
	optionalProduct
		.WhenSome()
		.MapTo(product => product.Name)
		.WhenNone()
		.MapTo(() => "Unknown product")
		.Map();

Avoid throwing exceptions using the Result type

Throwing exception in your code for an operation that may fail could be bad, because from the point of view of the code consumer, you never know if an exception will be throw or not or even which type of exception could be throw, so you must always catch every possible exceptions. This is where the Result type comes into the place. The Result type wraps errors checks and makes it obvious to the code consumer that the invoked operation may return an error or a success.

Executing actions on results

Use the Do method under the WhenError method to create an action that will be executed if the result is an error and/or use the same method under the WhenSuccess method to create an action that will be executed if the the result is a success. Then, invoke the Execute method to execute the appropriate action(s).

IResult<string, Product> productUpdateResult;
productUpdateResult
	.WhenError()
	.Do(error => Console.WriteLine("The product failed to update: ", error))
	.WhenSuccess()
	.Do(product => Console.WriteLine("The product has been updated successfully: ", product))
	.Execute();

Mapping results

Use the MapTo method under the WhenError method to define the mapping action that will be executed if the result is an error and/or use the same method under the WhenSuccess method to to define the mapping action that will be executed if the result is a success. Then, invoke the Map method to execute the appropriate mapping action.

IResult<string, Product> productUpdateResult;
var returnCode = productUpdateResult
	.WhenError()
	.MapTo(error => 1)
	.WhenSuccess()
	.MapTo(product => 0)
	.Map();

Handling multiple types of errors

The trick to handle multiple types of errors is to place every type of errors under a common interface like the following:

public interface IProductUpdateError { /* ... */ }
public class UnexistingProductError: IProductUpdateError { /* ... */ }
public class InvalidProductNameError: IProductUpdateError { /* ... */ }
// ...

Then, use the generic version of the WhenError method to target a specific type of errors and use the Do method or the MapTo method to access that error and to perform an action on/with it.

IResult<string, Product> productUpdateResult;
var returnCode = productUpdateResult
	.WhenError<UnexistingProductError>()
	.Do(error => Console.WriteLine("The product to update can't be found: ", error.ProductId))
	.WhenError<InvalidProductNameError>()
	.Do(error => Console.WriteLine("The product name should match the following pattern: ", error.ValidPattern))
	.WhenError()
	.Do(error => Console.WriteLine("The product failed to update: ", error))
	.WhenSuccess()
	.Do(product => Console.WriteLine("The product has been updated successfully: ", product))
	.Execute();