CALM: Architecture-as-Code

ISD Architecture

Finley Bacon

2025-10-15

Architecture as Code?

What?

  • Modelling architecture in code (e.g. JSON, YAML, DSLs)
  • Generate diagrams from models instead of manually drawing them
  • Store architecture close to source code

Why?

Alleged benefits:

  • Machine-readable architecture models enable automated validation & compliance checks
  • Architecture can be version controlled alongside source code
  • Developers and architects work from the same source of truth
  • A single model can generate multiple outputs (diagrams, documentation, reports)
  • Easier to keep architecture up to date as the system changes

Before we test those claims…

Cast your minds back

C4 Model

Recap

Data Archive

ARC Services Portal

c4model.com:

The C4 model was created as a way to help software development teams describe and communicate software architecture, both during up-front design sessions and when retrospectively documenting an existing codebase.

Rationale

Rationale

  • Inconsistent notation (colour coding, shapes, line styles, etc.)
  • Ambiguous naming
  • Unlabelled relationships
  • Generic terminology
  • Missing technology choices
  • Mixed abstractions
  • Not good for communicating architecture

Workflow

  1. Model using Structurizr DSL
workspace "ARC Services Portal" "Models the architecture of a research services web portal" {

    !identifiers hierarchical

    model {

        properties {
            "structurizr.groupSeparator" "/"
        }

        user = person "Researcher" "Uses the platform to manage their research profile and data."
        admin = person "Admin" "Portal user with elevated permissions to edit data or review submissions."

        entra = softwareSystem "Microsoft Entra" "Identity provider (SSO)" "Existing System"
        tre = softwareSystem "TRE" "Secure processing environment for sensitive research data" "Existing System"
        ghcr = softwareSystem "GitHub Container Registry" "Stores container images" "Existing System"
        uclSystem = softwareSystem "..." "Other UCL consumer of portal data" "Existing System"

        portal = softwareSystem "ARC Services Portal" "Enables management of Studies, Projects, user training and information governance" {

            group "K8S cluster" {

                api = container "API" "API backend with /web/api and /tre/api (and other) endpoints" "Go" {

                    group "Shared Services" {
                        projectService = component "Project Service" "Handles project workflows" "Go"
                        userService = component "User Service" "Handles user workflows" "Go"
                        otherService = component "..." "Other backend services" "Go"
                    }

                    group "Web API" {
                        webhttpHandlers = component "HTTP Handlers for web frontend" "Handles REST endpoints" "Go"
                        webhttpHandlers -> userService "Calls"
                        webhttpHandlers -> projectService "Calls"
                        webhttpHandlers -> otherService "Calls"
                    }

                    group "TRE API" {
                        trehttpHandlers = component "HTTP Handlers for UCL TREs" "Handles REST endpoints" "Go"
                        trehttpHandlers -> userService "Calls"
                        trehttpHandlers -> projectService "Calls"
                        trehttpHandlers -> otherService "Calls"
                    }

                    group "Other API" {
                        otherhttpHandlers = component "HTTP Handlers for other UCL systems" "Handles REST endpoints" "Go"
                        otherhttpHandlers -> otherService "Calls"
                    }
                }

                webFrontend = container "Web Frontend" "User-facing web app" "React, TypeScript" {
                    pages = component "Pages" "UCL Design System-styled pages for profile, studies, projects" "React"
                    apiClient = component "API Client" "Generated from OpenAPI spec" "TypeScript"

                    pages -> apiClient "Calls"
                }

                oauth2Proxy = container "OAuth2 Proxy" "Proxy providing OAuth2 authentication" "quay.io/oauth2-proxy/oauth2-proxy"

                nginx = container "Reverse Proxy" "Routes requests to frontend and backend" "nginx"

                postgres = container "Database" "Stores user, project, portal etc. data" "PostgreSQL"

                api.projectService -> postgres "Reads/writes data"
                api.userService -> postgres "Reads/writes data"
                api.otherService -> postgres "Read/writes data"
                nginx -> api.trehttpHandlers "Proxies /tre/api requests"
                nginx -> api.otherhttpHandlers "Proxies other api requests"
                nginx -> webFrontend.pages "Serves"
                webFrontend.apiClient -> oauth2Proxy "Forwards /web/api requests for authentication"
                oauth2Proxy -> api.webhttpHandlers "Forwards authenticated /web/api requests to backend"

            }

            deploy = container "CI/CD Pipeline" "Builds and deploys infrastructure and app" "GitHub Actions + Terraform" {

                group "Portal App Repository" {
                    gha_portal = component "GitHub Actions (Portal Repo)" "Builds & pushes container images" "GitHub Actions"
                }

                group "Infrastructure Repository" {
                    gha_infra = component "GitHub Actions (Infra Repo)" "Applies Terraform in AWS" "GitHub Actions"
                    tf = component "Terraform Code" "Defines and provisions cloud infra" "Terraform"
                    gha_infra -> tf "Runs"
                }
            }

            portal.deploy.tf -> ghcr "Pulls images for deployment"
            portal.deploy.gha_portal -> ghcr "Pushes container images"
        }

        user -> portal.webFrontend.pages "Accesses via browser"
        admin -> portal.webFrontend.pages "Administers via browser"
        portal.oauth2Proxy -> entra "Authenticates requests against" "SSO"
        tre -> portal.nginx "Accesses /tre/api for data retrieval" "REST/JSON"
        uclSystem -> portal.nginx "Accesses api for data retrieval" "REST/JSON"
    }

    views {

        systemContext portal "C1-Portal_Context" {
            include *
            description "Context diagram showing how the Research Platform fits within the broader environment."
        }

        container portal "C2-Portal_Containers" {
            include *
            description "Container view showing major services running in Kubernetes, including API, frontend, proxy, and DB."
        }

        component portal.api "C3-Web_API_Internals" {
            include *
            description "Component view showing key service components inside the API backend."
        }

        component portal.webFrontend "C3-Web_Frontend_Internals" {
            include *
            description "Component view showing the UI modules and OpenAPI client."
        }

        component portal.deploy "C3-Deployment_Workflow" {
            include *
            description "Component view showing how CI/CD is managed using GitHub Actions and Terraform."
        }

        styles {
            element "Person" {
                shape person
                background #08427b
                color #ffffff
            }

            element "Software System" {
                background #1168bd
                color #ffffff
            }

            element "Container" {
                background #438dd5
                color #ffffff
            }

            element "Component" {
                background #85bbf0
                color #000000
            }

            element "Existing System" {
                background #999999
            }

            element "Group:K8S cluster" {
                color #d86613
                icon https://static.structurizr.com/themes/amazon-web-services-2020.04.30/amazon-elastic-kubernetes-service.png
            }
        }
    }
}

