Decoupling your application from the framework

There are a lot of articles written on how to decouple a framework. There are methods to separate the presentation and persistence layer. But these are not the only things a framework can offer. For example, Symfony 2 provides a very nice process component, used for executing commands via the command line.

I was recently working on a project which involved executing some grunt tasks. I managed to decouple the persistence and presentation layers, and I didn’t want to use the process component directly in my domain objects. This would allow me to change from the Symfony process component to any other package that could run commands, whenever I wanted. I didn’t want my application’s objects to know that they are using Symfony.

The solution I used, is separating the Symfony process component behind an interface. I don’t want to give away the details of the project I am working on, so let’s assume that we are building a continuous integration server like Jenkins or Travis. Let’s say that each of our projects would have a grunt build task that we would need to run and get output from.

That initial code could look like this:

use Symfony\Component\Process\Process;

class Project
{
    protected $name;
    protected $path;

    public function __construct($name, Path $path)
    {
        $this->name = $name;
        $this->path = $path;
    }

    public function build()
    {
        $process = new Process("cd ".$this->path->getPath() . " && grunt build");
        $process->run();

        if (!$process->isSuccessful()) {
            throw new \RuntimeException($process->getErrorOutput());
        }

        return $process->getOutput();
    }

}

Our class uses the Symfony process component directly. If we wanted to switch to another way of running commands, we would have to change all the objects that know about the process component. Also, for our unit tests we wouldn’t want to actually run build commands and wait for their responses. To decouple this, we could first extract the actual process of running a command to a separate class, and send that to our build method:

use Symfony\Component\Process\Process;

class SymfonyProcessCommandRunner
{
    public function run($command)
    {
        $process = new Process($command);
        $process->run();

        if (!$process->isSuccessful()) {
            throw new \RuntimeException($process->getErrorOutput());
        }

        return $process->getOutput();
    }
}
class Project
{
    protected $name;
    protected $path;

    public function __construct($name, Path $path)
    {
        $this->name = $name;
        $this->path = $path;
    }

    public function build(SymfonyProcessCommandRunner $commandRunner)
    {
        $command = "cd ".$this->path->getFullPath() . " && grunt build";

        $output = $commandRunner->run($command);

        return $output;
    }
}

This looks much better, but if we wanted to change the command runner, we would still have to change the Project class, we would have to change the SymfonyProcessCommandRunner typehint with another command runner. Also, there is no way to send a mock command runner, so that we don’t actually run commands while running unit tests (this actually can be done with PHPUnit’s mocks, but it’s not very elegant, it’s much better to mock an interface).

To make things even better, we can introduce an interface that will separate the Project class from the actual implementation of the command runner.

interface CommandRunner
{
    public function run($command);
}

Now our project entity could become this:

class Project
{
    protected $name;
    protected $path;

    public function __construct($name, Path $path)
    {
        $this->name = $name;
        $this->path = $path;
    }

    public function build(CommandRunner $commandRunner)
    {
        $command = "cd ".$this->path->getFullPath() . " && grunt build";

        $output = $commandRunner->run($command);

        return $output;
    }
}

Basically we are saying, that the project class doesn’t care what object is sent to the build method, as long as it can run commands, or at least act as something that can run commands.
As you can see, now our domain entity knows nothing about the Symfony process component. We can mock the CommandRunner for unit tests, or switch any time to a better or more suitable implementation without touching our Project class.

You could argue that the Symfony Process Component already implements an interface, why don’t we use that ? In my opinion, that interface is external. The CommandRunner belongs to our application, the actual implementation is external. Besides that, this is a very simple example, our interface could provide other application specific methods. We have complete control to modify our interface to suit our needs, the one in Symfony not so much.

Make sure not to add any business logic to the implementation behind the interface. That class should be dumb, it should know how to run the command and return the output without knowing how the command is built, or how to interpret the output.

Single responsibility principle

This is a great example of the single responsibility principle (The S in SOLID). Before this, our project entity knew too much about running commands, it had more than one reasons to change. If we changed the way our commands were executed, we would have to change the Project class, which had nothing to do with it. Running the commands is a very clear example of a different responsibility, and it should be extracted to a different class.

Another example of a responsibility we could extract out of this class is building the actual command. We now use grunt to build projects, but in the future we might want different build commands, using other systems. We could extract that to a class like GruntCommandBuilder, that could create the command. This class should implement a CommandBuilder interface. Adding new ways to build the project would just involve making a new class that would implement the CommandBuilder interface. For example, we can add a GulpCommandBuilder without doing any changes to the Project class.

Dependency inversion principle

This is also an example of the dependency inversion principle (The D in SOLID). This principle states that we should depend on abstractions and not on concrete implementations. The CommandRunner interface is an abstraction, the SymfonyProcessCommandRunner is a concrete implementation.

That is very useful when you want to mock classes, or you want to reserve the right to switch implementations at any time without changing multiple classes to do this.

Conclusion

This is a nice example on how to decouple from a framework component. Ideally, your application code should not know anything about any external packages that it uses. This gives you the possibility to switch to different or better implementations with minimal effort and cost. If you do this right, you could also switch or update frameworks when you need to.

Robert C. Martin once said that great architecture allows you to postpone big decisions. Choosing a framework is one of the biggest decisions you need to make. Decoupling from it allows you to write the actual application code, and pick a framework when it’s most convenient for you. It’s not the primordial decision of the project.

 

Ovidiu Maghetiu

Passionate Full-Stack Developer with more than 10 years experience in building complex web apps.