Skip to content

PHP Cody Tutorial Exercise III

Alexander Miertsch edited this page Feb 13, 2021 · 5 revisions

Exercise II introduced Card Metadata and how it can be used to provide additional information to a Cody Hook.

One last basic concept is missing to complete the picture. On an InspectIO event map you model message flows, behavior, business processes. Different objects interact with each other by exchanging messages like commands and events. A Cody Hook can make use of this information to assist developers by generating all required glue code if not a fully working implementation (depends on complexity).

Task

We start again by looking at the failing test case for exercise III: docker-compose run --rm composer exercise3

Ok, this time we're asked to generate an aggregate class named Building with a method handling the AddBuilding command from previous exercises.

Aggregate Hook

In Exercise I you've learned how to create a Command Hook and register it in codyconfig.php. Do the same for an AggregateHook and let the hook generate an empty aggregate class. The class should be written to the directory exercises/src/Aggregate.

If you think the hook is ready, switch to InspectIO, add an aggregate card with label Building on the event map and trigger Cody.

Did it work? You can verify the result by executing docker-compose run --rm composer exercise3 again.

Test case is still failing but the error message has changed. Building::addBuilding(AddBuilding $command) method is still missing.

Event Map Connections

Go back to the event map and draw an arrow from AddBuilding command to Building aggregate.

The connection we've just drawn can be read in a hook. Here is the Node interface passed to a hook:

<?php

/**
 * @see       https://github.com/event-engine/php-inspectio-graph-cody for the canonical source repository
 * @copyright https://github.com/event-engine/php-inspectio-graph-cody/blob/master/COPYRIGHT.md
 * @license   https://github.com/event-engine/php-inspectio-graph-cody/blob/master/LICENSE.md MIT License
 */

declare(strict_types=1);

namespace EventEngine\InspectioGraphCody;

interface Node
{
    public function id(): string;

    public function name(): string;

    public function type(): string;

    public function tags(): iterable;

    public function isLayer(): bool;

    public function isDefaultLayer(): bool;

    public function parent(): ?Node;

    /**
     * @return Node[]
     */
    public function children(): iterable;

    /**
     * @return Node[]
     */
    public function sources(): iterable;

    /**
     * @return Node[]
     */
    public function targets(): iterable;

    public function metadata(): ?string;
}

We already worked with name() and metadata(). From a Node you can follow connections using sources() and targets(). In our case the AddBuilding command node has the Building aggregate referenced as a target and from Building point of view AddBuilding command is a source.

One important thing to note here is that a hook gets only access to directly connected nodes, even if those nodes have further connections! This avoids circular reference problems and keeps payload exchanged between InspectIO and Cody small.

Node interface also provides information about parent() and children() which are useful when grouping cards in a frame

Enough theory! Let's see it in action. Update your AggregateHook with this version:

<?php
declare(strict_types=1);

namespace EventEngine\InspectioCody\Hook;

use EventEngine\InspectioCody\Board\BaseHook;
use EventEngine\InspectioCody\Http\Message\CodyResponse;
use EventEngine\InspectioCody\Http\Message\Response;
use EventEngine\InspectioGraph\VertexType;
use EventEngine\InspectioGraphCody\Node;
use LogicException;
use stdClass;
use function lcfirst;

final class AggregateHook extends BaseHook
{
    /**
     * @param  Node     $aggregate Information about aggregate sticky received from InspectIO event map
     * @param  stdClass $context Context object populated in codyconfig.php
     * @return CodyResponse      Response sent back to InspectIO, shown in Cody Console
     */
    public function __invoke(Node $aggregate, stdClass $context): CodyResponse
    {
        $aggregateName = $aggregate->name();
        $aggregateFile = $aggregateName . '.php';
        $aggregatePath = $context->path . '/Aggregate/' . $aggregateFile;


        $commandHandlingMethod = "";
        $includes = "";
        $command = null;

        foreach ($aggregate->sources() as $source) {
            if($source->type() === VertexType::TYPE_COMMAND) {
                if($command) {
                    throw new LogicException(
                        "Aggregate $aggregateName is connected to more than one command"
                    );
                }

                $command = $source;
            }
        }

        if($command) {
            $includes.= "use Cody\Tutorial\Command\\{$command->name()};\n";

            $methodName = lcfirst($command->name());

            $commandHandlingMethod =
                "public static function $methodName({$command->name()} \$command): void {}";
        }

        $code = <<<CODE
        <?php
        declare(strict_types=1);
        
        namespace Cody\Tutorial\Aggregate;
        
        $includes
        
        class $aggregateName
        {
            $commandHandlingMethod
        }
        CODE;

        $this->writeFile($code, $aggregatePath);

        return Response::fromCody(
            "Aggregate \"{$aggregateName}\" generated",
            ["Aggregate written to {$aggregatePath}"]
        );
    }
}

The implementation should be self explanatory. We included a logical validation that an aggregate card should only have one command as a source. If more than one is found an exception is thrown. Exceptions are caught by Cody and sent back to InspectIO as an error response. This way you can harden your hooks and validate event maps according to your own rules.

Trigger Cody with the Building aggregate card in InspectIO and run exercise III once again: docker-compose run --rm composer exercise3

Recap Exercise III

This exercise introduced the last basic building block: Connections between cards. Depending on arrow direction on InspectIO a connected card appears either in the sources or targets collection of the Node passed to a hook. It's important to keep in mind that a hook only has access to directly connected cards.

Next - Exercise IV