Factory Fetch methods and missing data - how do you handle it?

Factory Fetch methods and missing data - how do you handle it?

Old forum URL: forums.lhotka.net/forums/t/351.aspx


hurcane posted on Monday, June 12, 2006

I'm looking for advice on how others might handle missing (i.e. bad) data. I have a situation where the user can define product records in multiple warehouse locations. They can specify that an item is "sourced" from another location. The application does not enforce up-front that the sourced location have an existing record because that is an inconvenience to the users.

I am struggling with deciding how to handle the exception when there is bad data. What complicates matters is that I have over a hundred use cases that use this particular record in a read-only object that provides on-hand and cost information. About 3/4 of the cases fail when the data is missing. The other 1/4 have a defined alternative behavior when the data is missing.

Using Alistair Cockburn's terminology from "Writing Effective Use Cases", most of the failing use cases are "sea-level". Most of the fallback cases are "underwater" cases.

I can think of several ways to deal with this, but I'm not sure if I'm considering all the pros and cons.

I've considered throwing an exception in the DataPortal_Fetch method which includes the contextual information (product ID and warehouse ID) that the user would like to know. This makes it easy to handle the failing cases. The exception bubbles up to the UI where it is displayed to the user. For the fallback cases, they need to catch the exception and examine if it is the type that they can handle. This implies I'll have to create a custom exception class (e.g. MissingDataException) to aid in the scenario. I've also read that exceptions can be slow.

Another option I've considered is returning a Null/Nothing object when the data is missing. The downside to this is that the all the use cases should check the return value. If code does not check the return value, it is likely to get a null reference exception, which is not always helpful to the UI. This does simplify the fallback cases as they don't have to analyze the exception. It should be faster, too.

Another option is to have different objects. One raises an error, the other returns nothing. Is a new object justified when this would be the only difference? This could also be achieved by having separate factory methods or having an additional parameter for the Criteria object.

One last option I've considered is returning a concrete object, but exposing a property (e.g. IsMissing). I could fill all the private variables with "empty" values. This is really just an alternative to the approach of returning Nothing. It eliminates the null reference exception, but replaces it with a "blank" data scenario. I suppose blank data in an inquiry form is better than a hard error.

Another thought I just had as I've been typing... I could add code to every property that raises a specific error if the object is missing it's data.

Does anybody else have to deal with missing data? Are you using one of these approaches, or doing something even better?

RockfordLhotka replied on Monday, June 12, 2006

While it is true that exceptions are slow in a relative sense, they are not likely to be slow when compared to an operation like database access. In other words, they are slow relative to returning a method's result, or raising an event (and even then they are only slow to catch, not to throw), but they are not slow relative to many other operations like crossing a process or network boundary, doing file IO or talking to a database.

So my first instinct in cases like this is to define one or more custom exceptions that I can use to return detailed and meaningful information about the issue.

Another option is to wrap the creation of your object within a command object. So your business object's factory sends a command object to the server to retrieve the real object. If there's a failure to get valid data on the server, the command object can return (successfully - no exception) with information about why the operation failed. The client-side factory method can then examine the command object and decide what to do (throw an exception, return Nothing, return some other status object or whatever).

hurcane replied on Tuesday, June 13, 2006

Arrgggh! I had typed a lengthy reply, had to leave the computer for a spell, and lost the text when I tried to post the message. This second draft will be a lot shorter.

Thanks, Rocky, for the advice on exceptions. I will not allow performance to be a concern when raising exceptions during a data portal call.

I'm not sure I completely understand your explanation of a Command Object. When you said the client-side factory object can decide what to do, do you mean that in one case it can throw an exception and in another case it can return nothing? Or do you mean that the command object can be examined, determine that the data was missing, and take the same consistent action?

RockfordLhotka replied on Monday, June 19, 2006

The idea is that you use a command object to do the fetch rather than fetching your object directly.
 
In other words, your actual business object contains a private, nested command object kind of like this pseudo-code:
 
Public Class MyRealList
 
  Private Class MyListGetter
    Public filter As String ' or whatever criteria you are using
    Public result As String
    Public list As MyRealList
 
    Protected Overrides Sub DataPortal_Execute()
      ' try to load MyRealList here by calling
      ' a private factory
      list = MyRealList.LoadList(filter)
      ' if success, set list=new data and result="OK"
      ' if fail, set result=reason for failure
    End Sub
 
  End Class
 
  Public Shared Function GetList(filter As String) As MyRealList
    Dim cmd As New MyListGetter
    cmd = DataPortal.Execute(Of MyListGetter)(cmd)
    If cmd.result = "OK" Then
      Return cmd.list
 
    Else
      ' handle failure as desired
    End If
  End Function
 
  Private Shared Function LoadList(filter As String) As MyRealList
    Dim obj As New MyRealList
    obj.DataPortal_Fetch(new Criteria(filter))
    Return obj
  End Function 
 
  Private Overloads Sub DataPortal_Fetch(criteria As Criteria)
    ' load object here
  End Sub
 
End Class
 


I'm not sure I completely understand your explanation of a Command Object. When you sayd the client-side factory object can decide what to do, do you mean that in one case it can throw an exception and in another case it can return nothing? Or do you mean that the command object can be examined, determine that the data was missing, and take the same consistent action?

hurcane replied on Thursday, June 22, 2006

I follow Rocky's example, but I think I didn't define my scenario properly enough. Let me use the MyRealList object that Rocky defined. Rocky has the missing data being handled inside of MyRealList. My problem is that the handling of the missing data is defined not by the list, but by the objects that collaborate with the list.

I have two other business classes that use MyRealList. One class must collaborate with MyRealList. If MyRealList can't be successfully instantiated with the data, the use case says an exception must be thrown. In another use case, the class uses the information provided by MyRealList, but only if it can be loaded. If it can't be loaded, this second case can continue to work.

Perhaps I can have two versions of RealList (e.g. MyRealListRequired and MyRealListOptional). The command object can be externalized and shared betweent he two classes. The use cases collaborate with the appropriate object.

However, on further reflection, I think the nullable object (or a variation of it) sounds like the best scenario for me. I can have a separate nullable version of my object. Or I can handle the missing data internally and provide a property on the object to make available its "null" state. In any case, there must be a way for the two different business objects to confirm whether they are working with missing data.

pelinville replied on Monday, June 19, 2006

One option for the "not found" sql error, which I discovered just recently, is the null object. If the data you are looking for is not there you don't return null you return a "Null Object". 
 
This is single instance object that implements the interface of the object you are trying to retrieve, and is actually created within the interface.
 
This is a pattern that is explained in Robert C. Martin's book "Agile Software Development" book. 
 
Doesn't solve all problems but is useful in it's place.

malloc1024 replied on Tuesday, June 20, 2006

The null object pattern is well known and very useful.  It is used in situations where you want to avoid null checks.  This sounds like your situation.  I have used the null object pattern many times with great success.  I agree with pelinville that you should check it out.

Copyright (c) Marimer LLC