<

Go Premium for a chance to win a PS4. Enter to Win

x

Software Design: Dependencies

Published on
19,393 Points
3,793 Views
1 Endorsement
Last Modified:
Awarded
Dependencies in Software Design
In software development, the idea of dependencies is an issue of some importance. This article seeks to explain what dependencies are and where they can create a problem for growth or an opportunity for excellence.

In a nutshell, if program "A" calls upon program "B" for some data or service, program "A" is said to have a dependency on program "B." If the interface that program "A" uses to call program "B" changes, then program "A" must be changed, too. This sort of situation may be easy to fix if it is an isolated case (perhaps all calls to program "B" are routed through a particular class method in program "A"). However if the coupling is not an isolated interface, the dependency on program "B" can be a real risk to the continued health of program "A."

To try to illustrate this in practical terms, let's consider an issue that many PHP programmers are facing right now. They developed web sites or web applications that are dependent on the familiar but obsolete MySQL extension. Throughout their scripts, we find numerous instances of mysql_connect(), mysql_query(), mysql_fetch_assoc(), etc. There are red warning labels on all of those main pages! It has been a couple of years since PHP deprecated the MySQL extension, and these function calls have begun to throw Warning messages. There is not any "good fix" for this problem -- the only solution is the awkward and time-consuming process of manually remediating the PHP scripts, an issue we tackled in this article. These PHP scripts have a tightly coupled dependency on MySQL, and it's not a good thing!

Abstraction Layers to Isolate Dependencies
Some developers, anticipating this problem, created database abstraction layers. Instead of calling the PHP MySQL functions directly, they built their PHP code with an interface that put a database wrapper class around the MySQL functions. The underlying database extension was still MySQL, but instead of calling the MySQL functions directly in the "whole cloth" of the web pages and applications, the web pages and applications called specialized functions in the abstraction layer. This has huge benefits for the programmers who must update PHP scripts to remove the dependency on obsolete MySQL functions, because all of the calls to the obsolete functions are contained in one place. They simply have to fix the abstraction layer and the entire web site or application is automatically modernized. Easy!

The abstraction layer is a good step in the right direction, and it makes for easier updates but in a way, it only hides the dependency, moving it away from the application code. The dependency is still there; it's just hidden from view by a static abstraction layer. By "static" we mean that the abstraction layer is a fixed entity. It hides the dependency, and this makes for cleaner code, but by itself it can't change or remove the dependency.

Here is a somewhat contrived code example of a static abstraction layer, in the form of the WeatherInformationBoston class. In this example we have an application that gets the Boston weather from a service. The WeatherInformationBoston class "hides" the implementation details from the rest of the program, but it's only able to get the weather for Boston, because the URL of the weather service is hard-wired into the class. (I know you would never do something like this, but it's a contrived example intentionally made wrong to illustrate a point).
 
<?php // weather_information_boston.php
error_reporting(E_ALL);

/**
 * A Class that gets current weather information from a data source
 */
Class WeatherInformationBoston
{
    // THE GETTER METHODS THAT MIGHT BE REQUIRED BY AN INTERFACE
    public function getTempF()
    {
        return $this->fahrenheit;
    }

    public function getTempC()
    {
        return $this->centigrade;
    }

    // THE CONSTRUCTOR SETS UP THE PATH TO THE DEPENDENCY
    public function __construct()
    {
        $this->getCurrentWeather('http://iconoun.com/demo/weather_service_boston.php');
    }

    // THE ACCESSOR METHOD USES THE PATH TO THE DATA
    public function getCurrentWeather($url)
    {
        // READ AND INTERPRET THE WEATHER INFORMATION
        $jso = file_get_contents($url);
        $obj = json_decode($jso);

        // SET THE OBJECT PROPERTIES FROM THE EXTERNAL DATA SOURCE
        $this->fahrenheit = $obj->fahrenheit;
        $this->centigrade = $obj->centigrade;
    }
}

$weatherObject = new WeatherInformationBoston();

// ACCESS THE WEATHER INFORMATION
$f = $weatherObject->getTempF();
$c = $weatherObject->getTempC();

// PREPARE A REPORT
$weather_report = <<<EOD
In Boston, the temperature is: $f&deg;F ($c&deg;C)
EOD;