Workflow

  1. Create views
workspace "ARC Services Portal" "Models the architecture of a research services web portal" {

    !identifiers hierarchical

    model {

        properties {
            "structurizr.groupSeparator" "/"
        }

        user = person "Researcher" "Uses the platform to manage their research profile and data."
        admin = person "Admin" "Portal user with elevated permissions to edit data or review submissions."

        entra = softwareSystem "Microsoft Entra" "Identity provider (SSO)" "Existing System"
        tre = softwareSystem "TRE" "Secure processing environment for sensitive research data" "Existing System"
        ghcr = softwareSystem "GitHub Container Registry" "Stores container images" "Existing System"
        uclSystem = softwareSystem "..." "Other UCL consumer of portal data" "Existing System"

        portal = softwareSystem "ARC Services Portal" "Enables management of Studies, Projects, user training and information governance" {

            group "K8S cluster" {

                api = container "API" "API backend with /web/api and /tre/api (and other) endpoints" "Go" {

                    group "Shared Services" {
                        projectService = component "Project Service" "Handles project workflows" "Go"
                        userService = component "User Service" "Handles user workflows" "Go"
                        otherService = component "..." "Other backend services" "Go"
                    }

                    group "Web API" {
                        webhttpHandlers = component "HTTP Handlers for web frontend" "Handles REST endpoints" "Go"
                        webhttpHandlers -> userService "Calls"
                        webhttpHandlers -> projectService "Calls"
                        webhttpHandlers -> otherService "Calls"
                    }

                    group "TRE API" {
                        trehttpHandlers = component "HTTP Handlers for UCL TREs" "Handles REST endpoints" "Go"
                        trehttpHandlers -> userService "Calls"
                        trehttpHandlers -> projectService "Calls"
                        trehttpHandlers -> otherService "Calls"
                    }

                    group "Other API" {
                        otherhttpHandlers = component "HTTP Handlers for other UCL systems" "Handles REST endpoints" "Go"
                        otherhttpHandlers -> otherService "Calls"
                    }
                }

                webFrontend = container "Web Frontend" "User-facing web app" "React, TypeScript" {
                    pages = component "Pages" "UCL Design System-styled pages for profile, studies, projects" "React"
                    apiClient = component "API Client" "Generated from OpenAPI spec" "TypeScript"

                    pages -> apiClient "Calls"
                }

                oauth2Proxy = container "OAuth2 Proxy" "Proxy providing OAuth2 authentication" "quay.io/oauth2-proxy/oauth2-proxy"

                nginx = container "Reverse Proxy" "Routes requests to frontend and backend" "nginx"

                postgres = container "Database" "Stores user, project, portal etc. data" "PostgreSQL"

                api.projectService -> postgres "Reads/writes data"
                api.userService -> postgres "Reads/writes data"
                api.otherService -> postgres "Read/writes data"
                nginx -> api.trehttpHandlers "Proxies /tre/api requests"
                nginx -> api.otherhttpHandlers "Proxies other api requests"
                nginx -> webFrontend.pages "Serves"
                webFrontend.apiClient -> oauth2Proxy "Forwards /web/api requests for authentication"
                oauth2Proxy -> api.webhttpHandlers "Forwards authenticated /web/api requests to backend"

            }

            deploy = container "CI/CD Pipeline" "Builds and deploys infrastructure and app" "GitHub Actions + Terraform" {

                group "Portal App Repository" {
                    gha_portal = component "GitHub Actions (Portal Repo)" "Builds & pushes container images" "GitHub Actions"
                }

                group "Infrastructure Repository" {
                    gha_infra = component "GitHub Actions (Infra Repo)" "Applies Terraform in AWS" "GitHub Actions"
                    tf = component "Terraform Code" "Defines and provisions cloud infra" "Terraform"
                    gha_infra -> tf "Runs"
                }
            }

            portal.deploy.tf -> ghcr "Pulls images for deployment"
            portal.deploy.gha_portal -> ghcr "Pushes container images"
        }

        user -> portal.webFrontend.pages "Accesses via browser"
        admin -> portal.webFrontend.pages "Administers via browser"
        portal.oauth2Proxy -> entra "Authenticates requests against" "SSO"
        tre -> portal.nginx "Accesses /tre/api for data retrieval" "REST/JSON"
        uclSystem -> portal.nginx "Accesses api for data retrieval" "REST/JSON"
    }

    views {

        systemContext portal "C1-Portal_Context" {
            include *
            description "Context diagram showing how the Research Platform fits within the broader environment."
        }

        container portal "C2-Portal_Containers" {
            include *
            description "Container view showing major services running in Kubernetes, including API, frontend, proxy, and DB."
        }

        component portal.api "C3-Web_API_Internals" {
            include *
            description "Component view showing key service components inside the API backend."
        }

        component portal.webFrontend "C3-Web_Frontend_Internals" {
            include *
            description "Component view showing the UI modules and OpenAPI client."
        }

        component portal.deploy "C3-Deployment_Workflow" {
            include *
            description "Component view showing how CI/CD is managed using GitHub Actions and Terraform."
        }

        styles {
            element "Person" {
                shape person
                background #08427b
                color #ffffff
            }

            element "Software System" {
                background #1168bd
                color #ffffff
            }

            element "Container" {
                background #438dd5
                color #ffffff
            }

            element "Component" {
                background #85bbf0
                color #000000
            }

            element "Existing System" {
                background #999999
            }

            element "Group:K8S cluster" {
                color #d86613
                icon https://static.structurizr.com/themes/amazon-web-services-2020.04.30/amazon-elastic-kubernetes-service.png
            }
        }
    }
}

