Search code examples
c#unity-containerfailoverinterface-implementation

Multiple Implementation Attempts


I am developing a solution which will connect to a wide variety of servers to read data and perform operations. There are many variables which complicate reliable communications such as firewalls, stopped/failed services, authentication differences, and various software configurations. There are methods I can use to work around these issues, though at the time of execution it is not known which will prove successful.

My goal is to create an interface and implementations which can be used to perform operations. The first method call will be to the fastest implementation which works for the majority of devices followed by other calls which can deal with the issues listed earlier.

In a perfect world the process would be written to quickly identify which method would be successful, but in my tests that took as much processing time as simply catching an exception. While performance is always a consideration, in the end it is more important that the task completes successfully.

Below is an example I created which demonstrates a worst case scenario iterating over a list of implementations. While this works well for one method, it doesn't follow the DRY principle when used in 20 or more different operations. One possible solution is Unity and Interception but I found that the invoke method in the call handler uses a resolved implementation, not a list of possible implementations. Unless I am missing something, that doesn't appear to be an option. Also, I will need to follow this pattern for several interfaces, so it would be nice to create a generic handler which can iterate over a list of implementations.

Any advice on how to complete this task would be appreciated!

Interface

public interface IProcess
{
    int ProcessItem(string workType);
}

Implementations

public class ProcessImplementation1 : IProcess
{
    public int ProcessItem(string workType)
    {
        throw new TimeoutException("Took too long");
    }
}

public class ProcessImplementation2 : IProcess
{
    public int ProcessItem(string workType)
    {
        throw new Exception("Unexpected issue");
    }
}

public class ProcessImplementation3 : IProcess
{
    public int ProcessItem(string workType)
    {
        return 123;
    }
}

Special Implementation loops through the other implementations until one succeeds without exception

public class ProcessImplementation : IProcess
{
    public int ProcessItem(string workType)
    {
        List<IProcess> Implementations = new List<IProcess>();
        Implementations.Add(new ProcessImplementation1());
        Implementations.Add(new ProcessImplementation2());
        Implementations.Add(new ProcessImplementation3());
        int ProcessId = -1;
        foreach (IProcess CurrentImplementation in Implementations)
        {
            Console.WriteLine("Attempt using {0} with workType '{1}'...",
                CurrentImplementation.GetType().Name, workType);
            try
            {
                ProcessId = CurrentImplementation.ProcessItem(workType);
                break;
            }
            catch (Exception ex)
            {
                Console.WriteLine("  Failed: {0} - {1}.",
                    ex.GetType(), ex.Message);
            }
            Console.WriteLine();

            if (ProcessId > -1)
            {
                Console.WriteLine("  Success: ProcessId {0}.", ProcessId);
            }
            else
            {
                Console.WriteLine("Failed!");
            }
            return ProcessId;
        }
    }
}

Solution

  • You could use the TryParse pattern in a second interface:

    public interface IProcess
    {
        int ProcessItem(string workType);
    }
    
    internal interface ITryProcess
    {
        bool TryProcessItem(string workType, out int result);
    }
    
    public class ProcessImplementation1 : ITryProcess
    {
        public bool TryProcessItem(string workType, out int result)
        {
            result = -1;
            return false;
        }
    }
    
    public class ProcessImplementation : IProcess
    {
        public int ProcessItem(string workType)
        {
            var implementations = new List<ITryProcess>();
            implementations.Add(new ProcessImplementation1());
            // ...
            int processId = -1;
            foreach (ITryProcess implementation in implementations)
            {
                if (implementation.TryProcessItem(workType, out processId))
                {
                    break;
                }
            }
            if (processId < 0)
            {
                throw new InvalidOperationException("Unable to process.");
            }
            return processId;
        }
    }