Skip to content

Node.js Cody Tutorial Exercise II

Alexander Miertsch edited this page Feb 14, 2021 · 3 revisions

Exercise I introduced the core functionality of Cody. You can model business logic on an event map using Event Storming semantics and generate source code from it. So far we only generated an empty interface. But Cody offers much more than that! In Exercise II you'll learn how to use Card Metadata to define a schema and derive properties from it.

Task

Execute docker-compose run --rm exercises npm run exercise2 to see what we have to do next.

We're asked to add a buildingId and a name property (both of type string) to the AddBuilding command. Now it would be easy to just open the file and add those two properties. But we should expand our code generation logic instead. This has some significant advantages:

  • an InspectIO event map acts as documentation
  • one can generate contracts like an OpenAPI schema from Card Metadata
  • it is easier to discuss and design new features or refactorings when such details are included on the event map

Expand Command Hook Logic

Ok let's use a simple code generator implementation first to understand the basics. In a later tutorial part we'll look at some useful libraries that provide abstractions for advanced use cases.

Change the command hook in cody-tutorial/cody-bot/src/hooks/onCommandHook.ts like this:

import {CodyHook} from "../board/code";
import {Node} from "../board/graph";
import {CodyResponse, CodyResponseType, isCodyError} from "../general/response";
import {nodeNameToPascalCase} from "../utils/string";
import {Context} from "./Context";
import {writeFileSync} from "../utils/filesystem";
import {parseJsonMetadata} from "../utils/metadata";

/**
 * onCommandHook
 *
 * @param {Node}    command  Information about command card received from InspectIO
 * @param {Context} ctx      Context object populated in codyconfig.ts
 * @returns Promise<CodyResponse>
 */
export const onCommandHook: CodyHook<Context> = async (command: Node, ctx: Context): Promise<CodyResponse> => {
  // Cody ships with some util functions for common tasks
  const cmdName = nodeNameToPascalCase(command);
  const cmdFilename = cmdName+'.ts';
  // ctx.srcFolder is set in codyconfig.ts
  const cmdFile = ctx.srcFolder + `/Command/${cmdFilename}`;
  let successDetails = 'Checklist\n\n';

  const commandMetadata = parseJsonMetadata<{[prop: string]: string}>(command);

  if(isCodyError(commandMetadata)) {
    return commandMetadata;
  }

  if(typeof commandMetadata !== 'object') {
    // You can return your own error responses, too
    return {
      cody: `I expected metadata of command "${command.getName()}" to be an object, but it is of type: `
          + typeof commandMetadata,
      type: CodyResponseType.Error
    };
  }

  let properties = "";

  for(const prop in commandMetadata) {
    if(commandMetadata.hasOwnProperty(prop)) {
      const type = commandMetadata[prop];
      // Append property to properties string which is inserted in interface below
      properties = properties + `  ${prop}: ${type};\n`;
    }
  }

  const content = `
export interface ${cmdName} {
${properties}
}
`;

  // Util functions return Cody-Error-Responses in case something went wrong
  const writeFileErr = writeFileSync(cmdFile, content);

  if(isCodyError(writeFileErr)) {
    return writeFileErr;
  }

  successDetails = successDetails + `✔️ Command file ${cmdFile} written\n`;

  // Cody responses can be formatted similar to browser console formatting
  // @see https://developers.google.com/web/tools/chrome-devtools/console/console-write#styling_console_output_with_css
  return {
    cody: `Wasn't easy, but command ${cmdName} should work now!`,
    details: ['%c'+successDetails, 'color: #73dd8e;font-weight: bold'],
  }
}

Set Command Metadata in InspectIO

Now we can switch to InspectIO and set the following metadata in JSON format to the AddBuilding command:

{
  "buildingId": "string",
  "name": "string"
}

Metadata can be set by opening the Metadata Sidebar (choose Metadata from top menu) and selecting the appropriate card or sticky note. Metadata changes are saved automatically.

Once metadata is set we can trigger Cody again ...

... and validate the result by executing docker-compose run --rm exercises npm run exercise2 again:

Recap Exercise II

Cards on an InspectIO event map can have additional information set as metadata. By default metadata is stored in JSON format. Metadata itself is schemaless, meaning users are free to define any structure. The structure can be defined and even be type checked using Metadata Templates. In a Cody hook you have access to raw metadata and use it for advanced code generation.

Next - Exercise III