This blog post will give an introduction to the appserver.io implementation of techniques such as Design by Contract and AOP and how they can be used. As an example we will make a simple and self validating JSON REST webservice.

Our webservice in question exposes a basic CRUD like JSON API and can be used to store object representations of user accounts. What we will focus on is the handling of invalid data and how to respond to malformed requests without much (nearly none) effort from the developer side.

appserver.io utilizes the appserver-io/doppelgaenger library and allows to use its functionality within any webapp using annotations and XML configuration. This example makes use of this as a showcase example to further expand on.

How it works

To make our API self validation we have to meet two requirements:

  • We have to define valid/invalid states and transitions
  • We have to define a reaction to both

Defining valid/invalid states

To define how our API is used in a valid or invalid way we will make use of the Design by Contract (DbC for short) feature of appserver.io. Using it can be done by placing well designed constraints into our applications. These constraints define so called contracts, doublets of pre- and post-condition and a coherent state, the invariant, which marks the valid state and transitions our app can handle.

You can define these contracts using Doctrine-like annotations containing valid PHP code conditions resulting in the boolean TRUE or FALSE.

Any breach of a contract will result in an exception of the type AppserverIo\Psr\MetaobjectProtocol\Dbc\ContractExceptionInterface which we can later handle centrally.

Preconditions can be annotated using the @Requires annotation, whereas post-conditions are defined using the @Ensures annotation.

So to explain how we use these concepts, we will have to have a look at the flow of data through our application.

If a request hits any of the actions we defined (more on that later) the request content will be passed to a connector class (JSON in our example) which tries to extract the needed information from the request message. So basically turning JSON into objects of a certain type.

But as we do so, we want to also validate every step on the way. Have a look how we do it:

<?php
/**
 * Will create an instance of the $targetEntity class based on the data given as 
 * raw string
 *
 * @param string $rawData      Data string to generate the instance from
 * @param string $targetEntity The fully qualified class name to create an instance from
 *
 * @return object
 *
 * @Requires("is_object(json_decode($rawData))")
 * @Requires("class_exists($targetEntity)")
 *
 * @Ensures("$dgResult instanceof $targetEntity")
 */
public function instanceFromString($rawData, $targetEntity)
{
    // ...
}

As you can see in the example above we have used several annotations defining the following:

  • Any data coming in has to be valid JSON
  • Any object we are told to create from the JSON must be of a known class
  • The resulting object must be an instance of the specified class

This is great news! We can pass a JSON string which is validated and will be transformed into an object of our choice!

Next thing we would like to do is making sure that the resulting object itself has a coherent state.

An example would be that any user email we want to safe must be a valid email address:. We do that defining a contract for the used class setter method setEmail:

<?php
/**
 * Sets the value for the class member email.
 *
 * @param string $email Holds the value for the class member email
 *
 * @return void
 *
 * @Ensures("is_string(filter_var($this->email, FILTER_VALIDATE_EMAIL))")
 */
public function setEmail($email)
{
    $this->email = $email;
}

This @Ensures annotations defines that the property email will contain a valid email address after calling setEmail.

This technique can be used to test for password length, username syntax and a million other things.

Finally we want to collect our user entities and make sure nothing corrupts our storage.

Therefor we define an invariant which is a state of validity which is checked on every public access (read or write) on an object. This is done using the @Invariant annotation within the header

<?php
/**
 * A singleton session bean implementation that handles the
 * data by using Doctrine ORM.
 *
 * @author    Bernhard Wick <bw@appserver.io>
 * @copyright 2015 TechDivision GmbH <info@appserver.io>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/appserver-io-apps/api-guard
 * @link      http://www.appserver.io
 *
 * @Invariant("$this->isConsistent()")
 */
abstract class AbstractProcessor extends \Stackable implements ProcessorInterface
{
    // ...
}

Any class inheriting from our AbstractProcessor must implement the isConsistent method which is used to check for the storage consistency at every access. Atomic consistency checks out of the box.

So we got:

  • JSON validation
  • Creation of coherent entities
  • Continued check of storage integrity

And everything with just some small annotations!

Defining reactions to contract breaches

So we got our contracts as briefly described above which will turn every faulty request into an exception-hell.

To make sense of these exceptions we will use the power of AOP.

To understand how we will have a look at our actions which have been mentioned before.

Actions are used to expose URL endpoints which can be requested by a client. They have a simple (yet configurable) URL-class/method mapping (have a look at the routlt library we use here).

The action used for our examples is the create action which is implemented as seen below:

<?php
/**
 * Action to create an instance
 *
 * @param \AppserverIo\Psr\Servlet\Http\HttpServletRequestInterface  $servletRequest  
 *          The request instance
 * @param \AppserverIo\Psr\Servlet\Http\HttpServletResponseInterface $servletResponse 
 *          The response instance
 *
 * @return null
 */
public function createAction(
    HttpServletRequestInterface $servletRequest, 
    HttpServletResponseInterface $servletResponse
) {
    $instance = $this->connector->instanceFromString(
        $servletRequest->getBodyStream(), 
        static::TARGET_ENTITY
    );
    $proxy = $this->getProxy($servletRequest);
    $proxy->create($instance);
}

It is implemented within the abstract \AppserverIo\Apps\ApiGuard\Actions\AbstractCrudAction class and as inherited by the UsersAction class, results in the URL http://127.0.0.1:9080/api-guard/index.do/users/create being exposed to GET and POST requests.