echo $weather_report;

Open in new window


If we also needed to get the weather for Miami, we would need to set up another class, maybe WeatherInformationMiami. If we needed to get the weather for hundreds of cities we would need hundreds of classes. And if we had a change in the weather services that provided our data we would need to change hundreds of scripts. Just like the painful remediation of MySQL, this is a difficult and time-consuming problem with no good solution. The only way to handle this problem is to anticipate the problem and use a design pattern that allows us to avoid the problem completely.

Dependency Injection
Dependency Injection ("DI") is the term given to the real-time resolution of programmatic dependencies. It has all of the benefits of static abstraction layers, but it provides us with a dynamically created abstraction layer. The implementation details of the abstraction layer are created at run-time and the dependencies can be injected into the application. There are three main types of dependency injection: Constructor injection, Setter injection and Interface injection (see Further Reading, below). The implementation details differ, but they all share the ability to allow a run-time resolution of the dependencies, and they all avoid the same problem of binding your code to a hard-wired dependency. In our example below, we modify the WeatherInformationBoston class to generalize the class so that it can use other weather services and thereby return weather information from other cities.
 
<?php // weather_information.php
error_reporting(E_ALL);

/**
 * A Class that gets current weather information from a data source
 */
Class WeatherInformation
{
    // THE GETTER METHODS THAT MIGHT BE REQUIRED BY AN INTERFACE
    public function getTempF()
    {
        return $this->fahrenheit;
    }

    public function getTempC()
    {
        return $this->centigrade;
    }

    public function getDataSource()
    {
        return $this->dataSource;
    }

    // THE CONSTRUCTOR SETS UP THE PATH TO THE DEPENDENCY
    public function __construct($url)
    {
        $this->getCurrentWeather($url);
    }

    // THE ACCESSOR METHOD USES THE PATH TO THE DATA
    public function getCurrentWeather($url)
    {
        // READ AND INTERPRET THE WEATHER INFORMATION
        $jso = file_get_contents($url);
        $obj = json_decode($jso);

        // SET THE OBJECT PROPERTIES FROM THE EXTERNAL DATA SOURCE
        $this->dataSource = $obj->dataSource;
        $this->fahrenheit = $obj->fahrenheit;
        $this->centigrade = $obj->centigrade;
    }
}

/**
 * Constructor Injection Sets the URL path to the weather data
 */
$src = 'http://iconoun.com/demo/weather_service_miami.php';
$weatherObject = new WeatherInformation($src);

// ACCESS THE WEATHER INFORMATION
$d = $weatherObject->getDataSource();
$f = $weatherObject->getTempF();
$c = $weatherObject->getTempC();

// PREPARE A REPORT
$weather_report = <<<EOD
In $d, the temperature is: $f&deg;F ($c&deg;C)
EOD;

echo $weather_report;

Open in new window


Since we can now inject our dependencies, we can change the underlying code without any changes to our application. We simply modify the way we call the WeatherInformation class constructor (our bootstrap script), and a new dependency can be substituted. In our example, the new dependency is represented by the URL of the weather service that returns our data. As other weather services become available, we can add more cities to our application.

Dependency Injection in Automated Testing
A few years ago I wrote an article on "test-driven development."  In a nutshell, test-driven development, or "TDD" for short, is a work process that guides you to the best possible programming outcomes. You test your programming by running repeated incremental tests as you build your software. The test cases mirror the likely data sets you would encounter in the deployed environment (and some of the edge-cases).  If your software can produce predictable output for each of the test cases - and if the output is what you want - you have greatly increased the likelihood that your software deployment will succeed once it is released into the wild.

If you're smart about your TDD, you will choose automated tests. Of course you could just look at the inputs and outputs to see if they line up in a sensible way, but that approach is kind of limiting, and requires you to be engaged in a dynamic and interactive man-machine dialog for every test. Instead, we want to write automation that feeds our test data to our system under test, and checks the return values automatically. If everything works well, we can put out a "green means go" happy message. If some of the tests fail to produce the expected output, we can put out a "red means stop" message and highlight the tests that failed our expectations. This is the design that phpunit follows, and it has become one of the dominant patterns in the world of automated testing.

