Qafoo GmbH - passion for software quality

Help you and your team benefit from new perspectives on cutting-edge quality engineering techniques and tools through the Qafoo team weblog.

By Tobias Schlitt, first published at Tue, 24 Oct 2017 09:40:49 +0200

Download our free e-book "Crafting Quality Software" with a selection of the finest blog posts as PDF or EPub.

Crafting Quality Software

You can also buy a printed version of the book on Amazon or on epubli.

Injectables vs. Newables

Many projects I join - even those that claim to already do dependency injection - suffer from issues that result from mixing injectable and newable classes. Keeping these two appart seems to be challenging for many developers so that I try to give them a handy guide with Do's and Dont's in this blog post.

Lets clarify the difference first: Injectables perform actual work, talk to external systems or to other injectables. Newables hold state (data) and potentially perform work on this data (exclusively). As examples for these you can peak at Domain Driven Design (DDD): Services are injectable - entities and value objects are newable.

Two fundamental constraints apply to the concepts of newables and injectables:

  1. Newables must not depend on injectables and

  2. Injectables must not hold newables as their state (object attributes).

Our experience shows: Violating one of these eventually leads to hardly debuggable side-effects and technical debt. Sticking to these is a first big step into better maintainable software. I'll showcase this on basis of examples violating these rules.

Injectable Violation

Given this simple service as an example:

<?php class Warehouse { private $productRepository; public function __construct(ProductRepository $productRepository) { $this->productRepository = $productRepository; } public function pickItem($id, $count) { // ... } }

The class has only 1 dependency and 1 method: it works against the ProductRepository and pickItem allows to pick a number of items by ID from the warehouse and to keep track of picked items. Various implementations are possible for this functionality. One I saw is the following:

<?php class Warehouse { private $productRepository; private $pickedItems = []; // ... public function pickItem($id, $count) { $productDetails = $this->productRepository->find($id); $this->pickedItems[] = new Pick($productDetails, $count); } public function getPicks() { return $this->pickedItems; } }

This implementation problematic, because the class does two entirely different things at the same time: a) it fetches product details from somewhere (possibly a database) and b) it keeps track of the items and counts picked from the warehouse.

Now, what happens if two or more pick tours need to be created subsequently? Of course, the $pickedItems collection needs to be reset for each of them. Maybe these pick tours are even planned at entirely different places of the application. Now these code parts would influence each other if they receive the same Warehouse instance through dependency injection. Or even worse, some code-parts rely on receiving the very same instance of the warehouse to interchange a pick tour and due to some change in application configuration this changes. Such effects are generally called side effects and such can lead to extremely hard to find bugs.

So, what is the way to prevent such issues? We need to separate state artifacts from the working code artifact. In this specific case an example solution could be:

<?php class Warehouse { private $productRepository; public function pickItem($id, $count) { $productDetails = $this->productRepository->find($id); return new Pick($productDetails, $count); } }

and in the place using this Warehouse service:

// ... $picks[] = $warehouse->pickItem(23, 5);

or, if you want to wrap the collected picks into an object that maintains the collection:

$pickCollection = new PickCollection(); // ... $pickCollection->add($warehouse->pickItem(23, 5));

Now we have two code entities that a each perform a dedicated part of the work: a) the Warehouse is responsible for interacting with the external service and creates the picks and b) the PickCollection keeps track of the current state of picks.

We can also now safely pass around the Warehouse service without fear of unwanted side-effects. On the other hand we can also pass around a PickCollection to make it be used by various code pieces and with that indicate explicitely that state is meant to be changed.

Newable Violation

We already saw the extraction of a newable in the previous example: the PickCollection. In contrast to an injectable - which is injected from the outside everywhere it is needed - a newable can be created anywhere in the code to wrap data and represent a logical entity. For example:

class ProductDetails { private $title; private $availableInCountries = []; // ... public function getTitle() { return $this->title; } public function isAvailableIn($countryCode) { return in_array($countryCode, $this->availableInCountries); } }

This one holds state about a product title and in which countries the product should be available. In addition to that it wraps some simple logic. Now, what if we introduce some ActiveRecord functionality to make storing of product details easier? This could look like:

class ProductDetails { // ... public function __construct(DatabaseConnection $connection /* ... */) { $this->connection = $connection; } public funtcion save() { // ... prepare SQL ... $statement = $this->connection->prepare($sql); // ... bind parameters ... $statement->execute(); } }

Pretty convenient, now? Well, yes and no: Of course it is convenient to simply store a product by calling save() on the object. But it also has some very bad drawbacks: Every time you want to create a product in the code now, you need to have a DatabaseConnection available. This actually reduces the convenience of working with the ProductDetails class a lot. The possibly worst place is inside of unit tests: you will be required to create a mock for DatabaseConnection everytime you want to work with ProductDetails.

What went wrong? The DatabaseConnection is an injectable and the rule is: "Newables must not depend on injectables". Ripping out the code parts into their own injectable is the solution here:

class CodeDetailsRepository { public function __construct(DatabaseConnection $connection /* ... */) { $this->connection = $connection; } public funtcion save(ProductDetails $product) { // ... prepare SQL ... $statement = $this->connection->prepare($sql); // ... bind parameters ... $statement->execute(); } }

Bottom Line

Do you want to get training to learn more about Object Oriented Design? Qafoo can help you and unlock the potential of your development team. Book a workshop on Object Oriented Design now.

Keeping newables and injectables separate helps you to avoid a large portion of nasty bugs and eases working with either of these object categories. Follow the simple rules:

  1. Newables must not depend on injectables and

  2. injectables must not hold newables as their state (object attributes).

As usual in software engineering there are cases where you cannot entirely avoid mixing them up. Every project has one or two of these places. Try to keep them as small as possible and to draw a solid border between these rare places and your remaining code to keep it as clean as possible [1] .

1
For example, think about the concept of TokenStorage and RequestStack in Symfony. Do yourself a big favor and use these injectables only when really necessary!

Download our free e-book "Crafting Quality Software" with a selection of the finest blog posts as PDF or EPub.

Crafting Quality Software

You can also buy a printed version of the book on Amazon or on epubli.

Get Technical Insights With Our Newsletter

Stay up to date with regular new technological insights by subscribing to our newsletter. We will send you articles to improve your developments skills.

Comments