|
| 1 | +# Day 17: Introduction to CALM Patterns |
| 2 | + |
| 3 | +## Overview |
| 4 | +Learn how CALM Patterns enable you to define reusable architecture templates that can both generate new architectures and validate existing ones. |
| 5 | + |
| 6 | +## Objective and Rationale |
| 7 | +- **Objective:** Understand what Patterns are and create your first Pattern for a simple web application |
| 8 | +- **Rationale:** Patterns are CALM's superpower for reuse and governance. A single Pattern can generate architecture scaffolds instantly AND validate that existing architectures conform to a required structure. This dual capability makes Patterns essential for scaling architecture practices across teams. |
| 9 | + |
| 10 | +## Requirements |
| 11 | + |
| 12 | +### 1. Understand What Patterns Are |
| 13 | + |
| 14 | +**The Problem Patterns Solve:** |
| 15 | +- Teams keep building similar architectures from scratch |
| 16 | +- No easy way to enforce "all web apps must have these components" |
| 17 | +- Inconsistent structures across projects |
| 18 | + |
| 19 | +**The Solution:** |
| 20 | +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. |
| 21 | + |
| 22 | +### 2. Understand the Dual Superpower |
| 23 | + |
| 24 | +**One Pattern = Two Powers:** |
| 25 | + |
| 26 | +**Power 1 - Generation:** |
| 27 | +```bash |
| 28 | +calm generate -p my-pattern.json -o new-architecture.json |
| 29 | +``` |
| 30 | +Creates an architecture scaffold with all required nodes and relationships. |
| 31 | + |
| 32 | +**Power 2 - Validation:** |
| 33 | +```bash |
| 34 | +calm validate -p my-pattern.json -a existing-architecture.json |
| 35 | +``` |
| 36 | +Checks that an architecture has the required structure. |
| 37 | + |
| 38 | +### 3. Understand How Patterns Work |
| 39 | + |
| 40 | +Patterns use JSON Schema keywords to define requirements: |
| 41 | + |
| 42 | +- **`const`** - Requires an exact value (e.g., `"unique-id": { "const": "api-gateway" }`) |
| 43 | +- **`prefixItems`** - Defines the first exact items in an array (you can add others) |
| 44 | +- **`minItems`/`maxItems`** - Enforces array length |
| 45 | +- **`$ref`** - References other schemas |
| 46 | + |
| 47 | +**Example: Requiring a specific node** |
| 48 | +```json |
| 49 | +{ |
| 50 | + "nodes": { |
| 51 | + "type": "array", |
| 52 | + "prefixItems": [ |
| 53 | + { |
| 54 | + "properties": { |
| 55 | + "unique-id": { "const": "api-gateway" }, |
| 56 | + "node-type": { "const": "service" }, |
| 57 | + "name": { "const": "API Gateway" } |
| 58 | + } |
| 59 | + } |
| 60 | + ], |
| 61 | + "minItems": 1 |
| 62 | + } |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +This pattern requires an architecture to have a node with `unique-id: "api-gateway"`. |
| 67 | + |
| 68 | +### 4. Create Your First Pattern |
| 69 | + |
| 70 | +Create a simple pattern for a 3-tier web application. |
| 71 | + |
| 72 | +**Prompt:** |
| 73 | +```text |
| 74 | +Create a CALM pattern at patterns/web-app-pattern.json for a 3-tier web application. |
| 75 | +
|
| 76 | +The pattern should: |
| 77 | +1. Have a unique $id (https://example.com/patterns/web-app-pattern.json) |
| 78 | +2. Have title "Web Application Pattern" and a description |
| 79 | +3. Require exactly 3 nodes using prefixItems: |
| 80 | + - "web-frontend" (node-type: webclient, name: "Web Frontend") |
| 81 | + - "api-service" (node-type: service, name: "API Service") |
| 82 | + - "app-database" (node-type: database, name: "Application Database") |
| 83 | +4. Require exactly 2 relationships: |
| 84 | + - "frontend-to-api": connects web-frontend to api-service |
| 85 | + - "api-to-database": connects api-service to app-database |
| 86 | +
|
| 87 | +Use const for unique-id, node-type, and name properties. |
| 88 | +Set minItems and maxItems to enforce exact counts. |
| 89 | +``` |
| 90 | + |
| 91 | +### 5. Test Generation |
| 92 | + |
| 93 | +Generate an architecture from your pattern: |
| 94 | + |
| 95 | +```bash |
| 96 | +calm generate -p patterns/web-app-pattern.json -o architectures/generated-webapp.json |
| 97 | +``` |
| 98 | + |
| 99 | +Open `architectures/generated-webapp.json` and verify: |
| 100 | +- ✅ Has exactly 3 nodes with the correct IDs, types, and names |
| 101 | +- ✅ Has exactly 2 relationships connecting them |
| 102 | +- ✅ Structure matches what the pattern defined |
| 103 | + |
| 104 | +### 6. Visualize the Generated Architecture |
| 105 | + |
| 106 | +**Steps:** |
| 107 | +1. Open `architectures/generated-webapp.json` in VSCode |
| 108 | +2. Open preview (Ctrl+Shift+C / Cmd+Shift+C) |
| 109 | +3. See the 3-tier architecture visualized |
| 110 | +4. **Take a screenshot** |
| 111 | + |
| 112 | +### 7. Test Validation - Passing Case with Warnings |
| 113 | + |
| 114 | +Your generated architecture should validate against the pattern: |
| 115 | + |
| 116 | +```bash |
| 117 | +calm validate -p patterns/web-app-pattern.json -a architectures/generated-webapp.json |
| 118 | +``` |
| 119 | + |
| 120 | +Should pass! ✅ but show some warnings: |
| 121 | + |
| 122 | +```json |
| 123 | +{ |
| 124 | + "jsonSchemaValidationOutputs": [], |
| 125 | + "spectralSchemaValidationOutputs": [ |
| 126 | + { |
| 127 | + "code": "architecture-has-no-placeholder-properties-string", |
| 128 | + "severity": "warning", |
| 129 | + "message": "String placeholder detected in architecture.", |
| 130 | + "path": "/nodes/0/description", |
| 131 | + "schemaPath": "", |
| 132 | + "line_start": 0, |
| 133 | + "line_end": 0, |
| 134 | + "character_start": 98, |
| 135 | + "character_end": 117 |
| 136 | + }, |
| 137 | + { |
| 138 | + "code": "architecture-has-no-placeholder-properties-string", |
| 139 | + "severity": "warning", |
| 140 | + "message": "String placeholder detected in architecture.", |
| 141 | + "path": "/nodes/1/description", |
| 142 | + "schemaPath": "", |
| 143 | + "line_start": 0, |
| 144 | + "line_end": 0, |
| 145 | + "character_start": 203, |
| 146 | + "character_end": 222 |
| 147 | + }, |
| 148 | + { |
| 149 | + "code": "architecture-has-no-placeholder-properties-string", |
| 150 | + "severity": "warning", |
| 151 | + "message": "String placeholder detected in architecture.", |
| 152 | + "path": "/nodes/2/description", |
| 153 | + "schemaPath": "", |
| 154 | + "line_start": 0, |
| 155 | + "line_end": 0, |
| 156 | + "character_start": 319, |
| 157 | + "character_end": 338 |
| 158 | + } |
| 159 | + ], |
| 160 | + "hasErrors": false, |
| 161 | + "hasWarnings": true |
| 162 | +}% |
| 163 | +``` |
| 164 | + |
| 165 | +This is because `description` is a required field of nodes in the calm schema, but because we didn't provide a default description in our pattern, the generate command has put in placeholders which it expects us to fill in after generation. |
| 166 | + |
| 167 | +String placeholders can be identified by two square brackets: `"description": "[[ DESCRIPTION ]]"`. Numeric placeholders will have the value `-1`. |
| 168 | + |
| 169 | +As you will have seen when you visualized the architecture, placeholders don't make the architecture invalid, hence why only warnings are reported, but it allows you to build integration in your own tooling to spot where an architect or engineer is expected to replace those placeholders. |
| 170 | + |
| 171 | +### 8. Test Validation - Failing Case |
| 172 | + |
| 173 | +Create a broken architecture to see validation fail: |
| 174 | + |
| 175 | +1. Create architectures/broken-webapp.json by copying generated-webapp.json |
| 176 | +2. Change the unique-id of "api-service" to "backend-api" |
| 177 | + |
| 178 | +Validate: |
| 179 | +```bash |
| 180 | +calm validate -p patterns/web-app-pattern.json -a architectures/broken-webapp.json |
| 181 | +``` |
| 182 | + |
| 183 | +Should fail! ❌ The pattern catches that "api-service" is missing. |
| 184 | + |
| 185 | +```json |
| 186 | +{ |
| 187 | + "jsonSchemaValidationOutputs": [ |
| 188 | + { |
| 189 | + "code": "json-schema", |
| 190 | + "severity": "error", |
| 191 | + "message": "must be equal to constant", |
| 192 | + "path": "/nodes/1/unique-id", |
| 193 | + "schemaPath": "#/properties/nodes/prefixItems/1/properties/unique-id/const" |
| 194 | + } |
| 195 | + ], |
| 196 | + "spectralSchemaValidationOutputs": [ |
| 197 | + { |
| 198 | + "code": "connects-relationship-references-existing-nodes-in-architecture", |
| 199 | + "severity": "error", |
| 200 | + "message": "'api-service' does not refer to the unique-id of an existing node.", |
| 201 | + "path": "/relationships/0/relationship-type/connects/destination/node", |
| 202 | + "schemaPath": "", |
| 203 | + "line_start": 0, |
| 204 | + "line_end": 0, |
| 205 | + "character_start": 440, |
| 206 | + "character_end": 453 |
| 207 | + }, |
| 208 | + { |
| 209 | + "code": "connects-relationship-references-existing-nodes-in-architecture", |
| 210 | + "severity": "error", |
| 211 | + "message": "'api-service' does not refer to the unique-id of an existing node.", |
| 212 | + "path": "/relationships/1/relationship-type/connects/source/node", |
| 213 | + "schemaPath": "", |
| 214 | + "line_start": 0, |
| 215 | + "line_end": 0, |
| 216 | + "character_start": 539, |
| 217 | + "character_end": 552 |
| 218 | + }, |
| 219 | + { |
| 220 | + "code": "architecture-nodes-must-be-referenced", |
| 221 | + "severity": "warning", |
| 222 | + "message": "Node with ID 'api-service-1' is not referenced by any relationships.", |
| 223 | + "path": "/nodes/1/unique-id", |
| 224 | + "schemaPath": "", |
| 225 | + "line_start": 0, |
| 226 | + "line_end": 0, |
| 227 | + "character_start": 119, |
| 228 | + "character_end": 134 |
| 229 | + } |
| 230 | + ], |
| 231 | + "hasErrors": true, |
| 232 | + "hasWarnings": true |
| 233 | +}% |
| 234 | +``` |
| 235 | + |
| 236 | +As you can see, not only does the `validate` command identify that it is missing an expected node, but the relationship which was referencing it is also now in error and there is also a warning because we now have a node which is not referenced in any relationships. |
| 237 | + |
| 238 | +### 9. Enhance the Generated Architecture |
| 239 | + |
| 240 | +The generated architecture has the required structure. Now add details: |
| 241 | + |
| 242 | +**Prompt:** |
| 243 | +```text |
| 244 | +Update architectures/generated-webapp.json to add: |
| 245 | +1. Descriptions for each node explaining their purpose |
| 246 | +2. A description for each relationship |
| 247 | +3. Interfaces on api-service (host, port for HTTPS) |
| 248 | +4. Interfaces on app-database (host, port for PostgreSQL) |
| 249 | +
|
| 250 | +Keep the unique-ids, node-types, and names exactly as they are. |
| 251 | +``` |
| 252 | + |
| 253 | +### 10. Validate the Enhanced Architecture |
| 254 | + |
| 255 | +```bash |
| 256 | +calm validate -p patterns/web-app-pattern.json -a architectures/generated-webapp.json |
| 257 | +``` |
| 258 | + |
| 259 | +```json |
| 260 | +{ |
| 261 | + "jsonSchemaValidationOutputs": [], |
| 262 | + "spectralSchemaValidationOutputs": [], |
| 263 | + "hasErrors": false, |
| 264 | + "hasWarnings": false |
| 265 | +}% |
| 266 | +``` |
| 267 | + |
| 268 | +It now passes without warnings ✅ and adding extra properties doesn't break pattern compliance. This is how patterns act as the base foundation for new applications that share the same architecture, but aren't the same application. |
| 269 | + |
| 270 | +Think about how this can help you automate typically time consuming processes such as security review, which rely on certain things following strict convention, but other things being the responsibility of the engineers. |
| 271 | + |
| 272 | +### 11. Document the Pattern |
| 273 | + |
| 274 | +**Prompt:** |
| 275 | +```text |
| 276 | +Create patterns/README.md that documents: |
| 277 | +
|
| 278 | +1. What Patterns are and their dual superpower (generation + validation) |
| 279 | +2. How to use web-app-pattern.json: |
| 280 | + - Generation: calm generate -p patterns/web-app-pattern.json -o my-app.json |
| 281 | + - Validation: calm validate -p patterns/web-app-pattern.json -a my-app.json |
| 282 | +3. What the pattern enforces (3 specific nodes, 2 specific relationships) |
| 283 | +4. What's flexible (descriptions, interfaces, metadata) |
| 284 | +``` |
| 285 | + |
| 286 | +### 12. Commit Your Work |
| 287 | + |
| 288 | +```bash |
| 289 | +git add patterns/ architectures/generated-webapp.json README.md |
| 290 | +git commit -m "Day 17: Create first CALM pattern for web applications" |
| 291 | +git tag day-17 |
| 292 | +``` |
| 293 | + |
| 294 | +## Deliverables / Validation Criteria |
| 295 | + |
| 296 | +Your Day 17 submission should include a commit tagged `day-17` containing: |
| 297 | + |
| 298 | +✅ **Required Files:** |
| 299 | +- `patterns/web-app-pattern.json` - Pattern for 3-tier web app |
| 300 | +- `patterns/README.md` - Pattern documentation |
| 301 | +- `architectures/generated-webapp.json` - Generated and enhanced architecture |
| 302 | +- Updated `README.md` - Day 17 marked as complete |
| 303 | + |
| 304 | +✅ **Validation:** |
| 305 | +```bash |
| 306 | +# Pattern exists |
| 307 | +test -f patterns/web-app-pattern.json |
| 308 | + |
| 309 | +# Generation works |
| 310 | +calm generate -p patterns/web-app-pattern.json -o /tmp/test-webapp.json |
| 311 | + |
| 312 | +# Validation works |
| 313 | +calm validate -p patterns/web-app-pattern.json -a /tmp/generated-webapp.json |
| 314 | + |
| 315 | +# Check tag |
| 316 | +git tag | grep -q "day-17" |
| 317 | +``` |
| 318 | + |
| 319 | +## Resources |
| 320 | + |
| 321 | +- [JSON Schema prefixItems](https://json-schema.org/understanding-json-schema/reference/array#tupleValidation) |
| 322 | +- [JSON Schema const](https://json-schema.org/understanding-json-schema/reference/const) |
| 323 | + |
| 324 | +## Tips |
| 325 | + |
| 326 | +- Use `const` for values that MUST be exactly as specified |
| 327 | +- `prefixItems` defines the exact items required in order |
| 328 | +- `minItems`/`maxItems` together enforce exact array length |
| 329 | +- Patterns only constrain what you specify - extra properties are allowed |
| 330 | +- Test both generation AND validation to ensure the pattern works |
| 331 | + |
| 332 | +## Trouble Shooting |
| 333 | +- Remember AI can make mistakes, if for some reason your pattern won't generate a valid architecture ask CALM Chatmode to figure out why not, this prompt may be helpful. |
| 334 | + |
| 335 | +```text |
| 336 | +My pattern doesn't generate a valid architecture when I run the generate command, look at this valid pattern - https://raw.githubusercontent.com/finos/architecture-as-code/refs/heads/main/conferences/osff-ln-2025/workshop/conference-signup.pattern.json - identify the problem. |
| 337 | +``` |
| 338 | + |
| 339 | +## Key Concepts |
| 340 | + |
| 341 | +| Concept | Purpose | Example | |
| 342 | +|---------|---------|---------| |
| 343 | +| `const` | Require exact value | `"unique-id": { "const": "api-gateway" }` | |
| 344 | +| `prefixItems` | Define required array items | First node must be X, second must be Y | |
| 345 | +| `minItems` | Minimum array length | At least 3 nodes | |
| 346 | +| `maxItems` | Maximum array length | At most 3 nodes | |
| 347 | +| `$ref` | Reference other schemas | Point to node definition | |
| 348 | + |
| 349 | +## Next Steps |
| 350 | + |
| 351 | +Tomorrow (Day 18) you'll learn how to create organizational Standards - JSON Schema extensions that define custom properties like cost centers, owners, and compliance tags that your Patterns can enforce! |
0 commit comments