Phpunit creates an environment of "mock objects," where programmatic constructs are either real (meaning that they are actual instances of your code and external services) or mocked instances, given the task of standing in and given the responsibility of receiving predictable inputs and producing predictable outputs. Think of these mock objects as the crash-test-dummies of your application. If you design your tests correctly you can inject a mock object for every dependency of your code, then remove the mocks one-at-a-time, rerunning the tests each time a mock is removed and your actual code is employed. This can create a testing environment where code coverage is at or near 100%. In other words, you can test every single object through the entire application, and verify that each object works correctly.

Phpunit requires considerable setup and background understanding that is beyond the scope of this article, but we can show an example of using an automated test to verify that our code is returning the expected values. Here is a modification of the weather information script with an automated test attached at the end. Try running it as-is, then try modifying the expectation to see what happens. Or try modifying the URL path to the weather data. Well-constructed tests like these will save you many, many hours of development time!
 
<?php // weather_information_test.php
error_reporting(E_ALL);

/**
 * A Class that gets current weather information from a data source
 */
Class WeatherInformation
{
    // THE GETTER METHODS THAT MIGHT BE REQUIRED BY AN INTERFACE
    public function getTempF()
    {
        return $this->fahrenheit;
    }

    public function getTempC()
    {
        return $this->centigrade;
    }

    public function getDataSource()
    {
        return $this->dataSource;
    }

    // THE CONSTRUCTOR SETS UP THE PATH TO THE DEPENDENCY
    public function __construct($url)
    {
        $this->getCurrentWeather($url);
    }

    // THE ACCESSOR METHOD USES THE PATH TO THE DATA
    public function getCurrentWeather($url)
    {
        // READ AND INTERPRET THE WEATHER INFORMATION
        $jso = file_get_contents($url);
        $obj = json_decode($jso);

        // SET THE OBJECT PROPERTIES FROM THE EXTERNAL DATA SOURCE
        $this->dataSource = $obj->dataSource;
        $this->fahrenheit = $obj->fahrenheit;
        $this->centigrade = $obj->centigrade;
    }
}

/**
 * Constructor Injection Sets the URL path to the weather data
 */
$src = 'http://iconoun.com/demo/weather_service_miami.php';
$weatherObject = new WeatherInformation($src);

// ACCESS THE WEATHER INFORMATION
$d = $weatherObject->getDataSource();
$f = $weatherObject->getTempF();
$c = $weatherObject->getTempC();

// PREPARE A REPORT
$weather_report = <<<EOD
In $d, the temperature is: $f&deg;F ($c&deg;C)
EOD;


/**
 * Test the data against our expectations
 */
$expected_report = 'In Miami, the temperature is: 98.6&deg;F (37&deg;C)';
if ($weather_report == $expected_report)
{
    echo PHP_EOL . '<span style="color:green;">Success!</span>' . PHP_EOL;
}
else
{
    echo PHP_EOL . '<span style="color:red;">Failure Asserting that Expected ' . $expected_report . ' == Actual ' . $weather_report . '</span>' . PHP_EOL;
}

Open in new window


How DI Facilitates Automated Testing
If you cannot do dependency injection, you are very limited in how you can test your scripts. Consider the procedural MySQL example. Everything in the script depends on MySQL. The dependency is hard-wired into the code in many different places. There is no way to get to each of these hard-wired places with mock examples of MySQL. If you take MySQL out of one part of the application, you take it out of all of the places in the application, thus the application is untestable. And if an application cannot be tested, you cannot know whether it truly meets the requirements. Similarly, if we cannot test, we cannot verify that changes in our implementation of new requirements have not introduced errors in other parts of the application. Fix one, break two - that's never a happy path.

But if we have object-oriented MySQL and we inject the MySQL object into our classes, we can selectively mock the MySQL object in each of the classes. It can be the "real" database in some parts of the application and a mock object in other parts. This means we have great control over the way MySQL responds to our tests, giving us a fine granularity of code assessment, and enabling us to prove that our code works correctly. Tools like phpunit use dependency injection to insert and remove mock objects in real time, allowing rapid repeated tests. 

