Introduction to Funkshun

Monday, January 30, 2012

A long time ago I created this little project and even released it on NuGet but never took the time to write a introduction for it. In this post I will redeem myself from this and do a quick introduction on how to use it and where it’s for.

This project was inspired by the lack of a convention on how to deal with technical and functional errors within the project I am working for. This lacking resulted in a mixture of ways of returning and handling errors. To be honest this wasn’t the first project I’ve been on where this was a problem and in most cases you don’t get the time to address this issue because it’s not where you are hired for.

A couple of bad solutions I’ve seen are: throwing exceptions for functional errors, error output parameters and even worse returning a string with the occurred error message. All of them work but don’t fit the job. For example throwing a exception should IMHO only be used for technical exceptions, in the case when you need a full stop scenario. Functional errors should not be encumbered with exceptions because this results in creating lots of nasty try-catch blocks and unnecessary performance loss when a exception is thrown. 

To address this kind of problems I created a small and simple library which is more or less a implementation of a request/response pattern surrounded with helper classes which simplifies working with returned responses. Now like most frameworks or libraries they are opinionated by the creators. Mine is no exception and is based on the following three assumptions:

  1. Only technical errors should be thrown by exceptions.
  2. Functional messages can be part of the result of a method (even void methods) but are not mandatory.
  3. Functional messages have a severity (error, warning or information).

The basic idea, is that functional messages are being returned inside a predefined container object next to the result value, while technical exceptions should in most cases just be thrown and handled by a exception handler (be it a global or a specific one). The core library is based on this idea but doesn’t stop there. It also introduces Funkshun methods which are build on top of this idea and gives you more power then the ordinary static or instance methods. These will be explained in a next post. Now the best way to explain myself, is by writing some code, so before we can do that we have to get the project from NuGet or GitHub.

Installing

NuGet

1. Add Package Reference by going to Manage NuGet Packages…

image

2. Search package Funkshun.Core and click install.

image

3. Accept license (Apache License 2.0).

image

4. Check if Funkshun.Core is added to the references. 

image_thumb12

GitHub

