Skip to content

Model Governance with dbt Mesh

There is another reporting team who want to build on the models you are developing.

They will be consuming two models: - fct_crm_touchpoints: already exists - models in the all_streaming domain/folder: created in Modeling with dbt Mesh

Your job in this extension is to prepare your project so that a separate dbt project can safely depend on it via dbt Mesh. Just like you have done in the previous part, except now you are the producer not the consumer.

That means adding contracts, access controls, groups, and a model version - each of these builds on the last.


Model contracts

A contract is a promise about a model's shape (column names, data types, nullability). Once another project depends on you across a project boundary, you can no longer silently change that shape - dbt Mesh relies on contracts to catch you if you try.

This part is to make sure you get the contract right before you touch access or groups, or you'll be redoing this step later.

Steps:

  1. Open the yml entry for fct_crm_touchpoints.
  2. Add contract: {enforced: true} under the model's config.
  3. Contracts require every column to declare a data_type. Go through each column in fct_crm_touchpoints and add the missing data_type values - check your SQL/warehouse to confirm the actual type dbt is producing for each one (don't guess; a mismatched declared type vs actual type will fail the contract check at build time).
  4. Contracts don't work on ephemeral materializations. Confirm fct_crm_touchpoints is materialized as table or view (check its config - if it's inheriting ephemeral from a parent config, override it explicitly here).
  5. Run dbt run --select fct_crm_touchpoints and confirm it builds clean. If the contract enforcement fails, the error will tell you exactly which column/type mismatch to fix.

Do not add a contract to dim_sales_reps, dim_advertisers, or fct_advertiser_contracts for this exercise - contracts are only required on models something outside your project will actually reference, and none of those three are being exposed.


Access

Access controls decide which models in your project even can be referenced from inside another project. This only makes sense to configure once you know which model is contracted and stable.

dbt Mesh recognizes three access levels: private, protected, and public. For this exercise you only need private and public.

Steps:

  1. Set the access to public on the models you want to be accessible by the downstream team, all other models should be classified as private.
  2. Apply the same pattern to the models in the all_streaming domain: whichever model is the intended "front door" for that domain gets access: public; everything upstream of it in that domain gets access: private.

A quick sanity-check:

If you covered every column of a private model in a public model's select, that's fine - access controls govern which model can be referenced and not what data flows through it.


Groups

Groups are about ownership and organization within your project. Usually, once access is settled, you'll naturally be grouping models along the same public/private lines you just drew.

A dbt group assigns an owner to a set of models and lets you enforce that private models can only be referenced by models in the same group (an extra layer of protection beyond public/private access).

Steps:

  1. Define two groups in your project - one for the CRM domain, one for the streaming domain. Give each an owner (name and/or email - this is who downstream consumers should contact if something breaks).
  2. Assign dim_sales_reps, dim_advertisers, fct_advertiser_contracts, and fct_crm_touchpoints to the CRM group.
  3. Assign the models in the all_streaming domain/folder to the streaming group.
  4. Every model should end up in exactly one group.

Versioning

Model versioning lets multiple versions of the same dbt model live in the codebase and be built at the same time, so a producer can ship a breaking change as a new version while consumers keep querying the old one until they've had time to migrate.

The breaking change: you're going to be splitting the existing occurred_at timestamp column on fct_crm_touchpoints into two separate columns - occurred_at_date and occurred_at_time. This is a breaking change as occurred_at disappears entirely, so any downstream query selecting that column (including the other team's incoming project) will break the moment this ships to latest.

Why now, and why like this: you already contracted and exposed fct_crm_touchpoints in the steps above. The other team is about to start depending on it. If you make this column change without versioning, you either force them to absorb a breaking change with no warning, or you block yourself from ever improving the model.

Steps:

  1. Decide the mechanics first: will v1 keep occurred_at, and v2 introduce occurred_at_date/occurred_at_time Sketch this out before touching yml so you don't end up guessing your way through the config.
  2. In the yml, add a versions: block under fct_crm_touchpoints with two entries: v: 1 and v: 2.
  3. For v: 1, use include: all with an exclude: on the new columns which v1's contract doesn't have yet.
  4. For v: 2, use include: all with an exclude: on the column v2 will no longer have.
  5. Set latest_version: 1 explicitly. Do not let v2 become latest yet - the other team hasn't migrated, and bumping latest out from under them defeats the entire point of this exercise. Treat v2 as a "prerelease" for now.
  6. Create the second model file for v2 (fct_crm_touchpoints_v2.sql by convention) with the actual column split logic - casting occurred_at into a date part and a time part. Leave the existing file as v1's implementation (rename to fct_crm_touchpoints_v1.sql, or leave it as the un-suffixed file - re-read the "How to create a new version of a model" section of the dbt docs if you're unsure which convention to follow).
  7. Set a deprecation_date on v1 - pick something realistic (e.g. one quarter out). This is the signal to the other team for how long they have to migrate before v1 stops being maintained.
  8. Confirm with dbt run --select fct_crm_touchpoints that both versions build, and that dbt run --select fct_crm_touchpoints,version:latest builds only v1.

Communicate this to the other team explicitly - call out in your PR description (or wherever you two coordinate) that occurred_at will eventually disappear, that v2 is available now for early testing, and what the deprecation date is for v1. Model versioning solves the mechanical problem; it doesn't replace telling people it's happening.