Introduction to CALM Patterns
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.
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:
| Keyword | Purpose | Example |
|---|---|---|
const | Requires an exact value | "unique-id": { "const": "api-gateway" } |
prefixItems | Defines exact ordered items in an array | First node must be X, second must be Y |
items | Constrains every position after those items | A cache or a queue may be added, or neither |
minItems / maxItems | Enforces array length | Exactly 3 nodes |
$ref | References other schemas | Point 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โ
- Open
architectures/generated-webapp.jsonin VSCode - Open preview (
Ctrl+Shift+C/Cmd+Shift+C) - 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:
- Copy
architectures/generated-webapp.jsontoarchitectures/broken-webapp.json - Change the
unique-idof"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:
- Copy
architectures/generated-webapp.jsontoarchitectures/cached-webapp.json - Add this node to
nodes. The pattern requires itsunique-id,node-typeandname, and the CALM node schema requires adescription:
{
"unique-id": "app-cache",
"node-type": "database",
"name": "Application Cache",
"description": "Caches API responses"
}
- 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:
- Copy
architectures/generated-webapp.jsontoarchitectures/unlisted-webapp.json - 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โ
| Keyword | Enforces | Allows |
|---|---|---|
const | Exact value | Nothing else |
prefixItems | Specific ordered items | Additional items after them |
items | Every later position matches one of its alternatives | Any number of them, or none |
minItems + maxItems (equal) | Exact array length | โ |
$ref | Schema from another file | Properties defined there |
Placeholdersโ
Generated architectures use placeholders as signals:
- String:
"[[ DESCRIPTION ]]"โ must be replaced before production - Numeric:
-1for 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.
Resourcesโ
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!