Pragmatism in the real world

Using Fractal as your OpenWhisk API's view layer

When writing an API, it’s common to produce an output that conforms to a known media type such as JSON API or HAL, etc. I’m a strong believer that even though I’m writing an API, my application has a view layer. It’s not the same as building an HTML page, but you still need to separate out the code that creates the structured output from your model layer. For a couple of APIs that I’ve written recently, I’ve used Fractal for this.

Using Fractal

To use Fractal, you need some data. This can be a single entity or a list of entities. You then put your entity into Fractal Item or your list into a Collection and then transform it.

use League\Fractal\Manager;
use League\Fractal\Resource\Collection;

$todos = $todoMapper->fetchAll();

$resource = new Collection($todos, new TodoTransformer);
$manager = new Manager();
$body = $manager->createData($resource)->toJson();

The magic of Fractal is in the Transformer which is where you tell Fractal how to interpret the model data.

This is my TodoTransformer:

<?php declare(strict_types=1);

namespace Todo;

use League\Fractal\TransformerAbstract;

class TodoTransformer extends TransformerAbstract
{
    private $baseUrl;

    public function __construct(string $baseUrl)
    {
        $this->baseUrl = $baseUrl;
    }

    public function transform(Todo $todo) : array
    {
        return [
            'id' => $todo->getId(),
            'title' => $todo->getTitle(),
            'completed' => $todo->isCompleted(),
            'order' => $todo->getOrder(),
            'links'   => [
                [
                    'rel' => 'self',
                    'uri' => $this->baseUrl . '/todos/' . $todo->getId(),
                ]
            ]
        ];
    }
}

The transform method receives our entity and returns an array describing how we would like it to be presented to the world. This is the key decoupling between our model layer and our view.

Plugging Fractal into OpenWhisk

To use this in OpenWhisk, I choose to use my DI container that I have discussed previously. Within my AppContainer, I need two factories: one for the Manager and one for the Transformer.

Let’s start with the Transformer’s factory:

/**
 * Factory to create a TodoTransformer instance
 */
$configuration[TodoTransformer::class] = function (Container $c) : TodoTransformer {
    return new TodoTransformer($c['settings']['base_url']);
};

The only complexity here is that we need the base_url. As this is an OpenWhisk API using API Gateway, I referenced Lorna Mitchell’s article Relative Links with IBM Cloud API Gateway, which applies to Apache OpenWhisk too.

From this, we know that the full URL that was used for this action is in $args['__ow_headers']['x-forwarded-url'], however we need to do a little bit of work to ensure that we remove any extra path segments such as the todo’s id. We do this by removing the __ow_path value from the end of the x-forwarded-for header:

class AppContainer extends Container
{
    public function __construct(array $args)
    {
        // determine base URL by removing __ow_path from x-forwarded-for header
        $url = $args['__ow_headers']['x-forwarded-url'];
        $path = $args['__ow_path'];
        $length = strlen($url) - strlen($path);
        $configuration['settings']['base_url'] = substr($url, 0, $length);

        // ...

The Manager factory is very few lines:

        /**
         * Factory to create a Manager instance
         */
        $configuration[Manager::class] = function (Container $c) : Manager {
            $baseUrl = $c['settings']['base_url'];

            $manager = new Manager();
            $manager->setSerializer(new ArraySerializer($baseUrl));
            return $manager;
        };

Serializers are the way that Fractal structures your data. It comes with a number of different ones including JsonApiSerializer which supports JSON-API. In this case, I want the simplest possible formatting, which is ArraySerializer.

Using Fractal in an action

To put this into our action, we grab the objects from the container and use them:

// various use statements as required go here!

function main(array $args) : array
{
    $container = new AppContainer($args);
    $todoMapper = $container[TodoMapper::class];
	$todos = $todoMapper->fetchAll();

    $transformer = $container[TodoTransformer::class];
    $resource = new Collection($todos, $transformer);

    $manager = $container[Manager::class];;

    return [
        'body' => $fractal->createData($resource)->toArray()['data'],
    ];
}

Our action now renders structured data for our API clients.

Fin

Separating the rendering of your API data from your model code makes for a much more maintainable application. This is also valid in serverless applications and Fractal provides for a clean way to do this.

4 thoughts on “Using Fractal as your OpenWhisk API's view layer

  1. I dont see the require('vendor/autoload.php') in the action file. Why is this? How is the PSR-4 autoloading happening? I am having issues with a similar scenario…

Comments are closed.