Beware of Dumb Objects

In some projects I often see systems which look like this:

class InvoiceFactory
{
    public function __construct($service);
    public function createObjects($param);
}

class Invoice
{
    public function __construct($array);
    public function createObjects();
}

class InvoiceProducts
{
    public function __construct($object);
}

class WarehouseInvoice
{
    public function __construct($invoice, $products);
}

// Classes are used like this:
$invoices = new InvoiceFactory($database);
// Load database rows by id.
$array = $invoices->fetchInvoices($someId);
// Create useable objects from those rows
$invoice = new Invoice($array);
// List of products
$productList = $invoice->getProducts();
// Usable list products.
$products = InvoiceProducts($productList);
// Convert those rows into a usable form.
$objectsDto = $products->fetchProducts();
// Convert one useable form into the one that we actually need.
$products = new WarehouseProducts($invoice, $products);
// Convert list of products to one we can use in a template
$yetAnotherDto = $products->listProducts();
// Actually use the object.
doSomething($yetAnotherDto[0]->actualProperty);

Here we’ve gone from a factory to an object to a list of objects and back to a single object again several times. This can go on and on through several iterations of tiny one-function stateless objects that just convert data.

In this example, the code never actually does anything until the objects are converted into primitive data because all the models only accept primitive data. It seems attractive at first that all the objects would be without dependencies and behave as simple services, however there are several problems with this:

  • usage code is long
  • all objects involved need to be understood in terms of how they accept and produce data in order to know what’s going on
  • difficult to include data from other services without performing more data wrangling
  • difficult to change the behaviour of primitive data since it’s all pre-computed
  • no common API for access to data derived from the service, every client has to build their own conversion
  • difficult to unit test correct usage because it all relies on integration

There seems to be a natural tendency to obsessively break objects down to their smallest possible form, especially around a single database table or similar technical object.

I think the example above would have been just as good with a single object of functions to perform these conversions.

When you find yourself implementing this kind of pattern, consider using just one root service model and smarter objects to access the data.

// Persistence and external service access.
class ObjectService
{
    public function __construct($service);
    public function fetchObjectById($id);
    public function fetchExtraObjectData($id);
}

// Containing value object.
class ObjectList
{
    public function __construct($service, $id);
    public function getActualObjects();
}

// Element value object.  This can still use the ObjectService.
class ActualObject 
{
    public function __construct($objectList, $id);
    public function getActualProperty();
}

// Declare dependencies.
$service = new ObjectService($database);
$objects = new ObjectList($service, $id);
// Load usable data.
$list->getActualObjects()[0]->getActualProperty();

This gives you a more flexible implementation where your data access concerns are dealt with by the smart value objects and the service concerns are dealt with the root object service. The maintainer does not need to know anything about the interactions between these objects, they only need to know the dependencies (which should be defined by the constructor interface).

The conclusions I draw are:

  1. don’t be scared of high-level value objects
  2. dependencies are fine if they mean client code needs to know less
  3. create new abstractions only when they remove work from client code
Advertisements
Beware of Dumb Objects

Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com 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 )

Google+ photo

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

Connecting to %s