Workflow

  1. Apply styles
workspace "ARC Services Portal" "Models the architecture of a research services web portal" {

    !identifiers hierarchical

    model {

        properties {
            "structurizr.groupSeparator" "/"
        }

        user = person "Researcher" "Uses the platform to manage their research profile and data."
        admin = person "Admin" "Portal user with elevated permissions to edit data or review submissions."

        entra = softwareSystem "Microsoft Entra" "Identity provider (SSO)" "Existing System"
        tre = softwareSystem "TRE" "Secure processing environment for sensitive research data" "Existing System"
        ghcr = softwareSystem "GitHub Container Registry" "Stores container images" "Existing System"
        uclSystem = softwareSystem "..." "Other UCL consumer of portal data" "Existing System"

        portal = softwareSystem "ARC Services Portal" "Enables management of Studies, Projects, user training and information governance" {

            group "K8S cluster" {

                api = container "API" "API backend with /web/api and /tre/api (and other) endpoints" "Go" {

                    group "Shared Services" {
                        projectService = component "Project Service" "Handles project workflows" "Go"
                        userService = component "User Service" "Handles user workflows" "Go"
                        otherService = component "..." "Other backend services" "Go"
                    }

                    group "Web API" {
                        webhttpHandlers = component "HTTP Handlers for web frontend" "Handles REST endpoints" "Go"
                        webhttpHandlers -> userService "Calls"
                        webhttpHandlers -> projectService "Calls"
                        webhttpHandlers -> otherService "Calls"
                    }

                    group "TRE API" {
                        trehttpHandlers = component "HTTP Handlers for UCL TREs" "Handles REST endpoints" "Go"
                        trehttpHandlers -> userService "Calls"
                        trehttpHandlers -> projectService "Calls"
                        trehttpHandlers -> otherService "Calls"
                    }

                    group "Other API" {
                        otherhttpHandlers = component "HTTP Handlers for other UCL systems" "Handles REST endpoints" "Go"
                        otherhttpHandlers -> otherService "Calls"
                    }
                }

                webFrontend = container "Web Frontend" "User-facing web app" "React, TypeScript" {
                    pages = component "Pages" "UCL Design System-styled pages for profile, studies, projects" "React"
                    apiClient = component "API Client" "Generated from OpenAPI spec" "TypeScript"

                    pages -> apiClient "Calls"
                }

                oauth2Proxy = container "OAuth2 Proxy" "Proxy providing OAuth2 authentication" "quay.io/oauth2-proxy/oauth2-proxy"

                nginx = container "Reverse Proxy" "Routes requests to frontend and backend" "nginx"

                postgres = container "Database" "Stores user, project, portal etc. data" "PostgreSQL"

                api.projectService -> postgres "Reads/writes data"
                api.userService -> postgres "Reads/writes data"
                api.otherService -> postgres "Read/writes data"
                nginx -> api.trehttpHandlers "Proxies /tre/api requests"
                nginx -> api.otherhttpHandlers "Proxies other api requests"
                nginx -> webFrontend.pages "Serves"
                webFrontend.apiClient -> oauth2Proxy "Forwards /web/api requests for authentication"
                oauth2Proxy -> api.webhttpHandlers "Forwards authenticated /web/api requests to backend"

            }

            deploy = container "CI/CD Pipeline" "Builds and deploys infrastructure and app" "GitHub Actions + Terraform" {

                group "Portal App Repository" {
                    gha_portal = component "GitHub Actions (Portal Repo)" "Builds & pushes container images" "GitHub Actions"
                }

                group "Infrastructure Repository" {
                    gha_infra = component "GitHub Actions (Infra Repo)" "Applies Terraform in AWS" "GitHub Actions"
                    tf = component "Terraform Code" "Defines and provisions cloud infra" "Terraform"
                    gha_infra -> tf "Runs"
                }
            }

            portal.deploy.tf -> ghcr "Pulls images for deployment"
            portal.deploy.gha_portal -> ghcr "Pushes container images"
        }

        user -> portal.webFrontend.pages "Accesses via browser"
        admin -> portal.webFrontend.pages "Administers via browser"
        portal.oauth2Proxy -> entra "Authenticates requests against" "SSO"
        tre -> portal.nginx "Accesses /tre/api for data retrieval" "REST/JSON"
        uclSystem -> portal.nginx "Accesses api for data retrieval" "REST/JSON"
    }

    views {

        systemContext portal "C1-Portal_Context" {
            include *
            description "Context diagram showing how the Research Platform fits within the broader environment."
        }

        container portal "C2-Portal_Containers" {
            include *
            description "Container view showing major services running in Kubernetes, including API, frontend, proxy, and DB."
        }

        component portal.api "C3-Web_API_Internals" {
            include *
            description "Component view showing key service components inside the API backend."
        }

        component portal.webFrontend "C3-Web_Frontend_Internals" {
            include *
            description "Component view showing the UI modules and OpenAPI client."
        }

        component portal.deploy "C3-Deployment_Workflow" {
            include *
            description "Component view showing how CI/CD is managed using GitHub Actions and Terraform."
        }

        styles {
            element "Person" {
                shape person
                background #08427b
                color #ffffff
            }

            element "Software System" {
                background #1168bd
                color #ffffff
            }

            element "Container" {
                background #438dd5
                color #ffffff
            }

            element "Component" {
                background #85bbf0
                color #000000
            }

            element "Existing System" {
                background #999999
            }

            element "Group:K8S cluster" {
                color #d86613
                icon https://static.structurizr.com/themes/amazon-web-services-2020.04.30/amazon-elastic-kubernetes-service.png
            }
        }
    }
}

