Understanding the Data-Driven Metamodel
Most EA tools hard-code their object model in the application source. Canopy takes a different approach: every structural concept — card types, fields, subtypes, relations, stakeholder roles, and calculated fields — is stored as data in the database, not as code. This page explains why that decision was made and what it means in practice.
What the metamodel is
The metamodel is the schema of your EA model. It defines:
- Card types — the kinds of entities you track (Application, BusinessCapability, Initiative, etc.)
- Fields — the properties those entities can hold (lifecycle status, cost, owner, technology stack, etc.)
- Subtypes — fine-grained variants of a type that share the same field set (e.g. Application → Microservice, SaaS, AI Agent)
- Relation types — the allowed edges between types (Application "runs on" ITComponent, Initiative "realises" BusinessCapability, etc.)
- Stakeholder roles — the roles users can hold on a specific card (Technical Owner, Business Owner, Data Steward, etc.)
- Calculated fields — formulas that derive field values from other data at save time
All of these are rows in card_types, relation_types, and associated JSONB columns. None of them require a code change or application restart to modify.
Why data, not code?
Customisation without forks
Different organisations have wildly different EA metamodels. Some teams track "Vendor", "License", and "SLA". Others track "Data Domain", "ESG Capability", and "Value Stream". If the metamodel were code, every customisation would require either a fork of the product or a plugin system — both costly to maintain.
With a data-driven metamodel, an admin can add a new field, rename a card type, or define a custom relation type in minutes through the UI or API. The frontend automatically adapts: the Inventory grid gains a new column, the Card Detail page shows the new section, and the reports can filter on the new field. No deploy required.
Schema evolution without migrations per customer
When Canopy ships a new built-in card type or field, it does so via seed.py — a startup routine that checks whether the built-in row exists and creates it only if missing. Existing customisations are never touched. This means:
- Upgrading Canopy does not overwrite your custom fields
- Built-in types remain editable (you can change the icon, color, or label of "Application" without forking)
- If a built-in default drifts from its original value, a guarded Alembic migration corrects only the unmodified rows
Multi-tenancy in a single codebase
Because the metamodel is data, a single Canopy deployment can serve teams with completely different object models — or the same deployment can be reconfigured entirely without touching the codebase.