What you will be able to do
- Enforce a model contract and know which materializations support it
- Decide between a platform constraint and a data test
- Identify the changes dbt treats as breaking and decide when a new model version is needed
- Roll out a new model version without silently switching consumers to it
1.Contracts guarantee a model's shape
A model's columns usually come from whatever its select returns. That is convenient while you iterate, and risky once other people and processes query the model. A contract fixes the shape in advance. You declare the columns, their data types and, where the platform allows, their constraints. dbt then refuses to build the model if its SQL produces anything else. Contracts are recommended for public models that others rely on, whether inside dbt (other groups and projects) or outside it (dashboards and reports).
models:
- name: dim_customers
config:
contract:
enforced: true
columns:
- name: customer_id
data_type: int
constraints:
- type: not_null
- name: customer_name
data_type: stringA contract is all or nothing. With enforced: true, every column needs a name and a data_type the platform understands. A partial contract is not allowed. For wide models that means a lot of YAML, and the explicitness is deliberate. At build time an enforced contract changes two things. First, a preflight check confirms the query returns matching column names and types, and column order doesn't matter to it. Second, dbt puts the names, types and constraints into the DDL it submits and orders the columns as the contract lists them, not as the SQL selects them.
Not every model can be contracted. SQL models materialized as table, view or incremental can, but each comes with a condition:
- Views support column names and data types, but not constraints.
- Incremental models need on_schema_change: append_new_columns or fail.
- Unsupported: Python models, materialized views, ephemeral models, custom materializations (unless the author added support), and BigQuery models with recursive CTEs.
Checkpoint 1 of 5· Check yourself
Which of these models can enforce a contract that includes column names and data types?
Views support contracts for names and types, though not constraints. Python models, ephemeral models and materialized views can't be contracted.
“views offer support for column names and data types, but not constraints”Source: docs.getdbt.com
Sources1
2.Constraints versus data tests
Declaring a constraint in a contract doesn't mean the platform will enforce it. dbt sorts every constraint, per platform, into one of three categories, and the category decides whether a violation actually stops the build. In cloud warehouses, many constraints can be declared but are not enforced. They are stored as metadata only.
| Category | Effect of a violating row |
|---|---|
| Definable and enforced | The model won't build |
| Definable and not enforced | The model still builds; the constraint is metadata only |
| Not definable and not enforced | The constraint can't be specified on that platform |
Data tests are the more flexible tool. They run after the build against content you can query. They support custom severity thresholds, and their failures are easier to debug because the built model is there to inspect. Replacing a test with a constraint moves the validation to build time and probably costs less compute. It only works when three things are true: the platform enforces that constraint (most enforce only not_null), the model is a table or incremental, and the model has a full contract.
Tests are deliberately not part of the contract. Compare a software API: the structure of the response is the contract, while quality and uptime matter but sit outside it. Changing the structure in a backwards-incompatible way is a breaking change, and that is the point where versions come in.
Checkpoint 2 of 5· Match them up
Match each constraint category to what happens when a row violates it
Tap a term, then the definition that fits it.
Only enforced constraints block a build. A definable but unenforced constraint is accepted in the DDL without being checked.
“a model can still build even if building the model violates the constraint”Source: docs.getdbt.com
Sources1
3.Breaking changes and when to version
With a contract enforced, dbt compares your project with its previous state and raises a contract error when it finds a change that could hurt downstream consumers. The changes it treats as breaking are:
- Removing an existing column.
- Changing the data_type of an existing column.
- Removing or modifying a constraint on an existing column (dbt v1.6 or higher).
- Deleting, renaming or disabling a contracted model (dbt v1.9 or higher). This is an error for versioned models and a warning for unversioned ones.
A new version is for an intentional breaking change. Removing, renaming or retyping a column all qualify. Some changes pass the contract and still change what consumers see: different metric logic, a new grain, modified filters, a rewritten CASE statement. Whether those need a version is a judgment call for the model's owner. Small teams, and models that are still immature, can often just ship the break and have consumers adapt. That stops scaling once people and systems outside your control depend on the model.
Model versions work differently from git. Version control deploys one state of the whole project into an environment, and rolling back means reverting a commit. Model versions live side by side in the same repository and are deployed into the same environment at the same time, the way two or three versions of a web API run together. Each one you keep adds warehouse cost and clutter. Prefer non-breaking changes, and bump the latest version on a predictable cadence, once or twice a year and announced well in advance, rather than for every small change.
Checkpoint 3 of 5· Check yourself
Under an enforced contract, which change does dbt flag as breaking when it compares against the previous state?
Retyping an existing column is on the list of breaking changes. Adding a column and fixing a calculation are non-breaking, and the preflight check ignores column order.
“Changing the data_type of an existing column”Source: docs.getdbt.com
Checkpoint 4 of 5· Exam question
Which two of the following are core building blocks of dbt's model governance framework? (Select two.)(Select 2)
Correct answers: A, B — Access modifiers that control whether a model can be referenced by other groups, packages, or projects.; Model contracts that validate column names, data types, and constraints before a model is allowed to build.
- A. Access is one of governance's core building blocks, distinguishing mature, referenceable data products from private implementation details through private, protected, and public tiers.
- B. Contracts are a core governance building block, guaranteeing a model's shape before it builds so downstream consumers are protected from unannounced schema changes.
- C. Job scheduling and deployment triggers are a dbt Cloud orchestration concern, not a model governance concept, and they are not part of the access/contract/version/group framework.
- D. Custom schema-naming macros control where models land in a warehouse for development purposes, which is unrelated to controlling who can reference a model or validating its schema.
4.Rolling out a new version safely
The riskiest part of releasing a new version is the default. A plain ref('model_name') with no version resolves to whatever latest_version points to. If latest_version isn't set, it is the numerically greatest version. So the moment you add v: 2, every unpinned consumer moves to it, breaking change included. To keep the default stable, give the new version its own SQL file (for example fishtown_analytics_orders_v2.sql), list both versions, and set latest_version to the old one until you're ready to switch.
models:
- name: model_name
latest_version: 2
versions:
- v: 3
- v: 2
- v: 1Tools outside dbt never use ref. They query the table by name. An alias, or a view over the latest version, keeps an unsuffixed fishtown_analytics_orders object in the warehouse that always points at the current version, so BI dashboards keep working. A deprecation_date, supported since dbt 1.6, puts a known end on the migration window. Consumers can test the new version with ref('upstream_project', 'fishtown_analytics_orders', v=2). If it breaks them, they can pin to v=1 for now, but they still have to migrate before the deprecation date.
Checkpoint 5 of 5· Put it in order
Put the producer's steps for introducing a new model version in order
- 1.Clean up the deprecated version
- 2.Communicate the new version to consumers
- 3.Create the new version without changing the default
- 4.Decide whether the change needs a new version
- 5.Set the new version as latest
- 6.Add a deprecation date to the old version
Consumers get a stable default, a known deadline and advance notice before the default switches. Old objects are removed only at the end.
“Step 5: Set the new latest version”Source: docs.getdbt.com
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.A contracted view enforces its not_null constraints the same way a table does.Why is that wrong?
Views support contracts for column names and data types only. Constraints need a table or incremental materialization.
Covered in Contracts guarantee a model's shape
2.Adding v2 to a model's versions list leaves unpinned refs on v1 until you say otherwise.Why is that wrong?
Without an explicit latest_version, unpinned refs resolve to the highest numerical version, so consumers move to v2 immediately.
Covered in Rolling out a new version safely
3.Data tests are part of a model's contract.Why is that wrong?
The contract covers structure only: column names, types and constraints. Tests check content after the build.
Covered in Constraints versus data tests
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“dbt will verify that your model's transformation will produce a dataset matching up with its contract, or it will fail to build.”
↩︎ Contracts guarantee a model's shape“When enforced, your contract must include every column's name and data_type”
↩︎ Contracts guarantee a model's shape“This check is agnostic to the order of columns specified in your model (SQL) or YAML spec.”
↩︎ Contracts guarantee a model's shape“We recommend defining contracts for "public" models that are being relied on downstream.”
↩︎ Contracts guarantee a model's shape“Most platforms only enforce not_null.”
↩︎ Constraints versus data tests“Materializing your model as table or incremental (not view or ephemeral).”
↩︎ Constraints versus data tests“versioned models will raise an error. unversioned models will raise a warning.”
↩︎ Breaking changes and when to version“views offer support for column names and data types, but not constraints”
↩︎ Exam trap 1“When the contract changes in a backwards-incompatible way, it is a breaking change that requires a bump in major version.”
↩︎ Exam trap 3“views offer support for column names and data types, but not constraints”
↩︎ Checkpoint“a model can still build even if building the model violates the constraint”
↩︎ Checkpoint“Changing the data_type of an existing column”
↩︎ Checkpoint - 2.
“Multiple versions of a model will live in the same code repository at the same time”
↩︎ Breaking changes and when to version“opt for a predictable cadence (once or twice a year, communicated well in advance)”
↩︎ Breaking changes and when to version“dbt 1.6 introduced first-class support for deprecating models by specifying a deprecation_date.”
↩︎ Rolling out a new version safely“you don't need a new version—such as adding a new column, or fixing a bug in an existing column's calculation”
↩︎ Prediction - 3.
“Changing logic behind a metric”
↩︎ Breaking changes and when to version“Each version of a model must have its own SQL file.”
↩︎ Rolling out a new version safely“Bear in mind that you will need to migrate at some point before the deprecation date.”
↩︎ Rolling out a new version safely“Without setting this, the default will be the highest numerical version, which could be a breaking change for consumers.”
↩︎ Exam trap 2“Step 5: Set the new latest version”
↩︎ Checkpoint - 4.
“Any unpinned references will resolve to model_name.v2.”
↩︎ Rolling out a new version safely