Can’t we do better than null?

So you want to retrieve an aggregate from a repository (assuming DDD) stored in some data store so you make a call not unlike the following:

Product product = productRepository.FindById(id);

You then go to execute some method on the Product aggregate and boom the dreaded NRE (Null Reference Exception) great, now we can just go to tried and tested !== null check however I want to explore different ways we can actually deal with values that may or may not be present and make this more explicit and give the consumer a better experience.

Is it an exceptional circumstance?

I guess the first question we could ask is would it be considered exceptional that we cannot find the aggregate we are looking for, in the example above we are using the id to find the aggregate which is about as specific as we can get and in most cases the id would have been assigned against a selectable item so not being able to retrieve the aggregate but likely be:

  1. Infrastructure issue in which case you would get an exception thrown at the infrastructure level
  2. The id has been tampered with to make it invalid or non accessible
  3. The aggregate has been removed

Now a case could be made that these are pretty exceptional conditions, however if we change is so that we are trying to retrieve an aggregate based off a natural id for instance a SKU then it becomes a non exceptional circumstance as it could just be that we have not assigned to the SKU to the product or has been done incorrectly.

I also find that your in the same boat with regard to the consumers point of view as the null return but in this case you will get a custom exception thrown at you which is slightly better (checked exceptions in java would make it explicit it could throw).

Return result with output

The next way we could make it more explicit to the caller is by changing the signature to this:

bool FindById(ProductId id, out Product product);

This goes back to the C days if you check out some of the WinAPI you would typically call a function that would return the result of the call and assign a value to a pointer passed in, thankfully the C# is a bit nicer but from a consumer point of view it is explicit but generally not a nice API to expose:

Product product = null;
bool found = productRepository.FindById(id, out product);

Personally I have never liked using out parameters I think mainly due to the declaring of the output parameter(s) before making the call.

Null Object

Another option is to have the repository return a Null Object if it cannot find the aggregate, I don’t think this option would be suitable for a repository as aggregates tend to be treated in much higher regard than optional objects however this strategy can work well in other scenarios where you could provide a default behaving object.

Why returning collections causes no problem

If we look at another method on our Product repository:

IList<Product> FindByCategory(ProductCategoryId productCategoryId);

When we use this method and it doesn’t find any Products for the category id supplied we don’t have the same problem as the FindById instead we just have an empty list (there’s a special place in hell reserved for people who return null out of these type of methods) so our consumer does not need to perform anything differently:

var foundProducts = productRepository.FindByCategory(productCategoryId);
foreach (var product in foundProducts)
    // do stuff here

The key difference between this signature and the FindById is that the actual products are now housed inside a container (in this case an implementation of IList<Product>) in order to get to the Product we have to go through the container.

This is one area where functional languages have the edge as they don’t have the concept of a null instead you have to be explicit by using a Maybe/Option and the consumer is forced to handle what to do in both cases (this is made easier by pattern matching).

What if we took a similar approach for returning our single Product.

Using a Tuple

The simplest way we could achieve this is by using a Tuple:

Tuple<bool, Product> FindById(ProductId id);

Now we are returned a result that gives us a container in that provides us with more context we can now use this as a consumer like this:

var result = productRepository.FindById(id);
if (result.Item1)
    // product was found
    // product not found

Now the consumer has a bit more to go on with regard to how the repository behaves and could probably assume that the first item corresponds to whether a Product has been found, as mentioned in the last section functional languages have pattern matching which makes this a bit more explicit, here is an example of what C# could look like if it had pattern matching:

var result = productRepository.FindById(id);
switch (result)
    case (false, _):
        // product not found
    case (true, product):
        // product found

Query Result

The Tuple approach is a step in the right direction however it’s not very expressive what if  we were to create our own object that represents the results of a query ideally as a consumer we could then use it like this:

QueryResult<Product> result = productRepository.FindById(id);
if (result.HasResult)
    var product = result.Result;
    // product found
    // product not found

// or if your feeling functional
result.Found(product => // product found);
result.Missing(() => // product not found);

I have shown 2 different API’s depending on the consumers preference, we have taken the approach of returning a container so the consumer must go through this in order to get to the Product, this makes it really explicit on how you deal with the result and no surprises.

I have pushed up a working version of this object to so feel free to take it and do what you want with it.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.