Skip to main content
Learn

Introduction to CALM Patterns

โฑ 4 min read๐ŸŸก Intermediate ยท 30-45 minutesTry in Learning Lab โ†—Edit on GitHub โ†—

Overviewโ€‹

Learn how CALM Patterns enable you to define reusable architecture templates that can both generate new architectures and validate existing ones.

Learning objectivesโ€‹

By the end of this tutorial, you will:

  • Understand the dual superpower of Patterns: generation and validation
  • Know how Patterns use JSON Schema keywords (const, prefixItems, items, minItems/maxItems, $ref)
  • Create a Pattern for a 3-tier web application
  • Generate a new architecture from your Pattern
  • Validate both a passing and a failing architecture against the Pattern
  • Understand placeholder warnings in generated architectures
  • Allow optional nodes with items

Prerequisitesโ€‹

Complete Generate Operations Documentation first.

Try in Learning Labโ€‹

You can work through this tutorial in your browser with the CALM Learning Lab. There is nothing to install: the lab runs the real CALM engine, and the calm commands this tutorial uses work as they do in the CLI. The lab has no AI assistant, so you write the CALM yourself instead of prompting Copilot.

Open this tutorial in the Learning Lab โ†—

Step-by-Step Guideโ€‹

1. Understand What Patterns Areโ€‹

The Problem Patterns Solve:

  • Teams keep building similar architectures from scratch
  • No easy way to enforce "all web apps must have these components"
  • Inconsistent structures across projects

The Solution: CALM Patterns pre-define the required structure of an architecture. They specify which nodes must exist, what relationships must connect them, and what properties they must have.

2. Understand the Dual Superpowerโ€‹

One Pattern = Two Powers:

Power 1 โ€” Generation:

calm generate -p my-pattern.json -o new-architecture.json

Power 2 โ€” Validation:

calm validate -p my-pattern.json -a existing-architecture.json

3. Understand How Patterns Workโ€‹

Patterns use JSON Schema keywords to define requirements:

KeywordPurposeExample
constRequires an exact value"unique-id": { "const": "api-gateway" }
prefixItemsDefines exact ordered items in an arrayFirst node must be X, second must be Y
itemsConstrains every position after those itemsA cache or a queue may be added, or neither
minItems / maxItemsEnforces array lengthExactly 3 nodes
$refReferences other schemasPoint to a node or Standards definition

Example: requiring a specific node

{
"nodes": {
"type": "array",
"prefixItems": [
{
"properties": {
"unique-id": { "const": "api-gateway" },
"node-type": { "const": "service" },
"name": { "const": "API Gateway" }
}
}
],
"minItems": 1
}
}

4. Create Your First Patternโ€‹

Prompt:

Create a CALM pattern at patterns/web-app-pattern.json for a 3-tier web application.