As you can see the method makes use of all three examples made above:

  • Passing the request body to our JSON connector
  • Creating the entity object
  • Passing the entity to our storage (over a proxy class)

So every exception resulting from a DbC contract breach will pass this method!

To not litter exception handling over all actions we might need in our API service (our example only has four but use your imagination) we will use AOP to centralize exception handling.

We will do so within our AOP Aspect class \AppserverIo\Apps\ApiGuard\Aspects\ExceptionHandlingAspect:

<?php
/**
 * Aspect which is used to catch webservice DbC errors and respond to 
 * the client automatically
 *
 * @author    Bernhard Wick <bw@appserver.io>
 * @copyright 2015 TechDivision GmbH <info@appserver.io>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/appserver-io-apps/api-guard
 * @link      http://www.appserver.io/
 *
 * @Aspect
 */
class ExceptionHandlingAspect
{

    /**
     * Pointcut specifying all actions within any action class we have
     *
     * @return null
     *
     * @Pointcut("call(\AppserverIo\Apps\ApiGuard\Actions\*->*Action())")
     */
    public function allActionMethods()
    {
    }

    /**
     * Advice used to proceed a method but catch all Design by Contract 
     * related exceptions.
     * Will react on any caught exception with an error response to the client
     *
     * @param \AppserverIo\Psr\MetaobjectProtocol\Aop\MethodInvocationInterface 
     *          $methodInvocation Initially invoked method
     *
     * @return mixed
     *
     * @Around("pointcut(allActionMethods())")
     */
    public function actionExceptionToError(MethodInvocationInterface $methodInvocation)
    {
        try {
            return $methodInvocation->proceed();

        } catch (ContractExceptionInterface $e) {
            // build up the right format for our response message
            $messageObject = new \stdClass();
            $messageObject->error = new \stdClass();
            $messageObject->error->code = 400;
            $messageObject->error->message = 'Invalid request data';
            $message = $methodInvocation->getContext()
                ->getConnector()
                ->stringFromObject($messageObject);

            // get the servlet response and set the error message
            $parameters = $methodInvocation->getParameters();
            $servletResponse = $parameters['servletResponse'];
            $servletResponse->appendBodyStream($message);
            $servletResponse->setStatusCode($messageObject->error->code);
        }
    }
}

You can see two things:

  • The body-less method allActionMethods acting as a pointcut specifying all action methods in all action classes
  • The method actionExceptionToError action as Around advice handling the exception

First things first, the pointcut:

Pointcuts are used to specify certain events at certain places within your applications flow.

The annotation @Pointcut("call(\AppserverIo\Apps\ApiGuard\Actions\*->*Action())") allows us to have a code hook at every call to every action method of every action class we have. Let me repeat that: every call to every action method of every action class we have! :)

The advice references the pointcut using the annotation @Around("pointcut(allActionMethods())") and wraps around (hence the name Around) all specified methods. That allows us to catch every DbC exception within any action and handle it in a central way.

Using the automatically passed $methodInvocation instance we are able to access the action instance in question and therefore using the connector to append an automatic response.

Tadaa: Automatic validation of client requests using annotations!

Try it yourself!

Got a taste for AOP? Build a contracted API yourself? Have a look at the api-guard app which contains all examples and the working code to build your own webservice.

To do this, we assume you have installed appserver.io in a version of at least 1.0.0-rc3.

You also need the sources of this repository. So clone it, open a command line, change into your working directory, which can be /tmp for example, and enter:

$ git clone https://github.com/appserver-io-apps/api-guard

Now you can change into the newly created api-guard directory and use our ant app deployment by executing:

$ ant composer-init
$ ant deploy

Testing it

You can test the example's guarding functionality using simple CURL commands. Using our default example of the User entity you might try the following:

Executing any of:

$ curl -X POST --data '{"password": "test", "username": "tester", "email": "mail"}' http://127.0.0.1:9080/api-guard/index.do/users/create
$ curl -X POST --data '{"username": "tester", "email": "mail@mail.me"}' http://127.0.0.1:9080/api-guard/index.do/users/create
$ curl -X POST --data '{"password": "test", "email": "mail@mail.me"}' http://127.0.0.1:9080/api-guard/index.do/users/create

Will return the message:

{
    "error": {
        "code": 400,
        "message": "Invalid request data"
    }
}

as at least one of our constraints has been broken.

Sending a request as such:

$ curl -X POST --data '{"password": "test", "username": "tester", "email": "mail@mail.me"}' http://127.0.0.1:9080/api-guard/index.do/users/create
$ curl -X POST --data '{"id": "f6e8f4c5073ee49d73e4a05b703c93de0e7bc321"}' http://127.0.0.1:9080/api-guard/index.do/users/get

will return a response status 200 as all constraints were met and a JSON representation of the user entity was stored in our Singleton Session Bean (yes, this is a built-in in-memory data storage).

A final note

The discussed techniques allow for a pretty powerful validation mechanism but there are still things we will expand on. Next thing on our roadmap will be a better mapping of exceptions, allowing for more specific response messages!

So stay tuned! :)

Next Post Previous Post

Load Comments

Please note that by clicking "Load Comments", your browser will establish a connection with servers from Disqus.
For more information, please visit our privacy statement.