Qafoo GmbH - passion for software quality ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Author: Tobias Schlitt :Date: Fri, 29 Sep 2017 08:40:59 +0200 :Revision: 4 :Copyright: All rights reserved ======================== Injectables vs. Newables ======================== :Abstract: 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. :Description: 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. :Keywords: object oriented design, transaction script 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: a) Newables **must not** depend on injectables and b) 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:: 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:: 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:: 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 ----------- .. note:: 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. __ /services/workshops/object_oriented_design.html 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: a) Newables **must not** depend on injectables and b) 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! .. Local Variables: mode: rst fill-column: 79 End: vim: et syn=rst tw=79 Trackbacks ========== Comments ======== - ApeHanger at Tue, 24 Oct 2017 13:14:52 +0200 Nice article. There is a typo, do s/funtcion/function/ regards ApeHanger - Daniel at Tue, 24 Oct 2017 21:56:10 +0200 Don't you think it would have been enough to link to http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/? - http://star-writers.com/ at Fri, 09 Mar 2018 11:16:10 +0100 Every single article is filled up with something extremely original and unique! Thanks for doing your painstaking job!