-
Notifications
You must be signed in to change notification settings - Fork 1
Node.js 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 exercises npm run exercise3
Ok, this time we're asked to generate a command handling function named addBuilding
in an aggregate directory Aggregate/Building
. The function should handle AddBuilding
command from previous exercises.
In Exercise I you've learned how to create a Command Hook and register it in codyconfig.ts
. Do the same for an onAggregateHook
and let the hook generate a directory exercises/src/Aggregate/${aggregateName}
.
Hint: You can use the util function mkdirIfNotExistsSync
to generate the directory. Make sure to check if the util function returns a Cody-Error-Response!
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 exercises npm run exercise3
again.
Test case is still failing but the error has changed. src/Aggregate/Building/addBuilding.ts
file is 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:
interface Node {
getId: () => NodeId;
getName: () => NodeName;
getType: () => NodeType;
getLink: () => NodeLink;
getTags: () => List<NodeTag>;
isLayer: () => boolean;
isDefaultLayer: () => boolean;
getParent: () => Node | null;
getChildren: () => List<Node>;
getGeometry: () => GraphPoint;
getSources: () => List<Node>;
getTargets: () => List<Node>;
getMetadata: () => string | null;
}
We already worked with getName()
and getMetadata()
. From a Node
you can follow connections using getSources()
and getTargets()
. 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 getParent()
and getChildren()
which are useful when grouping cards in a frame
Enough theory! Let's see it in action. Update your onAggregateHook
with this version:
import {CodyHook} from "../board/code";
import {Context} from "./Context";
import {Node, NodeType} from "../board/graph";
import {CodyResponse, isCodyError} from "../general/response";
import {nodeNameToCamelCase, nodeNameToPascalCase} from "../utils/string";
import {mkdirIfNotExistsSync, writeFileSync} from "../utils/filesystem";
import {getSingleSource} from "../utils/node-traversing";
export const onAggregateHook: CodyHook<Context> = async (aggregate: Node, ctx: Context): Promise<CodyResponse> => {
const aggregateName = nodeNameToPascalCase(aggregate);
const aggregateDir = ctx.srcFolder + `/Aggregate/${aggregateName}`;
let successDetails = 'Checklist\n\n';
const dirErrResponse = mkdirIfNotExistsSync(aggregateDir);
if(isCodyError(dirErrResponse)) {
return dirErrResponse;
}
successDetails = successDetails + `✔️ Aggregate directory ${aggregateDir} exists\n`;
const command = getSingleSource(aggregate, NodeType.command);
if(isCodyError(command)) {
return command;
}
const commandName = nodeNameToPascalCase(command);
const commandFunction = nodeNameToCamelCase(command);
const functionFile = `${commandFunction}.ts`;
const content = `import {${commandName}} from "../../Command/${commandName}";
export function ${commandFunction}(command: ${commandName}): void {}
`;
const writeFileErr = writeFileSync(aggregateDir + `/${functionFile}`, content);
if(isCodyError(writeFileErr)) {
return writeFileErr;
}
successDetails = successDetails + `✔️ Command handler ${aggregateDir}/${functionFile} created\n`;
return {
cody: `${aggregateName} Aggregate can now handle ${commandName} commands`,
details: ['%c'+successDetails, 'color: #73dd8e;font-weight: bold'],
}
}
The implementation should be self explanatory. The util function getSingleSource
is used to ensure that an aggregate card should only have one command as a source. If more than one is found a Cody error is returned. 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 exercises npm run 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.