Workflow

  1. Generate and export images
docker pull structurizr/lite
docker run -it --rm -p 8080:8080 -v PATH:/usr/local/structurizr structurizr/lite

ARC Services Portal

CALM

What is CALM?

The Common Architecture Language Model

How does CALM fit in?

  • It does not replace the C4 model, it complements it

C4 Comparison

C4 Model

  • A set of hierarchical abstractions - software systems, containers, components, and code.
  • Diagrams - system context, containers, components (and code)
  • Notation independent
  • Tooling independent

CALM

  • A JSON Meta Schema to define your architecture
  • A CLI for generating, validating, docifying architectures
  • A UI for visualising architectures
  • Provides the notation and tooling C4 is agnostic about

JSON Schemas

… are gross

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://calm.finos.org/release/1.0/meta/core.json",
  "title": "Common Architecture Language Model (CALM) Vocab",
  "type" : "object",
  "properties": {
    "nodes": {
      "type": "array",
      "items": {
        "$ref": "#/defs/node"
      }
    },
    "relationships": {
      "type": "array",
      "items": {
        "$ref": "#/defs/relationship"
      }
    },
    "metadata": {
      "$ref": "#/defs/metadata"
    },
    "controls": {
      "$ref": "control.json#/defs/controls"
    },
    "flows": {
      "type": "array",
      "items": {
        "$ref": "flow.json#/defs/flow"
      }
    },
    "adrs": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "External links to ADRs (Architecture Decision Records) or similar documents that provide context or decisions related to the architecture. These can be URLs or references to internal documentation."
    },
    "additionalProperties": false
  },
  "defs": {
    "node": {
      "type": "object",
      "properties": {
        "unique-id": {
          "type": "string"
        },
        "node-type": {
          "$ref": "#/defs/node-type-definition"
        },
        "name": {
          "type": "string"
        },
        "description": {
          "type": "string"
        },
        "details": {
          "type": "object",
          "properties": {
            "detailed-architecture": {
              "type": "string"
            },
            "required-pattern": {
              "type": "string"
            }
          },
          "additionalProperties": false
        },
        "interfaces": {
          "type": "array",
          "items": {
            "anyOf": [
              { "$ref": "interface.json#/defs/interface-definition" },
              { "$ref": "interface.json#/defs/interface-type" }
            ]
          }
        },
        "controls": {
          "$ref": "control.json#/defs/controls"
        },
        "metadata": {
          "$ref": "#/defs/metadata"
        }
      },
      "required": [
        "unique-id",
        "node-type",
        "name",
        "description"
      ],
      "additionalProperties": true
    },
    "relationship": {
      "type": "object",
      "properties": {
        "unique-id": {
          "type": "string"
        },
        "description": {
          "type": "string"
        },
        "relationship-type": {
          "type": "object",
          "properties": {
            "interacts": {
              "$ref": "#/defs/interacts-type"
            },
            "connects": {
              "$ref": "#/defs/connects-type"
            },
            "deployed-in": {
              "$ref": "#/defs/deployed-in-type"
            },
            "composed-of": {
              "$ref": "#/defs/composed-of-type"
            },
            "options": {
              "$ref": "#/defs/option-type"
            }
          },
          "oneOf": [
            {
              "required": [
                "deployed-in"
              ]
            },
            {
              "required": [
                "composed-of"
              ]
            },
            {
              "required": [
                "interacts"
              ]
            },
            {
              "required": [
                "connects"
              ]
            },
            {
              "required": [
                "options"
              ]
            }
          ]
        },
        "protocol": {
          "$ref": "#/defs/protocol"
        },
        "metadata": {
          "$ref": "#/defs/metadata"
        },
        "controls": {
          "$ref": "control.json#/defs/controls"
        }
      },
      "required": [
        "unique-id",
        "relationship-type"
      ],
      "additionalProperties": true
    },
    "protocol": {
      "enum": [
        "HTTP",
        "HTTPS",
        "FTP",
        "SFTP",
        "JDBC",
        "WebSocket",
        "SocketIO",
        "LDAP",
        "AMQP",
        "TLS",
        "mTLS",
        "TCP"
      ]
    },
    "node-type-definition": {
      "anyOf": [
        {
          "enum": [
            "actor",
            "ecosystem",
            "system",
            "service",
            "database",
            "network",
            "ldap",
            "webclient",
            "data-asset"
          ]
        },
        {
          "type": "string"
        }
      ]
    },
    "interacts-type": {
      "type": "object",
      "properties": {
        "actor": {
          "type": "string"
        },
        "nodes": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "actor",
        "nodes"
      ]
    },
    "connects-type": {
      "type": "object",
      "properties": {
        "source": {
          "$ref": "interface.json#/defs/node-interface"
        },
        "destination": {
          "$ref": "interface.json#/defs/node-interface"
        }
      },
      "required": [
        "source",
        "destination"
      ]
    },
    "deployed-in-type": {
      "type": "object",
      "properties": {
        "container": {
          "type": "string"
        },
        "nodes": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "container",
        "nodes"
      ]
    },
    "composed-of-type": {
      "type": "object",
      "properties": {
        "container": {
          "type": "string"
        },
        "nodes": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "container",
        "nodes"
      ]
    },
    "option-type": {
      "type": "array",
      "items": {
        "$ref": "#/defs/decision"
      }
    },
    "decision": {
      "type": "object",
      "properties": {
        "description": {
          "type": "string"
        },
        "nodes": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "relationships": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "controls": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "description",
        "nodes",
        "relationships"
      ]
    },
    "metadata": {
      "oneOf": [
        {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        {
          "type": "object",
          "additionalProperties": true
        }
      ]
    }
  }
}

