Skip to content

dbt Mesh

mediapulse_analytics doesn't own a staging layer for news, podcasts, or ads - it reaches into mediapulse_base for that data using dbt Mesh, dbt's mechanism for referencing models across project boundaries. Last topic you actually made dim_subscriptions and fct_streaming_events trustworthy (the fan-out fix), and dim_content_catalog was already fine. This topic is about setting up the mesh side of that properly.

1. Read the dependency declaration

Open mediapulse_analytics/dependencies.yml. This is what tells dbt that mediapulse_analytics depends on mediapulse_base as a project, not a package. Note what's different about a project dependency compared to the dbt_utils package dependency declared in packages.yml.

Hint: Package vs. project dependencies

A package dependency pulls in another project's full source code to compile alongside your own. A project dependency instead references a set of public models as an API - you never parse, compile, or run the upstream project's code yourself. See Project dependencies.

2. Map today's access levels

Open mediapulse_base/dbt_project.yml. For each of these seven models, decide - based on its layer and any model-level overrides - whether mediapulse_analytics is currently allowed to ref() it: stg_news__articles, stg_ads__campaigns, int_campaign_content_spend_allocation, dim_campaigns, dim_content_catalog, dim_subscriptions, fct_streaming_events.

Hint: The three access levels

Models default to protected (referenceable only within their own project). Only public models can be referenced from another project. Check both the project-level defaults and any folder-specific overrides. See Model access.

3. Read the request

This message lands in your team's channel:

From: the mediapulse_analytics team lead

Hey - a few of us are starting to model streaming data properly on our side. We want to pull in your current-platform catalog, subscriptions, and watch events and consolidate them with the legacy StreamView data we already own, so we end up with one clean streaming history instead of two separate ones.

For that to actually be useful to us, we need two things: access to dim_content_catalog, dim_subscriptions, and fct_streaming_events, and a real guarantee that we won't wake up one day to a renamed or dropped column with no warning. We're planning to build on these for a while, so please treat this as a genuine dependency, not a one-off favour.

Separately, a couple of us have also been poking at your ads and news staging models for some quick insights. That work is much more throwaway - we know you're planning to expand that part of the project eventually anyway, so don't go out of your way on our account there. It'll probably get dropped or redone once you do.

Two different asks, with two different levels of commitment attached. Before you touch any config, write down: which models is this message actually asking you to stand behind, and which models is it explicitly telling you not to worry about yet?

4. Grant it - make the models requested public

The analytics team want to work with the cleaned/normalized fact and dimensions you have created for thw streaming domain. Add a folder-level default on streaming/ in dbt_project.yml to open these models up.

Making the config change isn't the same as the analytics team being able to use it. Cross-project ref() resolves against mediapulse_base's most recent successful production run, not your local branch - so until this change is merged and a production job has actually built these models with it, the analytics team's dbt build won't see it at all.

5. Contract what you just promised

An access change is a promise about who can reach a model. A contract is a promise about what they'll find when they do - and once someone outside your project is depending on you, you don't get to silently change a column's name or type out from under them anymore.

Add contract: {enforced: true} under config for dim_content_catalog, dim_subscriptions, and fct_streaming_events. For each one, go through every column and add the missing data_type values - check your SQL/warehouse to confirm the actual type dbt produces, don't guess. Confirm each model is materialized as table or view (contracts don't work on ephemeral), then run dbt run --select dim_content_catalog dim_subscriptions fct_streaming_events and fix anything the contract check flags.

Now decide on the staging layer. stg_streaming__content_ctlg, stg_streaming__subscriptions_lifecycle_rec, and stg_streaming__usr_watch_events_log are also access: public today (staging is public project-wide, not just in streaming/) - so they're just as ref-able as the marts. Should they get a contract too?

Think about it before you answer

A contract is a maintenance commitment where every future change to that model now has to satisfy it. Does the request in step 3 actually ask you to stabilise the staging layer, or only the marts built on top of it? What would a contract on a staging model cost you the next time the raw source adds or renames a column, versus what it would actually protect anyone from?

Extension - version the next breaking change

dim_subscriptions already exposes monthly_fee_dollars instead of the raw cents value - fct_streaming_events still exposes monthly_fee_cents. The platform team wants to fix that inconsistency, but mediapulse_analytics is about to start depending on fct_streaming_events for real (you just wired that up above), so this is now a breaking change you can't just ship.

  1. Decide the mechanics first: v1 keeps monthly_fee_cents, v2 replaces it with monthly_fee_dollars (same / 100.0 conversion dim_subscriptions already does).
  2. In the yml, add a versions: block with v: 1 and v: 2, each using include: all with an exclude: for whichever fee column that version doesn't have.
  3. Set latest_version: 1 explicitly - don't let v2 become latest until mediapulse_analytics has actually migrated.
  4. Create fct_streaming_events_v2.sql with the real conversion logic; leave the existing file as v1.
  5. Set a deprecation_date on v1 - something realistic, a quarter or so out.
  6. Confirm dbt run --select fct_streaming_events builds both versions, and dbt run --select fct_streaming_events,version:latest builds only v1.

Communicate this explicitly - call out in your PR description that monthly_fee_cents will eventually disappear, that v2 is available now for early testing, and what the deprecation date is. Versioning solves the mechanical problem; it doesn't replace telling the consuming team it's happening.

Done?

You've mapped what's actually ref-able across this mesh today, turned a real request into a deliberate access decision instead of just flipping a switch, and backed it with a contract scoped to what you actually promised - not the whole project. If you did the extension, you also shipped a breaking change safely instead of just breaking it.