Wednesday, November 19, 2014

A Decoupled Application in PHP: Putting the Pieces Together

In this article, I'm going to discuss how to use existing modern programming patterns (such as, Dependency Injection, Dependency Container, REST services, Unit of Work, and Repository) to build a decoupled application in PHP.

There were two reasons behind the second part of this article's title (Putting the Pieces Together). First, this article brings many programming concepts (which are the pieces) together and describes an example architecture to do that. Second, the example architecture describes how the application is formed of de-modularized components (pieces) that are brought together through interfaces. Although, I focus on PHP in this article, almost all of the concepts I discuss here are applicable to other Object-Oriented Programming languages.

Many (older) PHP projects couple the view, business logic, and database access. Many of us have seen code like the one below:

<?php
if(isset($_POST['username') && isset($_POST['password'])){
... connect to database and verify user
}
?>
<HTML>
... HTML code to display login form (may contain <?php ?> tags within the form)

This code is not acceptable in today's standards. Fortunately, many of the developers have moved away from this pattern and are using patterns such as MVC and/or RESTful services, which decouple the view and client code from the back-end code. I hope you are doing the same!

Although many projects have moved to an MVC or RESTful pattern, I've seen projects where the back-end code is still tightly coupled. This article aims at discussing approaches and patterns that can help you decouple your back-end code. The example architecture is written with RESTful services in mind. However, the most of the concepts apply to an MVC pattern.

This article demonstrates how two well-known concepts, Dependency Injection and a Dependency Container, can be used to achieve the most decoupling of the back-end code components. These concepts are related but refer to different things. If you have not heard of them before, then I highly recommend readying about them here (this is also a link that explains Dependency Injection in a simple way: http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html).

Dependency Injection refers to the passing of a class' dependencies (properties or constructor parameters that are instances of other classes) as parameters into the constructor or setter methods. This allows us to unit test a class by "mocking" its dependencies and passing the mocked objects.

A Dependency Container (also called an Inversion of Control Container) is a mechanism which allows you to get concrete objects from abstractions (e.g., interfaces and classes). A Dependency Container instantiates an object for you and passes its required dependencies (i.e., it performs Dependency Injection for you). A Dependency Container is the "glue" that brings the different components of the application together. By centralizing the Dependency Injection concern in the Container, you make it make easy to change the implementations of specific components in your application. The example code snippets later in this article will help solidify these concepts.

Application architecture

Components of the application

This diagram describes how the different components of the application interact.





  1. Web: The RESTful HTTP request comes through the Web component. This component extracts the service name and JSON data and forwards that to the Broker component.
  2. Broker: The broker component maps the JSON data to into a request object, instantiates the corresponding service object, and passes the request object to the service object.
  3. Services: The responsible service object first validates the passed in request object checking that it contains valid data, and then processes the request.
  4. Unit of Work: This component implements the Unit of Work pattern. This component allows the Services component to query and persist data. The Unit of Work component keeps a list of Repositories. One for each Entity type.
  5. Data Model: This component implements the Repository pattern. It manages the list of Entities (e.g., User) and their Repositories.
  6. Providers: This component contains a list of providers. Examples of providers are: Logging and Email.
  7. Helpers: This component contains a list of utility classes. Examples of helpers are: JsonMapper (maps data from a JSON object into a specific object instantiated from a class).
  8. Interfaces: This component provides the interfaces for the entire application. These interfaces define the contract between the different components of the application, which allows us to program against interfaces and not implementations.
  9. Container: This component is responsible for injecting the specific implementation for each interface and is responsible for resolving the dependencies.

Something to note here is that we do not need a Web interface in order to be able to utilize this application. For example, we may want our application to work for native applications (e.g., native mobile applications) without utilizing the HTTP protocol. We can define a TCP/IP interface that accepts commands and returns results all through TCP/IP. This interface will replace (or can run in parallel with) the Web component. This is one of the advantages of having the components decoupled. Another advantage is the ability to shield your application from relying on a specific vendor package. For example, I use Doctrine 2 for Object Relational Mapping. If you want to switch to another package, then all you need to do is implement the interfaces in decoupled-app/Interfaces/DataModel and the EntityManagerInterface.

The Sample Application

I've developed a sample application that demonstrates how the components above can be implemented in PHP. The sample application performs basic CRUD operations against a single users table. There are only four supported services: AddUser, UpdateUser, DeleteUser, and GetAllUsers. (You may find it easier to follow along if you install the application in your environment. To do that, skip to the Sample Application Installation section.)

The Container
(see decoupled-app/Container/Container.php)

The sample application demonstrates an implementation of a Dependency Container. The Container utilizes annotations to know the type of each dependency (constructor parameter or property) in a class. There are a number of dependency injection frameworks out there (e.g., PHP-DI and Pimple) and I'm not aiming to compete with them. Rather, I want to provide an example container for illustration purposes.

The container in this article provides three different ways to register a dependency (see file /Container/ContainerFactory.php for examples):

  1. Registering the dependency as a Type. This will cause a new object to be returned every time a dependency needs to be resolved.

    $typeRegistry["DecoupledApp\\Interfaces\\DataModel\\Entities\\UserInterface"] = "\\DecoupledApp\\DataModel\\Entities\\User"
  2. Registering the dependency as a Singleton. In this case, the Container will call the "getInstance()" static function to get the object instance that resolves the dependency.

    $singletonRegistry["DecoupledApp\\Interfaces\\UnitOfWork\\UnitOfWorkInterface"] = "\\DecoupledApp\\UnitOfWork\\UnitOfWork";
  3. Registering the dependency as a closure (anonymous function). In this case, the Container will execute the function. The function is expected to return the instance that resolves the dependency.

    $closureRegistry["DecoupledApp\\Interfaces\\DataModel\\Repositories\\UserRepositoryInterface"] =
    function() {
      global $entityManager;
      return $entityManager->
        getRepository("\\DecoupledApp\\DataModel\\Entities\\User");
    };
The container relies on annotations to resolve constructor parameters and properties (see method "resolve" in /Container/Container.php). Each constructor parameter must have the @param annotation that defines its type. Similarly, each property must have the @var annotation that defines its type. If the property is private, then the container relies on its setter to set the property value. The setter's name must start with "set" followed by the property name starting with a capital letter (e.g., unitOfWork property must have setter setUnitOfWork). The container keeps tracing the dependency tree until all dependencies are resolved.

The Container implements the \DecoupledApp\Interfaces\Container\ContainerInterface and it is injected itself into objects that need to utilize the Container. So you can easily switch the Container implementation with any other Container implementation you like.

The Broker 
(see decoupled-app/Broker/Broker.php)

The main function in the Broker class is the callService function. This function accepts the request type (PUT, GET, POST, DELETE), the service name, and the data in JSON format. The function goes through the following steps:

  1. It uses the service name and the request type to identify what service object needs to be created and calls the ContainerInterface->make function so that the service object can be created (with all its dependencies resolved).
  2. It utilizes the JSON Mapper to map the JSON data into a request object.
  3. It calls the validate and process functions of the service object to obtain the request result.
  4. It calls the ServiceResultInterface->serialize() function to serialize the result object into a JSON string.

Adding a Service

The steps below show how a new service is added:

  1. Depending on the service type, add a folder with the service name under one of the folders in decoupled-app/Services.
  2. Add a service file and class, and a request file and class under the directory in Step 1. The service class must extend (inherit) the \DecoupledApp\Services\ServiceBase class. The application utilizes autoloading (see decoupled-app/autoload.php) so it's important that you add a namespace for each added class and that the namespace matches the folder structure.
  3. Implement the abstract methods validate and process.


The unitOfWork property will be automatically injected into your service object. So you can use it to retrieve a specific entity's repository and query or persist data.

Adding an Entity

Adding an new Entity is a bit more involved than adding a Service. To add an Entity:

  1. Add an interface file for the Entity under decoupled-app/Interfaces/DataModel/Entities. The interface must extend the EntityInterface and define a setter and getter for each of the Entity fields.
  2. Add an interface file for the Entity Repository interface under decoupled-app/Interfaces/DataModel/Repositories. The interface must extend the RepositoryBaseInterface.
  3. Add a class file for the Entity under decoupled-app/DataModel/Entities. The class must: (a) Implement the interface defined in Step 1 (don't forget to implement the jsonSerialize function), (b) extend the \DecoupledApp\DataModel\Entities\EntityBase class, and (c) define the necessary Doctrine 2 annotations for the class the properties (see the decoupled-app/DataModel/Entities/User.php file for examples).
  4. Add a class file for the Entity Repository under decoupled-app/DataModel/Repositories. The class must implement the interface defined in Step 2 and extend the \DecoupledApp\DataModel\Repositories\ReposityBase class.
  5. In \DecoupledApp\Interfaces\UnitOfWork\UnitOfWorkInterface, add a setter function and getter function for the new repository interface (defined in Step 2). See the functions for the UserRepositoryInterface as an example.
  6. In \DecoupledApp\UnitOfWork\UnitOfWork, add a private property (e.g., $userRepository) for the new repository interface (don't forget to add the @var annotation so that the Container knows which type to resolve), and implement the setter and getter for the new repository interface.
  7. Define the mappings from the new interfaces to the new concrete classes in the decoupled-app/Container/ContainerFactory.php file. Here are the mappings defined for the User Entity and Repository.

    $typeRegistry["DecoupledApp\\Interfaces\\DataModel\\Entities\\UserInterface"] =
      "\\DecoupledApp\\DataModel\\Entities\\User";
    $closureRegistry["DecoupledApp\\Interfaces\\DataModel\\Repositories\\UserRepositoryInterface"] =
      function() {   
        global $entityManager;
       
         return $entityManager
    ->getRepository
        ("\\DecoupledApp\\DataModel\\Entities\\User");
     
      };
  8. Finally, from the command prompt (or terminal), navigate to the decoupled-app/Container directory and run the command "../vendor/bin/doctrine orm:schemal-tool:update --force".

Adding Providers and Helpers

Adding providers or helpers is fairly straightforward. Add an interface under the decoupled-app/Interfaces/Providers folder or the decoupled-app/Interfaces/Helpers folder, add a class for the provider or helper, and then update the mapping in the decoupled-app/Container/ContainerFactory.php file. See the examples for the LoggingProvider and the JsonMapper.

Sample Application Installation

Prerequisites:

  1. MySQL (version 5.6.17)
  2. PHP (version 5.5.12)
  3. Apache (version 2.4.9)
  4. Composer

The versions above are the version that I used to run the application. It is highly likely that slightly older versions would work as well, I just haven't tested them.

Installation steps:

  1. Download the code from https://github.com/abdulla16/decoupled-app.
  2. Navigate to the root directory of the project (decoupled-app) and run command "composer install". This should download the project dependencies into the decoupled-app/vendor directory.
  3. Create a database called "decoupled_app".
  4. Create a database user (grant all permissions) with name "d_app_user" and password "123456". Note: you can change these settings as long as you change the information in decoupled-app/Container/bootstrap.php.
  5. Using the command prompt (or terminal), navigate to directory /Container and run the command "../vendor/bin/doctrine orm:schema-tool:create". This should create the user table under the decoupled_app database.
  6. Point your Apache server to the directory decoupled-app/Web.

That's it!

Now you can test adding a user:

  1. Launch the localhost/index.html page in your browser.
  2. Select the PUT request type.
  3. Enter service name: addUser.
  4. Enter JSON data:

    {"firstName": "First",
    "lastName": "Last",
    "userName": "username"}
  5. Click "Call Service". Now the new user should be created.


Future Work

I still need to implement tests for each of the components of the application. I'm also planning on writing another article to discuss how you can implement an object-oriented, decoupled, and unit testable front-end using JavaScript. So stay tuned!

Conclusion

Decoupling an application has many advantages; the main ones are testablility, maintainability, and extendability. Utilizing modern patterns (Dependency Injection, Dependency Container, REST services, Unit of Work, and Repository) when developing your software architecture will help you decouple your code. This article demonstrates, through an example application, how these patterns can be utilizes.

Community feedback is very important to me! Do you have any comments? Let me know below. Thanks for taking the time to read!