CALM’s superpower

Patterns! Through the use of the CALM CLI:

Usage: calm [options] [command]

A set of tools for interacting with the Common Architecture Language Model (CALM)

Options:
  -V, --version       output the version number
  -h, --help          display help for command

Commands:
  generate [options]  Generate an architecture from a CALM pattern file.
  validate [options]  Validate that an architecture conforms to a given CALM pattern.
  docify [options]    Generate a documentation website off your CALM model
  help [command]      display help for command

Patterns in CALM

In CALM, patterns are custom JSON schemas which adhere to the core CALM Meta Schema

{
  "$schema": "https://calm.finos.org/release/1.0/meta/calm.json",
  "title": "Consumer Pattern 3",
  "type": "object",
  "$defs": {
    "node-base": {
      "$ref": "https://calm.finos.org/release/1.0/meta/core.json#/defs/node"
    },
    "relationship-base": {
      "$ref": "https://calm.finos.org/release/1.0/meta/core.json#/defs/relationship"
    }
  },
  "properties": {
    "nodes": {
      "type": "array",
      "minItems": 5,
      "prefixItems": [
        {
          "$ref": "#/$defs/node-base",
          "properties": {
            "description": { "const": "Molecule that triggers a Scheduled Event" },
            "node-type": { "const": "system" },
            "name": { "const": "Trigger" },
            "unique-id": { "const": "trigger" }
          }
        },
        {
          "$ref": "#/$defs/node-base",
          "properties": {
            "description": { "const": "Molecule that sends request to the Enterprise API when it is triggered" },
            "node-type": { "const": "system" },
            "name": { "const": "'Fetch from System' Connector" },
            "unique-id": { "const": "fetch-from-system-connector" }
          }
        },
        {
          "$ref": "#/$defs/node-base",
          "properties": {
            "description": { "const": "Molecule that converts payload to System Data Model" },
            "node-type": { "const": "system" },
            "name": { "const": "Processing" },
            "unique-id": { "const": "processing" }
          }
        },
        {
          "$ref": "#/$defs/node-base",
          "properties": {
            "description": { "const": "Molecule that initiates connection to System and sends payload over it" },
            "node-type": { "const": "system" },
            "name": { "const": "'Send to System' Connector" },
            "unique-id": { "const": "send-to-system-connector" }
          }
        },
        {
          "$ref": "#/$defs/node-base",
          "properties": {
            "description": { "const": "Target System" },
            "node-type": { "const": "system" },
            "name": { "const": "Target System" },
            "unique-id": { "const": "target-system" }
          }
        }
      ]
    },
    "relationships": {
      "type": "array",
      "minItems": 4,
      "prefixItems": [
        {
          "$ref": "#/$defs/relationship-base",
          "properties": {
            "unique-id": { "const": "event-trigger" },
            "description": { "const": "Scheduled event triggers API request" },
            "relationship-type": {
              "const": {
                "connects": {
                  "source": { "node": "trigger" },
                  "destination": { "node": "fetch-from-system-connector" }
                }
              }
            },
            "protocol": { "const": "HTTPS" },
            "authentication": { "const": "OAuth2" },
            "controls": {
              "$ref": "https://calm.finos.org/release/1.0/meta/control.json#/defs/controls",
              "properties": {
                "encryption": {
                  "type": "object",
                  "properties": {
                    "description": {
                      "const": "Encryption controls for data in transit"
                    },
                    "requirements": {
                      "type": "array",
                      "minItems": 1,
                      "prefixItems": [
                        {
                          "$ref": "https://calm.finos.org/release/1.0/meta/control.json#/defs/control-detail",
                          "properties": {
                            "requirement-url": {
                              "const": "/home/fbacon/CALM/demo/requirements/encryption-in-transit-requirement.json"
                            },
                            "config-url": {
                              "const": "/home/fbacon/CALM/demo/configs/encryption-in-transit-config.json"
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "required": ["controls"]
        },
        {
          "$ref": "#/$defs/relationship-base",
          "properties": {
            "unique-id": { "const": "publish-response" },
            "description": { "const": "Publishes response payload for processing" },
            "relationship-type": {
              "const": {
                "connects": {
                  "source": { "node": "fetch-from-system-connector" },
                  "destination": { "node": "processing" }
                }
              }
            },
            "protocol": { "const": "HTTPS" }
          }
        },
        {
          "$ref": "#/$defs/relationship-base",
          "properties": {
            "unique-id": { "const": "processing complete" },
            "description": { "const": "Publishes transformed payload to 'Send to System' Connector" },
            "relationship-type": {
              "const": {
                "connects": {
                  "source": { "node": "processing" },
                  "destination": { "node": "send-to-system-connector" }
                }
              }
            },
            "protocol": { "const": "HTTPS" }
          }
        },
        {
          "$ref": "#/$defs/relationship-base",
          "properties": {
            "unique-id": { "const": "payload-to-system" },
            "description": { "const": "Initiates connection to System and sends payload over it" },
            "relationship-type": {
              "const": {
                "connects": {
                  "source": { "node": "send-to-system-connector" },
                  "destination": { "node": "target-system" }
                }
              }
            },
            "protocol": { "const": "HTTPS" }
          }
        }
      ]
    }
  },
  "required": ["nodes", "relationships"]
}

Controls in Patterns

You can include a control requirement-url and a control config-url

{
  "$schema": "https://calm.finos.org/release/1.0/meta/calm.json",
  "title": "Consumer Pattern 3",
  "type": "object",
  "$defs": {
    "node-base": {
      "$ref": "https://calm.finos.org/release/1.0/meta/core.json#/defs/node"
    },
    "relationship-base": {
      "$ref": "https://calm.finos.org/release/1.0/meta/core.json#/defs/relationship"
    }
  },
  "properties": {
    "nodes": {
      "type": "array",
      "minItems": 5,
      "prefixItems": [
        {
          "$ref": "#/$defs/node-base",
          "properties": {
            "description": { "const": "Molecule that triggers a Scheduled Event" },
            "node-type": { "const": "system" },
            "name": { "const": "Trigger" },
            "unique-id": { "const": "trigger" }
          }
        },
        {
          "$ref": "#/$defs/node-base",
          "properties": {
            "description": { "const": "Molecule that sends request to the Enterprise API when it is triggered" },
            "node-type": { "const": "system" },
            "name": { "const": "'Fetch from System' Connector" },
            "unique-id": { "const": "fetch-from-system-connector" }
          }
        },
        {
          "$ref": "#/$defs/node-base",
          "properties": {
            "description": { "const": "Molecule that converts payload to System Data Model" },
            "node-type": { "const": "system" },
            "name": { "const": "Processing" },
            "unique-id": { "const": "processing" }
          }
        },
        {
          "$ref": "#/$defs/node-base",
          "properties": {
            "description": { "const": "Molecule that initiates connection to System and sends payload over it" },
            "node-type": { "const": "system" },
            "name": { "const": "'Send to System' Connector" },
            "unique-id": { "const": "send-to-system-connector" }
          }
        },
        {
          "$ref": "#/$defs/node-base",
          "properties": {
            "description": { "const": "Target System" },
            "node-type": { "const": "system" },
            "name": { "const": "Target System" },
            "unique-id": { "const": "target-system" }
          }
        }
      ]
    },
    "relationships": {
      "type": "array",
      "minItems": 4,
      "prefixItems": [
        {
          "$ref": "#/$defs/relationship-base",
          "properties": {
            "unique-id": { "const": "event-trigger" },
            "description": { "const": "Scheduled event triggers API request" },
            "relationship-type": {
              "const": {
                "connects": {
                  "source": { "node": "trigger" },
                  "destination": { "node": "fetch-from-system-connector" }
                }
              }
            },
            "protocol": { "const": "HTTPS" },
            "authentication": { "const": "OAuth2" },
            "controls": {
              "$ref": "https://calm.finos.org/release/1.0/meta/control.json#/defs/controls",
              "properties": {
                "encryption": {
                  "type": "object",
                  "properties": {
                    "description": {
                      "const": "Encryption controls for data in transit"
                    },
                    "requirements": {
                      "type": "array",
                      "minItems": 1,
                      "prefixItems": [
                        {
                          "$ref": "https://calm.finos.org/release/1.0/meta/control.json#/defs/control-detail",
                          "properties": {
                            "requirement-url": {
                              "const": "/home/fbacon/CALM/demo/requirements/encryption-in-transit-requirement.json"
                            },
                            "config-url": {
                              "const": "/home/fbacon/CALM/demo/configs/encryption-in-transit-config.json"
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "required": ["controls"]
        },
        {
          "$ref": "#/$defs/relationship-base",
          "properties": {
            "unique-id": { "const": "publish-response" },
            "description": { "const": "Publishes response payload for processing" },
            "relationship-type": {
              "const": {
                "connects": {
                  "source": { "node": "fetch-from-system-connector" },
                  "destination": { "node": "processing" }
                }
              }
            },
            "protocol": { "const": "HTTPS" }
          }
        },
        {
          "$ref": "#/$defs/relationship-base",
          "properties": {
            "unique-id": { "const": "processing complete" },
            "description": { "const": "Publishes transformed payload to 'Send to System' Connector" },
            "relationship-type": {
              "const": {
                "connects": {
                  "source": { "node": "processing" },
                  "destination": { "node": "send-to-system-connector" }
                }
              }
            },
            "protocol": { "const": "HTTPS" }
          }
        },
        {
          "$ref": "#/$defs/relationship-base",
          "properties": {
            "unique-id": { "const": "payload-to-system" },
            "description": { "const": "Initiates connection to System and sends payload over it" },
            "relationship-type": {
              "const": {
                "connects": {
                  "source": { "node": "send-to-system-connector" },
                  "destination": { "node": "target-system" }
                }
              }
            },
            "protocol": { "const": "HTTPS" }
          }
        }
      ]
    }
  },
  "required": ["nodes", "relationships"]
}

Requirement

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "./requirements/encryption-in-transit.json",
  "title": "Encryption in Transit Requirement",
  "description": "Ensures that if HTTPS is used, TLS version is declared and must be >=1.2.",
  "type": "object",
  "properties": {
    "protocol": {
      "type": "string",
      "enum": ["HTTP", "HTTPS"]
    },
    "tls-version": {
      "type": "string",
      "enum": ["1.2", "1.3"]
    }
  },
  "allOf": [
    {
      "if": {
        "properties": { "protocol": { "const": "HTTPS" } }
      },
      "then": {
        "required": ["tls-version"]
      }
    }
  ]
}

Config

{
  "protocol": "HTTPS",
  "tls-version": "1.0"
}

Let’s try a calm validate

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

(calm-demo) fbacon@CS00058225:~/GitHub/presentations/calm/architecture$ calm validate -p pattern.json -a architecture.json
info [file-system-document-loader]:     architecture.json exists, loading as file...
info [file-system-document-loader]:     pattern.json exists, loading as file...
info [calm-validate]:     Formatting output as json
{
    "jsonSchemaValidationOutputs": [],
    "spectralSchemaValidationOutputs": [],
    "hasErrors": false,
    "hasWarnings": false

Now let’s change the architecture

{
  "nodes": [
    {
      "description": "Molecule that triggers a Scheduled Event",
      "node-type": "system",
      "name": "Trigger",
      "unique-id": "trigger"
    },
    {
      "description": "Molecule that sends request to the Enterprise API when it is triggered",
      "node-type": "system",
      "name": "'Fetch from System' Connector",
      "unique-id": "fetch-from-system-connector"
    },
    {
      "description": "Molecule that converts payload to System Data Model",
      "node-type": "system",
      "name": "Processing",
      "unique-id": "processing"
    },
    {
      "description": "Molecule that initiates connection to System and sends payload over it",
      "node-type": "system",
      "name": "'Send to System' Connector",
      "unique-id": "send-to-system-connector"
    },
    {
      "description": "Target System",
      "node-type": "system",
      "name": "Target System",
      "unique-id": "target-system"
    }
  ],
  "relationships": [
    {
      "unique-id": "event-trigger",
      "description": "Scheduled event triggers API request",
      "relationship-type": {
        "connects": {
          "source": {
            "node": "trigger"
          },
          "destination": {
            "node": "fetch-from-system-connector"
          }
        }
      },
      "protocol": "HTTPS",
      "authentication": "OAuth2",
      "controls": {
        "encryption": {
          "description": "Encryption controls for data in transit",
          "requirements": [
            {
              "requirement-url": "/home/fbacon/CALM/demo/requirements/encryption-in-transit-requirement.json",
              "config-url": "/home/fbacon/CALM/demo/configs/encryption-in-transit-config.json"
            }
          ]
        }
      }
    },
    {
      "unique-id": "publish-response",
      "description": "Publishes response payload for processing",
      "relationship-type": {
        "connects": {
          "source": {
            "node": "fetch-from-system-connector"
          },
          "destination": {
            "node": "processing"
          }
        }
      },
      "protocol": "HTTPS"
    },
    {
      "unique-id": "processing complete",
      "description": "Publishes transformed payload to 'Send to System' Connector",
      "relationship-type": {
        "connects": {
          "source": {
            "node": "processing"
          },
          "destination": {
            "node": "send-to-system-connector"
          }
        }
      },
      "protocol": "HTTPS"
    },
    {
      "unique-id": "payload-to-system",
      "description": "Initiates connection to System and sends payload over it",
      "relationship-type": {
        "connects": {
          "source": {
            "node": "send-to-system-connector"
          },
          "destination": {
            "node": "target-system"
          }
        }
      },
      "protocol": "HTTPS"
    }
  ]
}

Now let’s change the architecture

{
  "nodes": [
    {
      "description": "Molecule that triggers a Scheduled Event",
      "node-type": "system",
      "name": "Trigger",
      "unique-id": "trigger"
    },
    {
      "description": "Molecule that sends request to the Enterprise API when it is triggered",
      "node-type": "system",
      "name": "'Fetch from System' Connector",
      "unique-id": "fetch-from-system-connector"
    },
    {
      "description": "Molecule that converts payload to System Data Model",
      "node-type": "system",
      "name": "Processing",
      "unique-id": "processing"
    },
    {
      "description": "Molecule that initiates connection to System and sends payload over it",
      "node-type": "system",
      "name": "'Send to System' Connector",
      "unique-id": "send-to-system-connector"
    },
    {
      "description": "Target System",
      "node-type": "system",
      "name": "Target System",
      "unique-id": "target-system"
    }
  ],
  "relationships": [
    {
      "unique-id": "event-trigger",
      "description": "Scheduled event triggers API request",
      "relationship-type": {
        "connects": {
          "source": {
            "node": "processing"
          },
          "destination": {
            "node": "fetch-from-system-connector"
          }
        }
      },
      "protocol": "HTTPS",
      "authentication": "OAuth2",
      "controls": {
        "encryption": {
          "description": "Encryption controls for data in transit",
          "requirements": [
            {
              "requirement-url": "/home/fbacon/CALM/demo/requirements/encryption-in-transit-requirement.json",
              "config-url": "/home/fbacon/CALM/demo/configs/encryption-in-transit-config.json"
            }
          ]
        }
      }
    },
    {
      "unique-id": "publish-response",
      "description": "Publishes response payload for processing",
      "relationship-type": {
        "connects": {
          "source": {
            "node": "fetch-from-system-connector"
          },
          "destination": {
            "node": "processing"
          }
        }
      },
      "protocol": "HTTPS"
    },
    {
      "unique-id": "processing complete",
      "description": "Publishes transformed payload to 'Send to System' Connector",
      "relationship-type": {
        "connects": {
          "source": {
            "node": "processing"
          },
          "destination": {
            "node": "send-to-system-connector"
          }
        }
      },
      "protocol": "HTTPS"
    },
    {
      "unique-id": "payload-to-system",
      "description": "Initiates connection to System and sends payload over it",
      "relationship-type": {
        "connects": {
          "source": {
            "node": "send-to-system-connector"
          },
          "destination": {
            "node": "target-system"
          }
        }
      },
      "protocol": "HTTPS"
    }
  ]
}

Result?

calm validate -p pattern.json -a invalid-architecture.json

(calm-demo) fbacon@CS00058225:~/GitHub/presentations/calm/architecture$ calm validate -p pattern.json -a bad-architecture.json
info [file-system-document-loader]:     bad-architecture.json exists, loading as file...
info [file-system-document-loader]:     pattern.json exists, loading as file...
info [calm-validate]:     Formatting output as json
{
    "jsonSchemaValidationOutputs": [
        {
            "code": "json-schema",
            "severity": "error",
            "message": "must be equal to constant",
            "path": "/relationships/0/relationship-type",
            "schemaPath": "#/properties/relationships/prefixItems/0/properties/relationship-type/const"
        }
    ],
    "spectralSchemaValidationOutputs": [
        {
            "code": "architecture-nodes-must-be-referenced",
            "severity": "warning",
            "message": "Node with ID 'trigger' is not referenced by any relationships.",
            "path": "/nodes/0/unique-id",
            "schemaPath": "",
            "line_start": 0,
            "line_end": 0,
            "character_start": 118,
            "character_end": 127
        }
    ],
    "hasErrors": true,
    "hasWarnings": true