How easy is it to test with mock objects and DI?  In an application I'm currently developing for the Department of Defense, we run unit tests each time we make even a one-line change to the code set. There are hundreds of tests in the suite, usually at least two tests for each class method (one with expected input and success, and one with invalid input and failure) but sometimes there are more tests, if a range of inputs or outputs is in play. The entire application can be tested, with 100% code coverage, in less than 10 seconds. This sort of exhaustive testing would be impossible with hard-wired dependencies. 

Summary
This article has explained the difference between built-in dependencies, abstraction layers, and dependency injection. We have shown ways to make our code more flexible and more testable.  Using design patterns like these we can build code that is highly dependable and easy to reuse in other applications.

Addendum
Here are the "weather service" scripts that are used as dependencies in the code examples above.
<?php // weather_service_boston.php
error_reporting(E_ALL);

$data = new StdClass;
$data->dataSource = 'Boston';
$data->fahrenheit = 50;
$data->centigrade = 10;

echo json_encode($data);

Open in new window


<?php // weather_service_miami.php
error_reporting(E_ALL);

$data = new StdClass;
$data->dataSource = 'Miami';
$data->fahrenheit = 98.6;
$data->centigrade = 37;

echo json_encode($data);

Open in new window


Further Reading (other opinions, etc.)
http://fabien.potencier.org/article/11/what-is-dependency-injection
http://code.tutsplus.com/tutorials/dependency-injection-in-php--net-28146
http://martinfowler.com/articles/injection.html
https://r.je/constructor-injection-vs-setter-injection.html
http://www.sitepoint.com/dependency-injection-with-pimple/
http://symfony.com/doc/current/components/dependency_injection/types.html
http://www.whitewashing.de/2008/09/20/dependency-injection-via-interface-in-php-an-example.html
http://www.sitepoint.com/dependency-injection-laravels-ioc/
http://culttt.com/2014/03/24/exploring-laravels-ioc-container/

Please give us your feedback!
If you found this article helpful, please click the "thumb's up" button below. Doing so lets the E-E community know what is valuable for E-E members and helps provide direction for future articles.  If you have questions or comments, please add them.  Thanks!
 
1
Comment
Author:Ray Paseur
1 Comment
 

Expert Comment

by:calslim
Another great article! I have started using DI more and more in my code. I have not as yet done much with phpunit and automated testing, largely because there does not seem to much in the way of real-world, yet easily approachable "getting started" material. For example, many tutorials start with the obligatory test add function. assert that a call to add(1,2) equals 3. This is all well and good, but then they fall off quickly and do a bit of handing waving over mocks, stubs, automation, etc. But I digress.

One question I have about DI, is how to decouple use of global functions (particularly in a framework). After all, PHP is essentially a language and a framework. I total get DI with objects. For example, instantiate an object, then pass that instance into a function or method of another object.

But what about decoupling functions?

A bit of a contrived example:

class Gatekeeper
{
    public $passphrase;
    private $secret;

    function __construct($phrase) {
        $this->passphrase = $phrase;
    }

   private function validate(){
       return $this->secret == md5($this->passphrase);
   }

  public response(){
     if ($this->validate()){
         return 'Welcome. May I take your coat?';
     }
     else{
         return 'Get lost!';
    }
  }
}

Open in new window


// The above code is dependent on MD5. MD5 is a function, not an instantiable class; what is best practice to inject this dependency analogous to:

$JoeTheBouncer =  Gatekeeper('Shazaam', md5);
$JoeTheBouncer->response();

Open in new window

0

Featured Post

NFR key for Veeam Backup for Microsoft Office 365

Veeam is happy to provide a free NFR license (for 1 year, up to 10 users). This license allows for the non‑production use of Veeam Backup for Microsoft Office 365 in your home lab without any feature limitations.

Join & Write a Comment

The viewer will learn how to create and use a small PHP class to apply a watermark to an image. This video shows the viewer the setup for the PHP watermark as well as important coding language. Continue to Part 2 to learn the core code used in creat…
In this video, Percona Solutions Engineer Barrett Chambers discusses some of the basic syntax differences between MySQL and MongoDB. To learn more check out our webinar on MongoDB administration for MySQL DBA: https://www.percona.com/resources/we…
Suggested Courses

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month