-
Notifications
You must be signed in to change notification settings - Fork 0
SolidStack.Core.Flow
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.
- Getting started
- Turning nulls into optional objects with the
Option
type - Avoid throwing exceptions using the
Result
type
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();
First, install NuGet. Then, install SolidStack.Core.Flow from the package manager console:
PM> Install-Package SolidStack.Core.Flow
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.
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();
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();
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.
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();
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();
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();