If you want to have the sources of the project you can get them from the GitHub repository (https://github.com/martijnburgers/Funkshun/) as a single zipped download or as a fork. You can then build and reference the assembly yourself. As long as you comply with the Apache License 2.0 you’re free to do everything you want.

Getting Started

Let’s start with some examples, but don’t get afraid by the project’s simplicity. The most important types in the library are IResult<TR> and IResult. They define the return value of a method being a void or non-void. The type parameter TR defines the type of the return value of a method.

image

So let’s create three simple static methods (could be instance ones as well).

    public static class CustomerService
    {
        //method with a real return value and possible functional messages.
        public static IResult<Customer> GetCustomer(int id)
        {
            var result = ResultHelper.Make<Customer>();

            //do some more...

            return result;
        }

        //void method but with possible functional messages.
        public static IResult DeleteCustomer(int id)
        {
            var result = ResultHelper.Make<Void>();

            //do some more...

            return result;
        }

        //method to indicate that it's not bad to not use the IResult types. 
        //Sometimes you don't have or want to return any technical or 
        //functional messages back to the caller. In this case just don't use it!
        public static void TryDeleteCustomer(int id)
        {
            try {}
            catch (Exception) {}
        }
    }

 

The first example returns a IResult<Customer> which contains the actual return value of the method which is of type Customer. Besides that, it also has a sequence of possible functional messages because it inherits from the IResult interface.

Notice the Void class used in the second method. The method doesn’t have a return value other then possible functional messages. The Funkshun.Core.Void class is a custom class defined within the library and must be used for defining void results. In reality it returns a IResult<Void> implementation but in this case we can give back just the IResult which holds the functional messages.

The third method is just a normal as the other methods but I decided to not use the IResult types. By convention this means that the method will not provide functional messages and that’s ok. Not every method in your codebase will be returning functional messages.

Simpler then this I can’t make it for you. But in short it’s nothing more then this. Disappointed right? Well sometimes it just that simple. Let’s go through with extending some of the earlier examples and let me try to show you it’s power.

image

We extend the first method by giving back a functional error message when the id is smaller then 1.

public static IResult<Customer> GetCustomer(int id)
{

	var result = ResultHelper.Make<Customer>();

	if (id < 1)
	{
		result.Messages.Add(
			new Message
			{
				Code = 1,
				Description = String.Format("Customer ID {0} does not exists!", id),
				Severity = MessageType.Error
			});
	}
	else
	{
		var customer = new Customer { BirthDay = new DateTime(1984, 9, 7), ID = id, Name = "Martijn Burgers" };

		result.ReturnValue = customer;
	}

	return result;
}

The caller of this method could then check for errors, warnings or informationals and take appropriate action. For example you could log all messages, check for certain errors (code eq 999, first-last error), rollback your transaction, bubble up the message to the current caller, even throw a functional error as exception, etc. This al depends on your needs and you can extend it very easily. Checkout the following examples of what you can do with it more. 

IResult<Customer> result = CustomerService.GetCustomer(1);

if (result.HasInformationals() || result.HasWarnings())
{
	//Trace this information with a custom .Trace() extension method.
	//It's very easy to extend it with your own extension methods
	result.Informationals().Trace();
	result.Warnings().Trace();
}

if(result.HasErrors()) //result.Errors().Any() is the same
{
	//rollback transaction
}

try
{
	//throw functional error as exception (yes we also support it, altough it against my principle)
	result.ThrowOnError();                
}
catch (MessageException e)
{
	string msg = String.Format("A total of {0} error(s) occurred!. The first error occurred at {1}",
							   e.Errors.Count(), e.TimeStamp);

	e.Errors.Log();

	Debug.WriteLine(msg);

	//loop through errors
	foreach (var message in e.Errors)
	{
		//...
	}
}

//work with the return value which is a customer
var customer = result.ReturnValue;

Debug.WriteLine(customer.Name);

The methods Informationals(), Warnings() and Errors() gives back a sequence of Message types with the corresponding severity. The Trace() and the Log() methods are not part the Funkshun.Core project and can be implemented by yourself as a extension method. All the other methods are self explanatory and implemented as extension methods and are part of the project. The ThrowOnError() method throws a MessageException with the information of the first occurred error. The whole collection of occurred errors is available as well inside the exception object.

Going further

As you have seen, a large part comes down on dealing with the result of a method. With the previous examples I’ve shown that you can do most of the things you will need. To make some things more elegant, I created the OnSuccess, OnError, OnWarning and OnInformational extension methods. All of these methods have the same options and have many overloads to fulfill your needs. I will not go into detail of all the overloads, you can check them out in the API reference. To explain it I will use the OnSuccess extension method. The most basic extension method will execute a delegate (System.Action or System.Func) when there are no occurred errors.

Extension:

public static void OnSuccess(this IResult functionResult, Action successAction)
public static void OnSuccess<TFunctionReturnType>(this IResult<TFunctionReturnType> functionResult, 
                                              Action<TFunctionReturnType> successAction)

Example:

CustomerService.GetCustomer(1).OnSuccess(customer => CustomerService.DeleteCustomer(customer.ID));

Extension:

public static void OnSuccess(this IResult functionResult, Action<IResult> successAction, Action<IResult> elseAction)
public static void OnSuccess<TFunctionReturnType>(this IResult<TFunctionReturnType> functionResult,
												Action<IResult<TFunctionReturnType>> successAction,
												Action<IResult<TFunctionReturnType>> elseAction)

Example:

This extensions gives you the possibility to call a delegate went there where no errors or a delegate when there are.

CustomerService.GetCustomer(1).OnSuccess(
	result => Debug.WriteLine(String.Format("Retrieved Customer {0}", result.ReturnValue.Name)),
	result => Debug.WriteLine(String.Format("{0} error(s) occured", result.Errors().Count()))
	);

Extension:

public static TNewResult OnSuccess<TNewResult>(this IResult functionResult, Func<IResult, TNewResult> successFunction)
public static TNewResult OnSuccess<TFunctionReturnType, TNewResult>(
	this IResult<TFunctionReturnType> functionResult,
	Func<IResult<TFunctionReturnType>, TNewResult> successFunction)

Example:

This is in my opinion the most powerful extension and makes it possible to return something else then then original return value.

int customerID = CustomerService.GetCustomer(1).OnSuccess(result => result.ReturnValue.ID);

In a next post I will go into the earlier mentioned Funkshun methods. They will provide more power then you have seen so far. If you feel this library is for you then definitely come back for this next post. To sum up: it’s important to have a convention for dealing with responses and more specific dealing with functional messages and exceptions. This is whether you choose for Funkshun or not.

Comments are closed