The pattern should:
1. Have a unique $id (https://example.com/patterns/web-app-pattern.json)
2. Have title "Web Application Pattern" and a description
3. Require exactly 3 nodes using prefixItems:
- "web-frontend" (node-type: webclient, name: "Web Frontend")
- "api-service" (node-type: service, name: "API Service")
- "app-database" (node-type: database, name: "Application Database")
4. Require exactly 2 relationships:
- "frontend-to-api": connects web-frontend to api-service
- "api-to-database": connects api-service to app-database

Use const for unique-id, node-type, and name properties.
Set minItems and maxItems to enforce exact counts.

5. Test Generationโ€‹

calm generate -p patterns/web-app-pattern.json -o architectures/generated-webapp.json

Open architectures/generated-webapp.json and verify:

  • โœ… Has exactly 3 nodes with the correct IDs, types, and names
  • โœ… Has exactly 2 relationships connecting them

6. Visualize the Generated Architectureโ€‹

  1. Open architectures/generated-webapp.json in VSCode
  2. Open preview (Ctrl+Shift+C / Cmd+Shift+C)
  3. See the 3-tier architecture visualized

7. Test Validation โ€” Passing Case with Warningsโ€‹

calm validate -p patterns/web-app-pattern.json -a architectures/generated-webapp.json

Should pass โœ… but show warnings about placeholder strings:

{
"spectralSchemaValidationOutputs": [
{
"code": "architecture-has-no-placeholder-properties-string",
"severity": "warning",
"message": "String placeholder detected in architecture.",
"path": "/nodes/0/description"
}
],
"hasErrors": false,
"hasWarnings": true
}

String placeholders look like "[[ DESCRIPTION ]]". Numeric placeholders use -1. These warn you that the generated architecture needs to be filled in โ€” they are not errors.

8. Test Validation โ€” Failing Caseโ€‹

Create a broken architecture to see validation fail:

  1. Copy architectures/generated-webapp.json to architectures/broken-webapp.json
  2. Change the unique-id of "api-service" to "backend-api"
calm validate -p patterns/web-app-pattern.json -a architectures/broken-webapp.json

Should fail โŒ โ€” the pattern catches that "api-service" is missing. You'll also see errors for relationships referencing the now-missing node ID.

9. Enhance the Generated Architectureโ€‹

Prompt:

Update architectures/generated-webapp.json to add:
1. Descriptions for each node explaining their purpose
2. A description for each relationship
3. Interfaces on api-service (host, port for HTTPS)
4. Interfaces on app-database (host, port for PostgreSQL)

Keep the unique-ids, node-types, and names exactly as they are.

10. Validate the Enhanced Architectureโ€‹

calm validate -p patterns/web-app-pattern.json -a architectures/generated-webapp.json
{
"jsonSchemaValidationOutputs": [],
"spectralSchemaValidationOutputs": [],
"hasErrors": false,
"hasWarnings": false
}

No warnings โœ… โ€” adding extra properties doesn't break pattern compliance.

11. Allow Optional Nodesโ€‹

A pattern can also offer nodes that an architecture may add. Use items for them.

Prompt:

Update patterns/web-app-pattern.json so that an architecture may also add a cache, a message queue, both or neither:
1. Add "items" to nodes, with an "anyOf" of two candidates:
- "app-cache" (node-type: database, name: "Application Cache")
- "message-queue" (node-type: service, name: "Message Queue")
2. Add "items" to relationships, with an "anyOf" of two candidates:
- "api-to-cache": connects api-service to app-cache
- "api-to-queue": connects api-service to message-queue
3. Raise maxItems to 5 for nodes and 4 for relationships. Keep minItems as it is.

Validate the architecture from step 10 again:

calm validate -p patterns/web-app-pattern.json -a architectures/generated-webapp.json

It still passes with no warnings โœ… โ€” the candidates are optional.

Now add a cache:

  1. Copy architectures/generated-webapp.json to architectures/cached-webapp.json
  2. Add this node to nodes. The pattern requires its unique-id, node-type and name, and the CALM node schema requires a description:
{
"unique-id": "app-cache",
"node-type": "database",
"name": "Application Cache",
"description": "Caches API responses"
}
  1. Add this relationship to relationships:
{
"unique-id": "api-to-cache",
"description": "API Service reads from and writes to the cache",
"relationship-type": {
"connects": {
"source": { "node": "api-service" },
"destination": { "node": "app-cache" }
}
}
}
calm validate -p patterns/web-app-pattern.json -a architectures/cached-webapp.json

Should pass with no warnings โœ…

Last, add a node that the pattern does not list:

  1. Copy architectures/generated-webapp.json to architectures/unlisted-webapp.json
  2. Add a node with unique-id "audit-log"
calm validate -p patterns/web-app-pattern.json -a architectures/unlisted-webapp.json

Should fail โŒ โ€” among the errors, must be equal to constant shows that every added node must match a candidate.

calm generate does not add the candidates. To offer them as a choice, see The options relationship.

Before moving on, use git to capture the state of your work. A descriptive commit message will help future-you understand what changed and why.

Key conceptsโ€‹

Pattern Anatomyโ€‹

KeywordEnforcesAllows
constExact valueNothing else
prefixItemsSpecific ordered itemsAdditional items after them
itemsEvery later position matches one of its alternativesAny number of them, or none
minItems + maxItems (equal)Exact array lengthโ€”
$refSchema from another fileProperties defined there

Placeholdersโ€‹

Generated architectures use placeholders as signals:

  • String: "[[ DESCRIPTION ]]" โ€” must be replaced before production
  • Numeric: -1 for numeric fields like port numbers

Pattern vs Architectureโ€‹

A Pattern defines the shape any matching architecture must have. An architecture that satisfies the Pattern is free to add extra elements, unless maxItems or items limits them โ€” Patterns only constrain what they explicitly specify. maxItems limits how many elements an array holds. With items, each extra element must match a candidate the Pattern lists.

Troubleshootingโ€‹

If your pattern won't generate a valid architecture, use this prompt with the CALM Agent:

My pattern doesn't generate a valid architecture when I run the generate command.
Look at this valid pattern for reference:
https://raw.githubusercontent.com/finos/architecture-as-code/refs/heads/main/conferences/osff-ln-2025/workshop/conference-signup.pattern.json
Identify the problem in my pattern.

Next stepsโ€‹

In the next tutorial, you'll learn how to create organizational Standards โ€” JSON Schema extensions that define required properties like cost centers, owner fields, and compliance tags!