Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design pattern for including errors with return values

I'm writing an add-in for another piece of software through its API. The classes returned by the API can only be access through the native software and the API. So I am writing my own stand alone POCO/DTO objects which map to the API classes. I'm working on a feature which will read in a native file, and return a collection of these POCO objects which I can stole elsewhere. Currently I'm using JSON.NET to serialize these classes to JSON if that matters.

For example I might have a DTO like this

public class MyPersonDTO
{
    public string Name {get; set;}
    public string Age {get; set;}
    public string Address {get; set;}       
}

..and a method like this to read the native "Persons" into my DTO objects

public static class MyDocReader
{
    public static IList<MyPersonDTO> GetPersons(NativeDocument doc)
    {
        //Code to read Persons from doc and return MyPersonDTOs
    }
}

I have unit tests setup with a test file, however I keep running into unexpected problems when running my export on other files. Sometimes native objects will have unexpected values, or there will be flat out bugs in the API which throw exceptions when there is no reason to.

Currently when something "exceptional" happens I just log the exception and the export fails. But I've decided that I'd rather export what I can, and record the errors somewhere.

The easiest option would be to just log and swallow the exceptions and return what I can, however then there would be no way for my calling code to know when there was a problem.

One option I'm considering is returning a dictionary of errors as a separate out parameter. The key would identify the property which could not be read, and the value would contain the details of the exception/error.

public static class MyDocReader
{
    public static IList<MyPersonDTO> persons GetPersons(NativeDocument doc, out IDictionary<string, string> errors)
    {
        //Code to read persons from doc
    }
}

Alternatively I was also considering just storing the errors in the return object itself. This inflates the size of my object, but has the added benefit of storing the errors directly with my objects. So later if someone's export generates an error, I don't have to worry about tracking down the correct log file on their computer.

public class MyPersonDTO
{
    public string Name {get; set;}
    public string Age {get; set;}
    public string Address {get; set;}

    public IDictionary<string, string> Errors {get; set;}   
}

How is this typically handled? Is there another option for reporting the errors along with the return values that I'm not considering?

like image 728
Eric Anastas Avatar asked Oct 26 '15 19:10

Eric Anastas


2 Answers

Instead of returning errors as part of the entities you could wrap the result in a reply or response message. Errors could then be a part of the response message instead of the entities.

The advantage of this is that the entities are clean

The downside is that it will be harder to map the errors back to offending entities/attributes.

When sending batches of entities this downside can be a big problem. When the API is more single entity oriented it wouldn't matter that much.

like image 82
Emond Avatar answered Sep 30 '22 16:09

Emond


In principal, if something goes wrong in API (which cannot be recovered), the calling code must be aware that an exception has occurred. So it can have a strategy in place to deal with it.

Therefor, the approach that comes to my mind is influenced by the same philosophy -

1> Define your own exception Lets say IncompleteReadException. This Exception shall have a property IList<MyPersonDTO> to store the records read until the exception occurred.

public class IncompleteReadException : Exception
{
    IList<MyPersonDTO> RecordsRead { get; private set; }       

   public IncompleteReadException(string message, IList<MyPersonDTO> recordsRead, Exception innerException) : base(message,innerException)
    {
        this.RecordsRead = recordsRead;
    }
}

2> When an exception occurs while reading, you can catch the original exception, wrap the original exception in this one & throw IncompleteReadException

This will allow the calling code (Application code), to have a strategy in place to deal with the situation when incomplete data is read.

like image 36
Kapoor Avatar answered Sep 30 '22 14:09

Kapoor