-
Notifications
You must be signed in to change notification settings - Fork 1
PHP Cody Tutorial Exercise III
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).
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.
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.
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
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.
Join the community chat on Gitter.
No beta user yet? Request access in the community chat.